Skip to content

Commit

Permalink
Merge pull request #100 from VenusProtocol/feat/sweep-token-access-co…
Browse files Browse the repository at this point in the history
…ntrol

[VEN-2718]: Add access controls and receiver to sweepTokenFromPool
  • Loading branch information
kkirka authored Sep 5, 2024
2 parents 727828f + 477e0f8 commit c6d81b4
Show file tree
Hide file tree
Showing 11 changed files with 429 additions and 95 deletions.
Binary file added audits/117_riskFundUpgrade_certik_20240826.pdf
Binary file not shown.
25 changes: 17 additions & 8 deletions contracts/ProtocolReserve/RiskFundV2.sol
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,12 @@ contract RiskFundV2 is AccessControlledV8, RiskFundV2Storage, IRiskFund {
event SweepToken(address indexed token, address indexed to, uint256 amount);

/// @notice Event emitted when tokens are swept and transferred from pool
event SweepTokenFromPool(address indexed token, address indexed comptroller, uint256 amount);
event SweepTokenFromPool(
address indexed token,
address indexed comptroller,
address indexed receiver,
uint256 amount
);

/// @notice Error is thrown when updatePoolState is not called by riskFundConverter
error InvalidRiskFundConverter();
Expand Down Expand Up @@ -146,19 +151,23 @@ contract RiskFundV2 is AccessControlledV8, RiskFundV2Storage, IRiskFund {

/// @notice Function to sweep token from pool
/// @param tokenAddress Address of the asset(token)
/// @param comptroller Pool address to which assets will be transferred
/// @param comptroller Pool address that the assets belong to
/// @param receiver The receiver of the funds
/// @param amount Amount need to sweep from the pool
/// @custom:event Emits sweepTokenFromPool event on success
/// @custom:error ZeroAddressNotAllowed is thrown when tokenAddress/comptroller address is zero
/// @custom:event Emits SweepTokenFromPool event on success
/// @custom:error ZeroAddressNotAllowed is thrown when tokenAddress, comptroller, or receiver address is zero
/// @custom:error ZeroValueNotAllowed is thrown when amount is zero
/// @custom:access Only Governance
/// @custom:access Controlled by AccessControlManager
function sweepTokenFromPool(
address tokenAddress,
address comptroller,
address receiver,
uint256 amount
) external onlyOwner nonReentrant {
) external nonReentrant {
_checkAccessAllowed("sweepTokenFromPool(address,address,address,uint256)");
ensureNonzeroAddress(tokenAddress);
ensureNonzeroAddress(comptroller);
ensureNonzeroAddress(receiver);
ensureNonzeroValue(amount);

uint256 poolReserve = poolAssetsFunds[comptroller][tokenAddress];
Expand All @@ -171,9 +180,9 @@ contract RiskFundV2 is AccessControlledV8, RiskFundV2Storage, IRiskFund {
poolAssetsFunds[comptroller][tokenAddress] = poolReserve - amount;
}

IERC20Upgradeable(tokenAddress).safeTransfer(comptroller, amount);
IERC20Upgradeable(tokenAddress).safeTransfer(receiver, amount);

emit SweepTokenFromPool(tokenAddress, comptroller, amount);
emit SweepTokenFromPool(tokenAddress, comptroller, receiver, amount);
}

/**
Expand Down
7 changes: 6 additions & 1 deletion deployments/bscmainnet.json
Original file line number Diff line number Diff line change
Expand Up @@ -7669,7 +7669,7 @@
]
},
"RiskFundV2_Implementation": {
"address": "0x2F377545Fd095fA59A56Cb1fD7456A2a0B781Cb6",
"address": "0x7Ef5ABbcC9A701e728BeB7Afd4fb5747fAB15A28",
"abi": [
{
"inputs": [],
Expand Down Expand Up @@ -8219,6 +8219,11 @@
"name": "comptroller",
"type": "address"
},
{
"internalType": "address",
"name": "receiver",
"type": "address"
},
{
"internalType": "uint256",
"name": "amount",
Expand Down
85 changes: 48 additions & 37 deletions deployments/bscmainnet/RiskFundV2_Implementation.json

Large diffs are not rendered by default.

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion deployments/bscmainnet_addresses.json
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@
"RiskFundConverter_Implementation": "0xd420Bf9C31F6b4a98875B6e561b13aCB19210647",
"RiskFundConverter_Proxy": "0xA5622D276CcbB8d9BBE3D1ffd1BB11a0032E53F0",
"RiskFundV2": "0xdF31a28D68A2AB381D42b380649Ead7ae2A76E42",
"RiskFundV2_Implementation": "0x2F377545Fd095fA59A56Cb1fD7456A2a0B781Cb6",
"RiskFundV2_Implementation": "0x7Ef5ABbcC9A701e728BeB7Afd4fb5747fAB15A28",
"RiskFundV2_Proxy": "0xdF31a28D68A2AB381D42b380649Ead7ae2A76E42",
"SingleTokenConverterBeacon": "0x4c9D57b05B245c40235D720A5f3A592f3DfF11ca",
"SingleTokenConverterImp": "0x40ed28180Df01FdeB957224E4A5415704B9D5990",
Expand Down
55 changes: 54 additions & 1 deletion deployments/bsctestnet.json
Original file line number Diff line number Diff line change
Expand Up @@ -7669,7 +7669,7 @@
]
},
"RiskFundV2_Implementation": {
"address": "0xcA2A023FBe3be30b7187E88D7FDE1A9a4358B509",
"address": "0x394C9a8cDbbFcAbEAb21fB105311B6B1f09b667a",
"abi": [
{
"inputs": [],
Expand Down Expand Up @@ -7940,6 +7940,31 @@
"name": "SweepToken",
"type": "event"
},
{
"anonymous": false,
"inputs": [
{
"indexed": true,
"internalType": "address",
"name": "token",
"type": "address"
},
{
"indexed": true,
"internalType": "address",
"name": "comptroller",
"type": "address"
},
{
"indexed": false,
"internalType": "uint256",
"name": "amount",
"type": "uint256"
}
],
"name": "SweepTokenFromPool",
"type": "event"
},
{
"anonymous": false,
"inputs": [
Expand Down Expand Up @@ -8182,6 +8207,34 @@
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [
{
"internalType": "address",
"name": "tokenAddress",
"type": "address"
},
{
"internalType": "address",
"name": "comptroller",
"type": "address"
},
{
"internalType": "address",
"name": "receiver",
"type": "address"
},
{
"internalType": "uint256",
"name": "amount",
"type": "uint256"
}
],
"name": "sweepTokenFromPool",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [
{
Expand Down
141 changes: 108 additions & 33 deletions deployments/bsctestnet/RiskFundV2_Implementation.json

Large diffs are not rendered by default.

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion deployments/bsctestnet_addresses.json
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@
"RiskFundConverter_Implementation": "0x857ebb8CAcb97DE5ab719320c9FB3aa16076bFe3",
"RiskFundConverter_Proxy": "0x32Fbf7bBbd79355B86741E3181ef8c1D9bD309Bb",
"RiskFundV2": "0x487CeF72dacABD7E12e633bb3B63815a386f7012",
"RiskFundV2_Implementation": "0xcA2A023FBe3be30b7187E88D7FDE1A9a4358B509",
"RiskFundV2_Implementation": "0x394C9a8cDbbFcAbEAb21fB105311B6B1f09b667a",
"RiskFundV2_Proxy": "0x487CeF72dacABD7E12e633bb3B63815a386f7012",
"SingleTokenConverterBeacon": "0xD2410D8B581D37c5B474CD9Ee0C15F02138AC028",
"SingleTokenConverterImp": "0x42Ec3Eb6F23460dFDfa3aE5688f3415CDfE0C6AD",
Expand Down
43 changes: 30 additions & 13 deletions tests/ProtocolReserve/RiskFundV2.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,14 @@
import { FakeContract, MockContract, smock } from "@defi-wonderland/smock";
import { impersonateAccount, loadFixture } from "@nomicfoundation/hardhat-network-helpers";
import { SignerWithAddress } from "@nomiclabs/hardhat-ethers/signers";
import { expect } from "chai";
import { Signer, constants } from "ethers";
import { parseUnits } from "ethers/lib/utils.js";
import { ethers } from "hardhat";

import { convertToUnit } from "../../helpers/utils";
import {
IAccessControlManagerV8,
IComptroller,
IShortfall,
MockToken,
Expand All @@ -20,7 +22,7 @@ let riskFundConverter: FakeContract<RiskFundConverter>;
let shortfall: FakeContract<IShortfall>;
let riskFund: MockContract<RiskFundV2>;
let tokenA: MockContract<MockToken>;
let admin: Signer;
let admin: SignerWithAddress;
let nonAdmin: Signer;
let comptrollerA: FakeContract<IComptroller>;

Expand Down Expand Up @@ -216,33 +218,50 @@ describe("Risk Fund: Tests", function () {

describe("sweepTokenFromPool", () => {
let riskFundConverterSigner: Signer;
let acm: FakeContract<IAccessControlManagerV8>;
const COMPTROLLER_A_AMOUNT: string = convertToUnit(10, 18);

beforeEach(async () => {
await riskFund.connect(admin).setConvertibleBaseAsset(tokenA.address);
acm = await smock.fake<IAccessControlManagerV8>("IAccessControlManagerV8");
acm.isAllowedToCall.returns(true);
await riskFund.connect(admin).setAccessControlManager(acm.address);

await tokenA.transfer(riskFund.address, COMPTROLLER_A_AMOUNT);
});

it("Reverts on sweepToken() when token address is zero", async () => {
it("Reverts when token address is zero", async () => {
await expect(
riskFund.sweepTokenFromPool(constants.AddressZero, comptrollerA.address, parseUnits("1", 18)),
riskFund.sweepTokenFromPool(constants.AddressZero, comptrollerA.address, admin.address, parseUnits("1", 18)),
).to.be.revertedWithCustomError(riskFund, "ZeroAddressNotAllowed");
});

it("Reverts on sweepToken() when comptroller address is zero", async () => {
it("Reverts when comptroller address is zero", async () => {
await expect(
riskFund.sweepTokenFromPool(tokenA.address, constants.AddressZero, parseUnits("1", 18)),
riskFund.sweepTokenFromPool(tokenA.address, constants.AddressZero, admin.address, parseUnits("1", 18)),
).to.be.revertedWithCustomError(riskFund, "ZeroAddressNotAllowed");
});

it("Reverts on sweepToken() when amount entered is higher than balance", async () => {
it("Reverts when receiver address is zero", async () => {
await expect(
riskFund.sweepTokenFromPool(tokenA.address, comptrollerA.address, constants.AddressZero, parseUnits("1", 18)),
).to.be.revertedWithCustomError(riskFund, "ZeroAddressNotAllowed");
});

it("Reverts when access control manager does not allow the call", async () => {
acm.isAllowedToCall.returns(false);
await expect(
riskFund.sweepTokenFromPool(tokenA.address, comptrollerA.address, admin.address, parseUnits("1", 18)),
).to.be.revertedWithCustomError(riskFund, "Unauthorized");
});

it("Reverts when amount entered is higher than balance", async () => {
await expect(
riskFund.sweepTokenFromPool(tokenA.address, comptrollerA.address, parseUnits("1000", 18)),
riskFund.sweepTokenFromPool(tokenA.address, comptrollerA.address, admin.address, parseUnits("1000", 18)),
).to.be.revertedWithCustomError(riskFund, "InsufficientPoolReserve");
});

it("Sweep tokens from comptroller address", async () => {
it("Sweeps tokens from comptroller address", async () => {
await impersonateAccount(riskFundConverter.address);
riskFundConverterSigner = await ethers.getSigner(riskFundConverter.address);
await admin.sendTransaction({ to: riskFundConverter.address, value: ethers.utils.parseEther("10") });
Expand All @@ -251,11 +270,9 @@ describe("Risk Fund: Tests", function () {
.connect(riskFundConverterSigner)
.updatePoolState(comptrollerA.address, tokenA.address, COMPTROLLER_A_AMOUNT);

await expect(riskFund.sweepTokenFromPool(tokenA.address, comptrollerA.address, 1000)).to.changeTokenBalances(
tokenA,
[comptrollerA.address, riskFund.address],
[1000, -1000],
);
await expect(
riskFund.sweepTokenFromPool(tokenA.address, comptrollerA.address, admin.address, 1000),
).to.changeTokenBalances(tokenA, [admin.address, riskFund.address], [1000, -1000]);
});
});
});

0 comments on commit c6d81b4

Please sign in to comment.