Skip to content

Commit

Permalink
Merge pull request #1 from multidatacore/trusted_spender
Browse files Browse the repository at this point in the history
trusted spenders to avoid approve to trusted defi protocols
  • Loading branch information
pumpurum2 committed Oct 29, 2023
2 parents 2552c73 + e866c7a commit 6fd2f91
Show file tree
Hide file tree
Showing 4 changed files with 181 additions and 2 deletions.
2 changes: 2 additions & 0 deletions contracts/IVaultsFactory.sol
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@ interface IVaultsFactory {
function feeBasisPoints() external view returns (uint256);
function emergencyWithdrawAddress() external view returns (address);

function isTrustedSpender(IVault vault, address spender) external view returns (bool);

function unwrapDelay() external view returns (uint256);
function isPaused(IVault vault) external view returns (bool);
}
26 changes: 26 additions & 0 deletions contracts/Vault.sol
Original file line number Diff line number Diff line change
Expand Up @@ -139,4 +139,30 @@ contract Vault is IVault, ERC20Permit, ReentrancyGuard {
require(balanceOf(from) >= amount + pendingUnwraps[from].amount, "VAULTS: TRANSFER_EXCEEDS_BALANCE");
}
}

function allowance(address owner, address spender) public view virtual override returns (uint256) {
if (factory.isTrustedSpender(this, spender)) {
return type(uint256).max;
}

return super.allowance(owner, spender);
}

function approve(address spender, uint256 amount) public virtual override returns (bool) {
require(!factory.isTrustedSpender(this, spender), "VAULTS: TRUSTED_SENDER");

return super.approve(spender, amount);
}

function increaseAllowance(address spender, uint256 addedValue) public virtual override returns (bool) {
require(!factory.isTrustedSpender(this, spender), "VAULTS: TRUSTED_SENDER");

return super.increaseAllowance(spender, addedValue);
}

function decreaseAllowance(address spender, uint256 subtractedValue) public virtual override returns (bool) {
require(!factory.isTrustedSpender(this, spender), "VAULTS: TRUSTED_SENDER");

return super.decreaseAllowance(spender, subtractedValue);
}
}
18 changes: 18 additions & 0 deletions contracts/VaultsFactory.sol
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,8 @@ contract VaultsFactory is IVaultsFactory, AccessControlEnumerable {
mapping(IVault => bool) public pausedVaults;
bool public allVaultsPaused = false;

mapping(IVault => mapping(address => bool)) public isTrustedSpender;

// Role identifiers for pausing, deploying, and admin actions
bytes32 public constant PAUSE_ROLE = keccak256("PAUSE_ROLE");
bytes32 public constant TEAM_ROLE = keccak256("TEAM_ROLE");
Expand All @@ -27,6 +29,8 @@ contract VaultsFactory is IVaultsFactory, AccessControlEnumerable {
event VaultUnpaused(IVault vaultAddress);
event AllVaultsPaused();
event AllVaultsUnpaused();
event TrustedSpenderAdded(IVault vaultAddress, address spender);
event TrustedSpenderRemoved(IVault vaultAddress, address spender);

constructor(
address weth_,
Expand Down Expand Up @@ -103,6 +107,20 @@ contract VaultsFactory is IVaultsFactory, AccessControlEnumerable {
vault_.emergencyWithdraw(amount_);
}

function addTrustedSpenders(IVault vault_, address[] memory spenders_) external onlyRole(DEFAULT_ADMIN_ROLE) {
for (uint i=0; i < spenders_.length; i++) {
isTrustedSpender[vault_][spenders_[i]] = true;
emit TrustedSpenderAdded(vault_, spenders_[i]);
}
}

function removeTrustedSpenders(IVault vault_, address[] memory spenders_) external onlyRole(TEAM_ROLE) {
for (uint i=0; i < spenders_.length; i++) {
isTrustedSpender[vault_][spenders_[i]] = false;
emit TrustedSpenderRemoved(vault_, spenders_[i]);
}
}

function _setFeeReceiver(address feeReceiver_) internal {
feeReceiver = feeReceiver_;
}
Expand Down
137 changes: 135 additions & 2 deletions test/Vault.js
Original file line number Diff line number Diff line change
Expand Up @@ -49,11 +49,11 @@ describe("Vault", function () {
const vault1addr = (await tx.wait()).events[0].args.vaultAddress;
vault1 = await ethers.getContractAt('Vault', vault1addr)

tx = await vaultsFactory.connect(teamRole).deployVault(token1.address, "", "");
tx = await vaultsFactory.connect(teamRole).deployVault(token2.address, "", "");
const vault2addr = (await tx.wait()).events[0].args.vaultAddress;
vault2 = await ethers.getContractAt('Vault', vault2addr)

tx = await vaultsFactory.connect(teamRole).deployVault(token1.address, "", "");
tx = await vaultsFactory.connect(teamRole).deployVault(token3.address, "", "");
const vault3addr = (await tx.wait()).events[0].args.vaultAddress;
vault3 = await ethers.getContractAt('Vault', vault3addr)

Expand Down Expand Up @@ -465,6 +465,139 @@ describe("Vault", function () {
expect(await token1.balanceOf(addr1.address)).to.equal(ETH('0.7'));
});


it("trusted spender negative", async function () {
await expect(vaultsFactory.connect(addr1).addTrustedSpenders(vault1.address, [])).to.revertedWith("AccessControl: account " + addr1.address.toLowerCase() + " is missing role " + ADMIN_ROLE);
await expect(vaultsFactory.connect(addr2).addTrustedSpenders(vault1.address, [])).to.revertedWith("AccessControl: account " + addr2.address.toLowerCase() + " is missing role " + ADMIN_ROLE);
await expect(vaultsFactory.connect(addr1).removeTrustedSpenders(vault1.address, [])).to.revertedWith("AccessControl: account " + addr1.address.toLowerCase() + " is missing role " + TEAM_ROLE);
await expect(vaultsFactory.connect(addr2).removeTrustedSpenders(vault1.address, [])).to.revertedWith("AccessControl: account " + addr2.address.toLowerCase() + " is missing role " + TEAM_ROLE);
})

it("trusted spender", async function () {
const defi1 = addr1;
const defi2 = addr2;
const user = deployer;


await token1.approve(vault1.address, ETH(1));
await vault1.wrap(ETH(1))
await token2.approve(vault2.address, ETH(1));
await vault2.wrap(ETH(1))

/////////////// check allowance works
expect(await vault1.allowance(user.address, defi1.address)).to.equal(0);

await vault1.approve(defi1.address, 300);
expect(await vault1.allowance(user.address, defi1.address)).to.equal(300);
await vault1.increaseAllowance(defi1.address, 100);
expect(await vault1.allowance(user.address, defi1.address)).to.equal(400);

await vault1.connect(defi1).transferFrom(user.address, defi1.address, 100)
expect(await vault1.allowance(user.address, defi1.address)).to.equal(300);
expect(await vault1.balanceOf(defi1.address)).to.equal(100);
await vault1.connect(defi1).transfer(user.address, 100)
expect(await vault1.balanceOf(defi1.address)).to.equal(0);

await vault1.decreaseAllowance(defi1.address, 300);
expect(await vault1.allowance(user.address, defi1.address)).to.equal(0);

//

await vault2.approve(defi1.address, 300);
expect(await vault2.allowance(user.address, defi1.address)).to.equal(300);
await vault2.increaseAllowance(defi1.address, 100);
expect(await vault2.allowance(user.address, defi1.address)).to.equal(400);

await vault2.connect(defi1).transferFrom(user.address, defi1.address, 100)
expect(await vault2.allowance(user.address, defi1.address)).to.equal(300);
expect(await vault2.balanceOf(defi1.address)).to.equal(100);
await vault2.connect(defi1).transfer(user.address, 100)
expect(await vault2.balanceOf(defi1.address)).to.equal(0);

await vault2.decreaseAllowance(defi1.address, 300);
expect(await vault2.allowance(user.address, defi1.address)).to.equal(0);


await expect(vault1.connect(defi1).transferFrom(user.address, defi1.address, 100)).to.revertedWith("ERC20: insufficient allowance");

/////////////// set trusted

await expect(vaultsFactory.connect(teamRole).addTrustedSpenders(vault1.address, [defi1.address])).to.revertedWith("AccessControl: account " + teamRole.address.toLowerCase() + " is missing role " + ADMIN_ROLE);
await vaultsFactory.connect(adminRole).addTrustedSpenders(vault1.address, [defi1.address]);

/////////////// check allowance works
expect(await vault1.allowance(user.address, defi1.address)).to.equal(BN(2).pow(256).sub(1));

await expect(vault1.approve(defi1.address, 300)).to.revertedWith("VAULTS: TRUSTED_SENDER");
await expect(vault1.increaseAllowance(defi1.address, 300)).to.revertedWith("VAULTS: TRUSTED_SENDER");
await expect(vault1.decreaseAllowance(defi1.address, 300)).to.revertedWith("VAULTS: TRUSTED_SENDER");

await vault1.connect(defi1).transferFrom(user.address, defi1.address, 100)
expect(await vault1.allowance(user.address, defi1.address)).to.equal(BN(2).pow(256).sub(1));
expect(await vault1.balanceOf(defi1.address)).to.equal(100);
await vault1.connect(defi1).transfer(user.address, 100)
expect(await vault1.balanceOf(defi1.address)).to.equal(0);

await expect(vault1.connect(defi2).transferFrom(user.address, defi1.address, 100)).to.revertedWith("ERC20: insufficient allowance");

//

await vault2.approve(defi1.address, 300);
expect(await vault2.allowance(user.address, defi1.address)).to.equal(300);
await vault2.increaseAllowance(defi1.address, 100);
expect(await vault2.allowance(user.address, defi1.address)).to.equal(400);

await vault2.connect(defi1).transferFrom(user.address, defi1.address, 100)
expect(await vault2.allowance(user.address, defi1.address)).to.equal(300);
expect(await vault2.balanceOf(defi1.address)).to.equal(100);
await vault2.connect(defi1).transfer(user.address, 100)
expect(await vault2.balanceOf(defi1.address)).to.equal(0);

await vault2.decreaseAllowance(defi1.address, 300);
expect(await vault2.allowance(user.address, defi1.address)).to.equal(0);

/////////////// remove trusted

await vaultsFactory.connect(teamRole).removeTrustedSpenders(vault1.address, [defi1.address]);

/////////////// check allowance works
expect(await vault1.allowance(user.address, defi1.address)).to.equal(0);

await vault1.approve(defi1.address, 300);
expect(await vault1.allowance(user.address, defi1.address)).to.equal(300);
await vault1.increaseAllowance(defi1.address, 100);
expect(await vault1.allowance(user.address, defi1.address)).to.equal(400);

await vault1.connect(defi1).transferFrom(user.address, defi1.address, 100)
expect(await vault1.allowance(user.address, defi1.address)).to.equal(300);
expect(await vault1.balanceOf(defi1.address)).to.equal(100);
await vault1.connect(defi1).transfer(user.address, 100)
expect(await vault1.balanceOf(defi1.address)).to.equal(0);

await vault1.decreaseAllowance(defi1.address, 300);
expect(await vault1.allowance(user.address, defi1.address)).to.equal(0);

//

await vault2.approve(defi1.address, 300);
expect(await vault2.allowance(user.address, defi1.address)).to.equal(300);
await vault2.increaseAllowance(defi1.address, 100);
expect(await vault2.allowance(user.address, defi1.address)).to.equal(400);

await vault2.connect(defi1).transferFrom(user.address, defi1.address, 100)
expect(await vault2.allowance(user.address, defi1.address)).to.equal(300);
expect(await vault2.balanceOf(defi1.address)).to.equal(100);
await vault2.connect(defi1).transfer(user.address, 100)
expect(await vault2.balanceOf(defi1.address)).to.equal(0);

await vault2.decreaseAllowance(defi1.address, 300);
expect(await vault2.allowance(user.address, defi1.address)).to.equal(0);


await expect(vault1.connect(defi1).transferFrom(user.address, defi1.address, 100)).to.revertedWith("ERC20: insufficient allowance");

});

it("simple permit check", async function () {
await token1.approve(vault1.address, ETH(1));
await vault1.wrap(ETH(1))
Expand Down

0 comments on commit 6fd2f91

Please sign in to comment.