diff --git a/simulations/vip-357/abi/AccessControlManager.json b/simulations/vip-357/abi/AccessControlManager.json new file mode 100644 index 000000000..4a118fcc4 --- /dev/null +++ b/simulations/vip-357/abi/AccessControlManager.json @@ -0,0 +1,360 @@ +[ + { + "inputs": [], + "stateMutability": "nonpayable", + "type": "constructor" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "address", + "name": "account", + "type": "address" + }, + { + "indexed": false, + "internalType": "address", + "name": "contractAddress", + "type": "address" + }, + { + "indexed": false, + "internalType": "string", + "name": "functionSig", + "type": "string" + } + ], + "name": "PermissionGranted", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "address", + "name": "account", + "type": "address" + }, + { + "indexed": false, + "internalType": "address", + "name": "contractAddress", + "type": "address" + }, + { + "indexed": false, + "internalType": "string", + "name": "functionSig", + "type": "string" + } + ], + "name": "PermissionRevoked", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "bytes32", + "name": "role", + "type": "bytes32" + }, + { + "indexed": true, + "internalType": "bytes32", + "name": "previousAdminRole", + "type": "bytes32" + }, + { + "indexed": true, + "internalType": "bytes32", + "name": "newAdminRole", + "type": "bytes32" + } + ], + "name": "RoleAdminChanged", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "bytes32", + "name": "role", + "type": "bytes32" + }, + { + "indexed": true, + "internalType": "address", + "name": "account", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "sender", + "type": "address" + } + ], + "name": "RoleGranted", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "bytes32", + "name": "role", + "type": "bytes32" + }, + { + "indexed": true, + "internalType": "address", + "name": "account", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "sender", + "type": "address" + } + ], + "name": "RoleRevoked", + "type": "event" + }, + { + "inputs": [], + "name": "DEFAULT_ADMIN_ROLE", + "outputs": [ + { + "internalType": "bytes32", + "name": "", + "type": "bytes32" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "bytes32", + "name": "role", + "type": "bytes32" + } + ], + "name": "getRoleAdmin", + "outputs": [ + { + "internalType": "bytes32", + "name": "", + "type": "bytes32" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "contractAddress", + "type": "address" + }, + { + "internalType": "string", + "name": "functionSig", + "type": "string" + }, + { + "internalType": "address", + "name": "accountToPermit", + "type": "address" + } + ], + "name": "giveCallPermission", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "bytes32", + "name": "role", + "type": "bytes32" + }, + { + "internalType": "address", + "name": "account", + "type": "address" + } + ], + "name": "grantRole", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "account", + "type": "address" + }, + { + "internalType": "address", + "name": "contractAddress", + "type": "address" + }, + { + "internalType": "string", + "name": "functionSig", + "type": "string" + } + ], + "name": "hasPermission", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "bytes32", + "name": "role", + "type": "bytes32" + }, + { + "internalType": "address", + "name": "account", + "type": "address" + } + ], + "name": "hasRole", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "account", + "type": "address" + }, + { + "internalType": "string", + "name": "functionSig", + "type": "string" + } + ], + "name": "isAllowedToCall", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "bytes32", + "name": "role", + "type": "bytes32" + }, + { + "internalType": "address", + "name": "account", + "type": "address" + } + ], + "name": "renounceRole", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "contractAddress", + "type": "address" + }, + { + "internalType": "string", + "name": "functionSig", + "type": "string" + }, + { + "internalType": "address", + "name": "accountToRevoke", + "type": "address" + } + ], + "name": "revokeCallPermission", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "bytes32", + "name": "role", + "type": "bytes32" + }, + { + "internalType": "address", + "name": "account", + "type": "address" + } + ], + "name": "revokeRole", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "bytes4", + "name": "interfaceId", + "type": "bytes4" + } + ], + "name": "supportsInterface", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "view", + "type": "function" + } +] diff --git a/simulations/vip-357/abi/DefaultProxyAdmin.json b/simulations/vip-357/abi/DefaultProxyAdmin.json new file mode 100644 index 000000000..f0875d980 --- /dev/null +++ b/simulations/vip-357/abi/DefaultProxyAdmin.json @@ -0,0 +1,162 @@ +[ + { + "inputs": [ + { + "internalType": "address", + "name": "initialOwner", + "type": "address" + } + ], + "stateMutability": "nonpayable", + "type": "constructor" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "previousOwner", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "newOwner", + "type": "address" + } + ], + "name": "OwnershipTransferred", + "type": "event" + }, + { + "inputs": [ + { + "internalType": "contract TransparentUpgradeableProxy", + "name": "proxy", + "type": "address" + }, + { + "internalType": "address", + "name": "newAdmin", + "type": "address" + } + ], + "name": "changeProxyAdmin", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "contract TransparentUpgradeableProxy", + "name": "proxy", + "type": "address" + } + ], + "name": "getProxyAdmin", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "contract TransparentUpgradeableProxy", + "name": "proxy", + "type": "address" + } + ], + "name": "getProxyImplementation", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "owner", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "renounceOwnership", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "newOwner", + "type": "address" + } + ], + "name": "transferOwnership", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "contract TransparentUpgradeableProxy", + "name": "proxy", + "type": "address" + }, + { + "internalType": "address", + "name": "implementation", + "type": "address" + } + ], + "name": "upgrade", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "contract TransparentUpgradeableProxy", + "name": "proxy", + "type": "address" + }, + { + "internalType": "address", + "name": "implementation", + "type": "address" + }, + { + "internalType": "bytes", + "name": "data", + "type": "bytes" + } + ], + "name": "upgradeAndCall", + "outputs": [], + "stateMutability": "payable", + "type": "function" + } +] diff --git a/simulations/vip-357/abi/RiskFundV2.json b/simulations/vip-357/abi/RiskFundV2.json new file mode 100644 index 000000000..4371b652d --- /dev/null +++ b/simulations/vip-357/abi/RiskFundV2.json @@ -0,0 +1,626 @@ +[ + { + "inputs": [], + "name": "InsufficientBalance", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "comptroller", + "type": "address" + }, + { + "internalType": "uint256", + "name": "amount", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "poolReserve", + "type": "uint256" + } + ], + "name": "InsufficientPoolReserve", + "type": "error" + }, + { + "inputs": [], + "name": "InvalidRiskFundConverter", + "type": "error" + }, + { + "inputs": [], + "name": "InvalidShortfallAddress", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "sender", + "type": "address" + }, + { + "internalType": "address", + "name": "calledContract", + "type": "address" + }, + { + "internalType": "string", + "name": "methodSignature", + "type": "string" + } + ], + "name": "Unauthorized", + "type": "error" + }, + { + "inputs": [], + "name": "ZeroAddressNotAllowed", + "type": "error" + }, + { + "inputs": [], + "name": "ZeroValueNotAllowed", + "type": "error" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "oldConvertibleBaseAsset", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "newConvertibleBaseAsset", + "type": "address" + } + ], + "name": "ConvertibleBaseAssetUpdated", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "uint8", + "name": "version", + "type": "uint8" + } + ], + "name": "Initialized", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "address", + "name": "oldAccessControlManager", + "type": "address" + }, + { + "indexed": false, + "internalType": "address", + "name": "newAccessControlManager", + "type": "address" + } + ], + "name": "NewAccessControlManager", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "previousOwner", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "newOwner", + "type": "address" + } + ], + "name": "OwnershipTransferStarted", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "previousOwner", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "newOwner", + "type": "address" + } + ], + "name": "OwnershipTransferred", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "comptroller", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "asset", + "type": "address" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "amount", + "type": "uint256" + } + ], + "name": "PoolAssetsDecreased", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "comptroller", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "asset", + "type": "address" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "amount", + "type": "uint256" + } + ], + "name": "PoolAssetsIncreased", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "oldRiskFundConverter", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "newRiskFundConverter", + "type": "address" + } + ], + "name": "RiskFundConverterUpdated", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "oldShortfallContract", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "newShortfallContract", + "type": "address" + } + ], + "name": "ShortfallContractUpdated", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "token", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "to", + "type": "address" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "amount", + "type": "uint256" + } + ], + "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": [ + { + "indexed": true, + "internalType": "address", + "name": "comptroller", + "type": "address" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "amount", + "type": "uint256" + } + ], + "name": "TransferredReserveForAuction", + "type": "event" + }, + { + "inputs": [], + "name": "acceptOwnership", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "accessControlManager", + "outputs": [ + { + "internalType": "contract IAccessControlManagerV8", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "convertibleBaseAsset", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "comptroller", + "type": "address" + } + ], + "name": "getPoolsBaseAssetReserves", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "maxLoopsLimit", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "owner", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "pendingOwner", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + }, + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "name": "poolAssetsFunds", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "renounceOwnership", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "riskFundConverter", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "accessControlManager_", + "type": "address" + } + ], + "name": "setAccessControlManager", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "convertibleBaseAsset_", + "type": "address" + } + ], + "name": "setConvertibleBaseAsset", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "riskFundConverter_", + "type": "address" + } + ], + "name": "setRiskFundConverter", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "shortfallContractAddress_", + "type": "address" + } + ], + "name": "setShortfallContractAddress", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "shortfall", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "tokenAddress", + "type": "address" + }, + { + "internalType": "address", + "name": "to", + "type": "address" + }, + { + "internalType": "uint256", + "name": "amount", + "type": "uint256" + } + ], + "name": "sweepToken", + "outputs": [], + "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": [ + { + "internalType": "address", + "name": "newOwner", + "type": "address" + } + ], + "name": "transferOwnership", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "comptroller", + "type": "address" + }, + { + "internalType": "uint256", + "name": "amount", + "type": "uint256" + } + ], + "name": "transferReserveForAuction", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "comptroller", + "type": "address" + }, + { + "internalType": "address", + "name": "asset", + "type": "address" + }, + { + "internalType": "uint256", + "name": "amount", + "type": "uint256" + } + ], + "name": "updatePoolState", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + } +] diff --git a/simulations/vip-357/abi/TransparentProxyAbi.json b/simulations/vip-357/abi/TransparentProxyAbi.json new file mode 100644 index 000000000..f65d5eb42 --- /dev/null +++ b/simulations/vip-357/abi/TransparentProxyAbi.json @@ -0,0 +1,146 @@ +[ + { + "inputs": [ + { + "internalType": "address", + "name": "_logic", + "type": "address" + }, + { + "internalType": "address", + "name": "admin_", + "type": "address" + }, + { + "internalType": "bytes", + "name": "_data", + "type": "bytes" + } + ], + "stateMutability": "payable", + "type": "constructor" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "address", + "name": "previousAdmin", + "type": "address" + }, + { + "indexed": false, + "internalType": "address", + "name": "newAdmin", + "type": "address" + } + ], + "name": "AdminChanged", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "beacon", + "type": "address" + } + ], + "name": "BeaconUpgraded", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "implementation", + "type": "address" + } + ], + "name": "Upgraded", + "type": "event" + }, + { + "stateMutability": "payable", + "type": "fallback" + }, + { + "inputs": [], + "name": "admin", + "outputs": [ + { + "internalType": "address", + "name": "admin_", + "type": "address" + } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "newAdmin", + "type": "address" + } + ], + "name": "changeAdmin", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "implementation", + "outputs": [ + { + "internalType": "address", + "name": "implementation_", + "type": "address" + } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "newImplementation", + "type": "address" + } + ], + "name": "upgradeTo", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "newImplementation", + "type": "address" + }, + { + "internalType": "bytes", + "name": "data", + "type": "bytes" + } + ], + "name": "upgradeToAndCall", + "outputs": [], + "stateMutability": "payable", + "type": "function" + }, + { + "stateMutability": "payable", + "type": "receive" + } +] diff --git a/simulations/vip-357/bscmainnet.ts b/simulations/vip-357/bscmainnet.ts new file mode 100644 index 000000000..921e9021d --- /dev/null +++ b/simulations/vip-357/bscmainnet.ts @@ -0,0 +1,72 @@ +import { TransactionResponse } from "@ethersproject/providers"; +import { expect } from "chai"; +import { BigNumberish } from "ethers"; +import { parseEther, parseUnits } from "ethers/lib/utils"; +import { ethers } from "hardhat"; +import { expectEvents, initMainnetUser } from "src/utils"; +import { CRITICAL_TIMELOCK, forking, testVip } from "src/vip-framework"; + +import { NEW_RISK_FUND_IMPL, OLD_RISK_FUND_IMPL, PROXY_ADMIN, RISK_FUND, vip357 } from "../../vips/vip-357/bscmainnet"; +import ACCESS_CONTROL_MANAGER_ABI from "./abi/AccessControlManager.json"; +import PROXY_ADMIN_ABI from "./abi/DefaultProxyAdmin.json"; +import RISK_FUND_ABI from "./abi/RiskFundV2.json"; +import TRANSPARENT_PROXY_ABI from "./abi/TransparentProxyAbi.json"; + +forking(41350730, async () => { + const provider = ethers.provider; + const riskFund = new ethers.Contract(RISK_FUND, RISK_FUND_ABI, provider); + const proxyAdmin = new ethers.Contract(PROXY_ADMIN, PROXY_ADMIN_ABI, provider); + + let state: { + acm: string; + convertibleBaseAsset: string; + maxLoopsLimit: BigNumberish; + owner: string; + shortfall: string; + }; + + before(async () => { + state = { + acm: await riskFund.accessControlManager(), + convertibleBaseAsset: await riskFund.convertibleBaseAsset(), + maxLoopsLimit: await riskFund.maxLoopsLimit(), + owner: await riskFund.owner(), + shortfall: await riskFund.shortfall(), + }; + }); + + describe("Pre-VIP state", () => { + it("RiskFund Proxy should have old implementation", async () => { + expect(await proxyAdmin.getProxyImplementation(RISK_FUND)).to.equal(OLD_RISK_FUND_IMPL); + }); + }); + + testVip("VIP-357", await vip357(), { + callbackAfterExecution: async (txResponse: TransactionResponse) => { + await expectEvents(txResponse, [ACCESS_CONTROL_MANAGER_ABI], ["RoleGranted"], [5]); + await expectEvents(txResponse, [TRANSPARENT_PROXY_ABI], ["Upgraded"], [1]); + }, + }); + + describe("Post-VIP behavior", () => { + it("should set the new implementation for the risk fund", async () => { + expect(await proxyAdmin.getProxyImplementation(RISK_FUND)).to.equal(NEW_RISK_FUND_IMPL); + }); + + it("should preserve storage", async () => { + expect(await riskFund.accessControlManager()).to.equal(state.acm); + expect(await riskFund.convertibleBaseAsset()).to.equal(state.convertibleBaseAsset); + expect(await riskFund.maxLoopsLimit()).to.equal(state.maxLoopsLimit); + expect(await riskFund.owner()).to.equal(state.owner); + expect(await riskFund.shortfall()).to.equal(state.shortfall); + }); + + it("can sweep tokens from pool's allocation", async () => { + const timelock = await initMainnetUser(CRITICAL_TIMELOCK, parseEther("1")); + const usdtAddress = "0x55d398326f99059fF775485246999027B3197955"; + const comptrollerAddress = "0xfD36E2c2a6789Db23113685031d7F16329158384"; + const amount = parseUnits("840320.493777076510891477", 18); + await riskFund.connect(timelock).sweepTokenFromPool(usdtAddress, comptrollerAddress, CRITICAL_TIMELOCK, amount); + }); + }); +}); diff --git a/simulations/vip-357/bsctestnet.ts b/simulations/vip-357/bsctestnet.ts new file mode 100644 index 000000000..42e517811 --- /dev/null +++ b/simulations/vip-357/bsctestnet.ts @@ -0,0 +1,72 @@ +import { TransactionResponse } from "@ethersproject/providers"; +import { expect } from "chai"; +import { BigNumberish } from "ethers"; +import { parseEther, parseUnits } from "ethers/lib/utils"; +import { ethers } from "hardhat"; +import { expectEvents, initMainnetUser } from "src/utils"; +import { CRITICAL_TIMELOCK, forking, testVip } from "src/vip-framework"; + +import { NEW_RISK_FUND_IMPL, OLD_RISK_FUND_IMPL, PROXY_ADMIN, RISK_FUND, vip357 } from "../../vips/vip-357/bsctestnet"; +import ACCESS_CONTROL_MANAGER_ABI from "./abi/AccessControlManager.json"; +import PROXY_ADMIN_ABI from "./abi/DefaultProxyAdmin.json"; +import RISK_FUND_ABI from "./abi/RiskFundV2.json"; +import TRANSPARENT_PROXY_ABI from "./abi/TransparentProxyAbi.json"; + +forking(43116000, async () => { + const provider = ethers.provider; + const riskFund = new ethers.Contract(RISK_FUND, RISK_FUND_ABI, provider); + const proxyAdmin = new ethers.Contract(PROXY_ADMIN, PROXY_ADMIN_ABI, provider); + + let state: { + acm: string; + convertibleBaseAsset: string; + maxLoopsLimit: BigNumberish; + owner: string; + shortfall: string; + }; + + before(async () => { + state = { + acm: await riskFund.accessControlManager(), + convertibleBaseAsset: await riskFund.convertibleBaseAsset(), + maxLoopsLimit: await riskFund.maxLoopsLimit(), + owner: await riskFund.owner(), + shortfall: await riskFund.shortfall(), + }; + }); + + describe("Pre-VIP state", () => { + it("RiskFund Proxy should have old implementation", async () => { + expect(await proxyAdmin.getProxyImplementation(RISK_FUND)).to.equal(OLD_RISK_FUND_IMPL); + }); + }); + + testVip("VIP-357", await vip357(), { + callbackAfterExecution: async (txResponse: TransactionResponse) => { + await expectEvents(txResponse, [ACCESS_CONTROL_MANAGER_ABI], ["RoleGranted"], [5]); + await expectEvents(txResponse, [TRANSPARENT_PROXY_ABI], ["Upgraded"], [1]); + }, + }); + + describe("Post-VIP behavior", () => { + it("should set the new implementation for the risk fund", async () => { + expect(await proxyAdmin.getProxyImplementation(RISK_FUND)).to.equal(NEW_RISK_FUND_IMPL); + }); + + it("should preserve storage", async () => { + expect(await riskFund.accessControlManager()).to.equal(state.acm); + expect(await riskFund.convertibleBaseAsset()).to.equal(state.convertibleBaseAsset); + expect(await riskFund.maxLoopsLimit()).to.equal(state.maxLoopsLimit); + expect(await riskFund.owner()).to.equal(state.owner); + expect(await riskFund.shortfall()).to.equal(state.shortfall); + }); + + it("can sweep tokens from pool's allocation", async () => { + const timelock = await initMainnetUser(CRITICAL_TIMELOCK, parseEther("1")); + const usdtAddress = "0xA11c8D9DC9b66E209Ef60F0C8D969D3CD988782c"; + const comptrollerAddress = "0x94d1820b2D1c7c7452A163983Dc888CEC546b77D"; + const amount = parseUnits("43279", 6); + await riskFund.connect(timelock).sweepTokenFromPool(usdtAddress, comptrollerAddress, CRITICAL_TIMELOCK, amount); + }); + }); +}); diff --git a/vips/vip-357/bscmainnet.ts b/vips/vip-357/bscmainnet.ts new file mode 100644 index 000000000..9fd91803e --- /dev/null +++ b/vips/vip-357/bscmainnet.ts @@ -0,0 +1,102 @@ +import { ProposalType } from "src/types"; +import { makeProposal } from "src/utils"; +import { CRITICAL_TIMELOCK, FAST_TRACK_TIMELOCK, NORMAL_TIMELOCK } from "src/vip-framework"; + +export const ACM = "0x4788629abc6cfca10f9f969efdeaa1cf70c23555"; +export const PROXY_ADMIN = "0x6beb6D2695B67FEb73ad4f172E8E2975497187e4"; +export const RISK_FUND = "0xdF31a28D68A2AB381D42b380649Ead7ae2A76E42"; +export const OLD_RISK_FUND_IMPL = "0x2F377545Fd095fA59A56Cb1fD7456A2a0B781Cb6"; +export const NEW_RISK_FUND_IMPL = "0x7Ef5ABbcC9A701e728BeB7Afd4fb5747fAB15A28"; +export const LIQUIDATOR_CONTRACT = "0x0870793286aada55d39ce7f82fb2766e8004cf43"; + +export const EXPLOITER_WALLET = "0x489A8756C18C0b8B24EC2a2b9FF3D4d447F79BEc"; + +export const vip357 = async () => { + const meta = { + version: "v2", + title: "VIP-357 Extend accessibility to the risk funds", + description: `If passed, this VIP will upgrade the [RiskFund contract](https://bscscan.com/address/0xdF31a28D68A2AB381D42b380649Ead7ae2A76E42), enabling funds to be extracted through Fast-track and Critical VIPs, in addition to the current method of using Normal VIPs. + +Moreover, this VIP would authorize the Venus Governance to liquidate the BNB Exploiter account via any type of VIP’s, and to change the percentage of the liquidation fee for the protocol with Fast-track and Critical VIP’s. This would effectively allow Venus to be the liquidator of that account and to keep the seized BNB supplied to Venus. + +#### Security and additional considerations + +We applied the following security procedures for this upgrade: + +* **Audits:** [Certik](https://www.certik.com/) has audited the changes in the RiskFund contract. +* **VIP execution simulation:** in a simulation environment, validating the new implementation works as expected +* **Deployment on testnet:** the same contract has been deployed to BNB testnet, and used in the Venus Protocol testnet deployment + +#### Audit reports + +* [Certik audit report - 2024/08/26](https://github.com/VenusProtocol/protocol-reserve/blob/477e0f80384461163a5140c7e7f69e44c324bd91/audits/117_riskFundUpgrade_certik_20240826.pdf) + +#### Deployed contracts + +* [New RiskFund implementation on BNB Chain](https://bscscan.com/address/0x7Ef5ABbcC9A701e728BeB7Afd4fb5747fAB15A28) +* [New RiskFund implementation on BNB testnet ](https://testnet.bscscan.com/address/0x394C9a8cDbbFcAbEAb21fB105311B6B1f09b667a) + +#### References + +* [VIP simulation](https://github.com/VenusProtocol/vips/pull/358) +* [Execution on testnet](https://testnet.bscscan.com/tx/0x7241fe30b49b11fcbfa4359fd02aff0cc33dc85d0f94e1f21fe959e54fc61e85) +`, + forDescription: "I agree that Venus Protocol should proceed with this proposal", + againstDescription: "I do not think that Venus Protocol should proceed with this proposal", + abstainDescription: "I am indifferent to whether Venus Protocol proceeds or not", + }; + + return makeProposal( + [ + { + target: PROXY_ADMIN, + signature: "upgrade(address,address)", + params: [RISK_FUND, NEW_RISK_FUND_IMPL], + }, + { + target: ACM, + signature: "giveCallPermission(address,string,address)", + params: [RISK_FUND, "sweepTokenFromPool(address,address,address,uint256)", NORMAL_TIMELOCK], + }, + { + target: ACM, + signature: "giveCallPermission(address,string,address)", + params: [RISK_FUND, "sweepTokenFromPool(address,address,address,uint256)", FAST_TRACK_TIMELOCK], + }, + { + target: ACM, + signature: "giveCallPermission(address,string,address)", + params: [RISK_FUND, "sweepTokenFromPool(address,address,address,uint256)", CRITICAL_TIMELOCK], + }, + { + target: ACM, + signature: "giveCallPermission(address,string,address)", + params: [LIQUIDATOR_CONTRACT, "setTreasuryPercent(uint256)", FAST_TRACK_TIMELOCK], + }, + { + target: ACM, + signature: "giveCallPermission(address,string,address)", + params: [LIQUIDATOR_CONTRACT, "setTreasuryPercent(uint256)", CRITICAL_TIMELOCK], + }, + { + target: LIQUIDATOR_CONTRACT, + signature: "addToAllowlist(address,address)", + params: [EXPLOITER_WALLET, NORMAL_TIMELOCK], + }, + { + target: LIQUIDATOR_CONTRACT, + signature: "addToAllowlist(address,address)", + params: [EXPLOITER_WALLET, FAST_TRACK_TIMELOCK], + }, + { + target: LIQUIDATOR_CONTRACT, + signature: "addToAllowlist(address,address)", + params: [EXPLOITER_WALLET, CRITICAL_TIMELOCK], + }, + ], + meta, + ProposalType.REGULAR, + ); +}; + +export default vip357; diff --git a/vips/vip-357/bsctestnet.ts b/vips/vip-357/bsctestnet.ts new file mode 100644 index 000000000..18f6347bb --- /dev/null +++ b/vips/vip-357/bsctestnet.ts @@ -0,0 +1,60 @@ +import { ProposalType } from "src/types"; +import { makeProposal } from "src/utils"; +import { CRITICAL_TIMELOCK, FAST_TRACK_TIMELOCK, NORMAL_TIMELOCK } from "src/vip-framework"; + +export const ACM = "0x45f8a08F534f34A97187626E05d4b6648Eeaa9AA"; +export const PROXY_ADMIN = "0x7877ffd62649b6a1557b55d4c20fcbab17344c91"; +export const RISK_FUND = "0x487CeF72dacABD7E12e633bb3B63815a386f7012"; +export const OLD_RISK_FUND_IMPL = "0xcA2A023FBe3be30b7187E88D7FDE1A9a4358B509"; +export const NEW_RISK_FUND_IMPL = "0x394C9a8cDbbFcAbEAb21fB105311B6B1f09b667a"; +export const LIQUIDATOR_CONTRACT = "0x55AEABa76ecf144031Ef64E222166eb28Cb4865F"; + +export const vip357 = async () => { + const meta = { + version: "v2", + title: "VIP-357", + description: ``, + forDescription: "I agree that Venus Protocol should proceed with this proposal", + againstDescription: "I do not think that Venus Protocol should proceed with this proposal", + abstainDescription: "I am indifferent to whether Venus Protocol proceeds or not", + }; + + return makeProposal( + [ + { + target: PROXY_ADMIN, + signature: "upgrade(address,address)", + params: [RISK_FUND, NEW_RISK_FUND_IMPL], + }, + { + target: ACM, + signature: "giveCallPermission(address,string,address)", + params: [RISK_FUND, "sweepTokenFromPool(address,address,address,uint256)", NORMAL_TIMELOCK], + }, + { + target: ACM, + signature: "giveCallPermission(address,string,address)", + params: [RISK_FUND, "sweepTokenFromPool(address,address,address,uint256)", FAST_TRACK_TIMELOCK], + }, + { + target: ACM, + signature: "giveCallPermission(address,string,address)", + params: [RISK_FUND, "sweepTokenFromPool(address,address,address,uint256)", CRITICAL_TIMELOCK], + }, + { + target: ACM, + signature: "giveCallPermission(address,string,address)", + params: [LIQUIDATOR_CONTRACT, "setTreasuryPercent(uint256)", FAST_TRACK_TIMELOCK], + }, + { + target: ACM, + signature: "giveCallPermission(address,string,address)", + params: [LIQUIDATOR_CONTRACT, "setTreasuryPercent(uint256)", CRITICAL_TIMELOCK], + }, + ], + meta, + ProposalType.REGULAR, + ); +}; + +export default vip357;