diff --git a/simulations/vip-352/abi/AccessControlManager.json b/simulations/vip-352/abi/AccessControlManager.json new file mode 100644 index 000000000..4a118fcc4 --- /dev/null +++ b/simulations/vip-352/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-352/abi/DefaultProxyAdmin.json b/simulations/vip-352/abi/DefaultProxyAdmin.json new file mode 100644 index 000000000..f0875d980 --- /dev/null +++ b/simulations/vip-352/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-352/abi/RiskFundV2.json b/simulations/vip-352/abi/RiskFundV2.json new file mode 100644 index 000000000..4371b652d --- /dev/null +++ b/simulations/vip-352/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-352/abi/TransparentProxyAbi.json b/simulations/vip-352/abi/TransparentProxyAbi.json new file mode 100644 index 000000000..f65d5eb42 --- /dev/null +++ b/simulations/vip-352/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-352/bscmainnet.ts b/simulations/vip-352/bscmainnet.ts new file mode 100644 index 000000000..79dad6954 --- /dev/null +++ b/simulations/vip-352/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, vip352 } from "../../vips/vip-352/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-352", await vip352(), { + 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-352/bsctestnet.ts b/simulations/vip-352/bsctestnet.ts new file mode 100644 index 000000000..f8912b8fc --- /dev/null +++ b/simulations/vip-352/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, vip352 } from "../../vips/vip-352/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-352", await vip352(), { + 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-352/bscmainnet.ts b/vips/vip-352/bscmainnet.ts new file mode 100644 index 000000000..e438bf1df --- /dev/null +++ b/vips/vip-352/bscmainnet.ts @@ -0,0 +1,77 @@ +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 vip352 = async () => { + const meta = { + version: "v2", + title: "VIP-352", + 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], + }, + { + 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 vip352; diff --git a/vips/vip-352/bsctestnet.ts b/vips/vip-352/bsctestnet.ts new file mode 100644 index 000000000..29d74f7fb --- /dev/null +++ b/vips/vip-352/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 vip352 = async () => { + const meta = { + version: "v2", + title: "VIP-352", + 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 vip352;