diff --git a/simulations/vip-665/abi/ACMCommandsAggregator.json b/simulations/vip-665/abi/ACMCommandsAggregator.json new file mode 100644 index 000000000..0974d4ab3 --- /dev/null +++ b/simulations/vip-665/abi/ACMCommandsAggregator.json @@ -0,0 +1,118 @@ +[ + { + "inputs": [{ "internalType": "contract IAccessControlManagerV8", "name": "_acm", "type": "address" }], + "stateMutability": "nonpayable", + "type": "constructor" + }, + { "inputs": [], "name": "EmptyPermissions", "type": "error" }, + { "inputs": [], "name": "ZeroAddressNotAllowed", "type": "error" }, + { + "anonymous": false, + "inputs": [{ "indexed": false, "internalType": "uint256", "name": "index", "type": "uint256" }], + "name": "GrantPermissionsAdded", + "type": "event" + }, + { + "anonymous": false, + "inputs": [{ "indexed": false, "internalType": "uint256", "name": "index", "type": "uint256" }], + "name": "GrantPermissionsExecuted", + "type": "event" + }, + { + "anonymous": false, + "inputs": [{ "indexed": false, "internalType": "uint256", "name": "index", "type": "uint256" }], + "name": "RevokePermissionsAdded", + "type": "event" + }, + { + "anonymous": false, + "inputs": [{ "indexed": false, "internalType": "uint256", "name": "index", "type": "uint256" }], + "name": "RevokePermissionsExecuted", + "type": "event" + }, + { + "inputs": [], + "name": "ACM", + "outputs": [{ "internalType": "contract IAccessControlManagerV8", "name": "", "type": "address" }], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "components": [ + { "internalType": "address", "name": "contractAddress", "type": "address" }, + { "internalType": "string", "name": "functionSig", "type": "string" }, + { "internalType": "address", "name": "account", "type": "address" } + ], + "internalType": "struct ACMCommandsAggregator.Permission[]", + "name": "_permissions", + "type": "tuple[]" + } + ], + "name": "addGrantPermissions", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "components": [ + { "internalType": "address", "name": "contractAddress", "type": "address" }, + { "internalType": "string", "name": "functionSig", "type": "string" }, + { "internalType": "address", "name": "account", "type": "address" } + ], + "internalType": "struct ACMCommandsAggregator.Permission[]", + "name": "_permissions", + "type": "tuple[]" + } + ], + "name": "addRevokePermissions", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [{ "internalType": "uint256", "name": "index", "type": "uint256" }], + "name": "executeGrantPermissions", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [{ "internalType": "uint256", "name": "index", "type": "uint256" }], + "name": "executeRevokePermissions", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { "internalType": "uint256", "name": "", "type": "uint256" }, + { "internalType": "uint256", "name": "", "type": "uint256" } + ], + "name": "grantPermissions", + "outputs": [ + { "internalType": "address", "name": "contractAddress", "type": "address" }, + { "internalType": "string", "name": "functionSig", "type": "string" }, + { "internalType": "address", "name": "account", "type": "address" } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { "internalType": "uint256", "name": "", "type": "uint256" }, + { "internalType": "uint256", "name": "", "type": "uint256" } + ], + "name": "revokePermissions", + "outputs": [ + { "internalType": "address", "name": "contractAddress", "type": "address" }, + { "internalType": "string", "name": "functionSig", "type": "string" }, + { "internalType": "address", "name": "account", "type": "address" } + ], + "stateMutability": "view", + "type": "function" + } +] diff --git a/simulations/vip-665/abi/AccessControlManager.json b/simulations/vip-665/abi/AccessControlManager.json new file mode 100644 index 000000000..4a118fcc4 --- /dev/null +++ b/simulations/vip-665/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-665/abi/Comptroller.json b/simulations/vip-665/abi/Comptroller.json new file mode 100644 index 000000000..c1e0cc553 --- /dev/null +++ b/simulations/vip-665/abi/Comptroller.json @@ -0,0 +1,3091 @@ +[ + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "contract VToken", + "name": "vToken", + "type": "address" + }, + { + "indexed": true, + "internalType": "enum ComptrollerV9Storage.Action", + "name": "action", + "type": "uint8" + }, + { + "indexed": false, + "internalType": "bool", + "name": "pauseState", + "type": "bool" + } + ], + "name": "ActionPausedMarket", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "bool", + "name": "state", + "type": "bool" + } + ], + "name": "ActionProtocolPaused", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "borrower", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "delegate", + "type": "address" + }, + { + "indexed": false, + "internalType": "bool", + "name": "allowDelegatedBorrows", + "type": "bool" + } + ], + "name": "DelegateUpdated", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "components": [ + { + "internalType": "address", + "name": "facetAddress", + "type": "address" + }, + { + "internalType": "enum IDiamondCut.FacetCutAction", + "name": "action", + "type": "uint8" + }, + { + "internalType": "bytes4[]", + "name": "functionSelectors", + "type": "bytes4[]" + } + ], + "indexed": false, + "internalType": "struct IDiamondCut.FacetCut[]", + "name": "_diamondCut", + "type": "tuple[]" + } + ], + "name": "DiamondCut", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "contract VToken", + "name": "vToken", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "borrower", + "type": "address" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "venusDelta", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "venusBorrowIndex", + "type": "uint256" + } + ], + "name": "DistributedBorrowerVenus", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "contract VToken", + "name": "vToken", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "supplier", + "type": "address" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "venusDelta", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "venusSupplyIndex", + "type": "uint256" + } + ], + "name": "DistributedSupplierVenus", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "uint256", + "name": "amount", + "type": "uint256" + } + ], + "name": "DistributedVAIVaultVenus", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "uint256", + "name": "error", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "info", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "detail", + "type": "uint256" + } + ], + "name": "Failure", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "borrower", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "vToken", + "type": "address" + }, + { + "indexed": false, + "internalType": "bool", + "name": "enable", + "type": "bool" + } + ], + "name": "IsForcedLiquidationEnabledForUserUpdated", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "vToken", + "type": "address" + }, + { + "indexed": false, + "internalType": "bool", + "name": "enable", + "type": "bool" + } + ], + "name": "IsForcedLiquidationEnabledUpdated", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "contract VToken", + "name": "vToken", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "account", + "type": "address" + } + ], + "name": "MarketEntered", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "contract VToken", + "name": "vToken", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "account", + "type": "address" + } + ], + "name": "MarketExited", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "contract VToken", + "name": "vToken", + "type": "address" + } + ], + "name": "MarketListed", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "address", + "name": "oldAccessControlAddress", + "type": "address" + }, + { + "indexed": false, + "internalType": "address", + "name": "newAccessControlAddress", + "type": "address" + } + ], + "name": "NewAccessControl", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "contract VToken", + "name": "vToken", + "type": "address" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "newBorrowCap", + "type": "uint256" + } + ], + "name": "NewBorrowCap", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "uint256", + "name": "oldCloseFactorMantissa", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "newCloseFactorMantissa", + "type": "uint256" + } + ], + "name": "NewCloseFactor", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "contract VToken", + "name": "vToken", + "type": "address" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "oldCollateralFactorMantissa", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "newCollateralFactorMantissa", + "type": "uint256" + } + ], + "name": "NewCollateralFactor", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "address", + "name": "oldComptrollerLens", + "type": "address" + }, + { + "indexed": false, + "internalType": "address", + "name": "newComptrollerLens", + "type": "address" + } + ], + "name": "NewComptrollerLens", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "uint256", + "name": "oldLiquidationIncentiveMantissa", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "newLiquidationIncentiveMantissa", + "type": "uint256" + } + ], + "name": "NewLiquidationIncentive", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "address", + "name": "oldLiquidatorContract", + "type": "address" + }, + { + "indexed": false, + "internalType": "address", + "name": "newLiquidatorContract", + "type": "address" + } + ], + "name": "NewLiquidatorContract", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "address", + "name": "oldPauseGuardian", + "type": "address" + }, + { + "indexed": false, + "internalType": "address", + "name": "newPauseGuardian", + "type": "address" + } + ], + "name": "NewPauseGuardian", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "contract PriceOracle", + "name": "oldPriceOracle", + "type": "address" + }, + { + "indexed": false, + "internalType": "contract PriceOracle", + "name": "newPriceOracle", + "type": "address" + } + ], + "name": "NewPriceOracle", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "contract IPrime", + "name": "oldPrimeToken", + "type": "address" + }, + { + "indexed": false, + "internalType": "contract IPrime", + "name": "newPrimeToken", + "type": "address" + } + ], + "name": "NewPrimeToken", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "contract VToken", + "name": "vToken", + "type": "address" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "newSupplyCap", + "type": "uint256" + } + ], + "name": "NewSupplyCap", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "address", + "name": "oldTreasuryAddress", + "type": "address" + }, + { + "indexed": false, + "internalType": "address", + "name": "newTreasuryAddress", + "type": "address" + } + ], + "name": "NewTreasuryAddress", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "address", + "name": "oldTreasuryGuardian", + "type": "address" + }, + { + "indexed": false, + "internalType": "address", + "name": "newTreasuryGuardian", + "type": "address" + } + ], + "name": "NewTreasuryGuardian", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "uint256", + "name": "oldTreasuryPercent", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "newTreasuryPercent", + "type": "uint256" + } + ], + "name": "NewTreasuryPercent", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "contract VAIControllerInterface", + "name": "oldVAIController", + "type": "address" + }, + { + "indexed": false, + "internalType": "contract VAIControllerInterface", + "name": "newVAIController", + "type": "address" + } + ], + "name": "NewVAIController", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "uint256", + "name": "oldVAIMintRate", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "newVAIMintRate", + "type": "uint256" + } + ], + "name": "NewVAIMintRate", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "vault_", + "type": "address" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "releaseStartBlock_", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "releaseInterval_", + "type": "uint256" + } + ], + "name": "NewVAIVaultInfo", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "uint256", + "name": "oldVenusVAIVaultRate", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "newVenusVAIVaultRate", + "type": "uint256" + } + ], + "name": "NewVenusVAIVaultRate", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "contract VToken", + "name": "vToken", + "type": "address" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "newSpeed", + "type": "uint256" + } + ], + "name": "VenusBorrowSpeedUpdated", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "recipient", + "type": "address" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "amount", + "type": "uint256" + } + ], + "name": "VenusGranted", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "contract VToken", + "name": "vToken", + "type": "address" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "newSpeed", + "type": "uint256" + } + ], + "name": "VenusSupplySpeedUpdated", + "type": "event" + }, + { + "payable": true, + "stateMutability": "payable", + "type": "fallback" + }, + { + "constant": false, + "inputs": [ + { + "internalType": "contract Unitroller", + "name": "unitroller", + "type": "address" + } + ], + "name": "_become", + "outputs": [], + "payable": false, + "stateMutability": "nonpayable", + "type": "function" + }, + { + "constant": false, + "inputs": [ + { + "internalType": "address", + "name": "recipient", + "type": "address" + }, + { + "internalType": "uint256", + "name": "amount", + "type": "uint256" + } + ], + "name": "_grantXVS", + "outputs": [], + "payable": false, + "stateMutability": "nonpayable", + "type": "function" + }, + { + "constant": false, + "inputs": [ + { + "internalType": "address", + "name": "newAccessControlAddress", + "type": "address" + } + ], + "name": "_setAccessControl", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "payable": false, + "stateMutability": "nonpayable", + "type": "function" + }, + { + "constant": false, + "inputs": [ + { + "internalType": "address[]", + "name": "markets_", + "type": "address[]" + }, + { + "internalType": "enum ComptrollerV9Storage.Action[]", + "name": "actions_", + "type": "uint8[]" + }, + { + "internalType": "bool", + "name": "paused_", + "type": "bool" + } + ], + "name": "_setActionsPaused", + "outputs": [], + "payable": false, + "stateMutability": "nonpayable", + "type": "function" + }, + { + "constant": false, + "inputs": [ + { + "internalType": "uint256", + "name": "newCloseFactorMantissa", + "type": "uint256" + } + ], + "name": "_setCloseFactor", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "payable": false, + "stateMutability": "nonpayable", + "type": "function" + }, + { + "constant": false, + "inputs": [ + { + "internalType": "contract VToken", + "name": "vToken", + "type": "address" + }, + { + "internalType": "uint256", + "name": "newCollateralFactorMantissa", + "type": "uint256" + } + ], + "name": "_setCollateralFactor", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "payable": false, + "stateMutability": "nonpayable", + "type": "function" + }, + { + "constant": false, + "inputs": [ + { + "internalType": "contract ComptrollerLensInterface", + "name": "comptrollerLens_", + "type": "address" + } + ], + "name": "_setComptrollerLens", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "payable": false, + "stateMutability": "nonpayable", + "type": "function" + }, + { + "constant": false, + "inputs": [ + { + "internalType": "address", + "name": "vTokenBorrowed", + "type": "address" + }, + { + "internalType": "bool", + "name": "enable", + "type": "bool" + } + ], + "name": "_setForcedLiquidation", + "outputs": [], + "payable": false, + "stateMutability": "nonpayable", + "type": "function" + }, + { + "constant": false, + "inputs": [ + { + "internalType": "address", + "name": "borrower", + "type": "address" + }, + { + "internalType": "address", + "name": "vTokenBorrowed", + "type": "address" + }, + { + "internalType": "bool", + "name": "enable", + "type": "bool" + } + ], + "name": "_setForcedLiquidationForUser", + "outputs": [], + "payable": false, + "stateMutability": "nonpayable", + "type": "function" + }, + { + "constant": false, + "inputs": [ + { + "internalType": "uint256", + "name": "newLiquidationIncentiveMantissa", + "type": "uint256" + } + ], + "name": "_setLiquidationIncentive", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "payable": false, + "stateMutability": "nonpayable", + "type": "function" + }, + { + "constant": false, + "inputs": [ + { + "internalType": "address", + "name": "newLiquidatorContract_", + "type": "address" + } + ], + "name": "_setLiquidatorContract", + "outputs": [], + "payable": false, + "stateMutability": "nonpayable", + "type": "function" + }, + { + "constant": false, + "inputs": [ + { + "internalType": "contract VToken[]", + "name": "vTokens", + "type": "address[]" + }, + { + "internalType": "uint256[]", + "name": "newBorrowCaps", + "type": "uint256[]" + } + ], + "name": "_setMarketBorrowCaps", + "outputs": [], + "payable": false, + "stateMutability": "nonpayable", + "type": "function" + }, + { + "constant": false, + "inputs": [ + { + "internalType": "contract VToken[]", + "name": "vTokens", + "type": "address[]" + }, + { + "internalType": "uint256[]", + "name": "newSupplyCaps", + "type": "uint256[]" + } + ], + "name": "_setMarketSupplyCaps", + "outputs": [], + "payable": false, + "stateMutability": "nonpayable", + "type": "function" + }, + { + "constant": false, + "inputs": [ + { + "internalType": "address", + "name": "newPauseGuardian", + "type": "address" + } + ], + "name": "_setPauseGuardian", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "payable": false, + "stateMutability": "nonpayable", + "type": "function" + }, + { + "constant": false, + "inputs": [ + { + "internalType": "contract PriceOracle", + "name": "newOracle", + "type": "address" + } + ], + "name": "_setPriceOracle", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "payable": false, + "stateMutability": "nonpayable", + "type": "function" + }, + { + "constant": false, + "inputs": [ + { + "internalType": "contract IPrime", + "name": "_prime", + "type": "address" + } + ], + "name": "_setPrimeToken", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "payable": false, + "stateMutability": "nonpayable", + "type": "function" + }, + { + "constant": false, + "inputs": [ + { + "internalType": "bool", + "name": "state", + "type": "bool" + } + ], + "name": "_setProtocolPaused", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "payable": false, + "stateMutability": "nonpayable", + "type": "function" + }, + { + "constant": false, + "inputs": [ + { + "internalType": "address", + "name": "newTreasuryGuardian", + "type": "address" + }, + { + "internalType": "address", + "name": "newTreasuryAddress", + "type": "address" + }, + { + "internalType": "uint256", + "name": "newTreasuryPercent", + "type": "uint256" + } + ], + "name": "_setTreasuryData", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "payable": false, + "stateMutability": "nonpayable", + "type": "function" + }, + { + "constant": false, + "inputs": [ + { + "internalType": "contract VAIControllerInterface", + "name": "vaiController_", + "type": "address" + } + ], + "name": "_setVAIController", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "payable": false, + "stateMutability": "nonpayable", + "type": "function" + }, + { + "constant": false, + "inputs": [ + { + "internalType": "uint256", + "name": "newVAIMintRate", + "type": "uint256" + } + ], + "name": "_setVAIMintRate", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "payable": false, + "stateMutability": "nonpayable", + "type": "function" + }, + { + "constant": false, + "inputs": [ + { + "internalType": "address", + "name": "vault_", + "type": "address" + }, + { + "internalType": "uint256", + "name": "releaseStartBlock_", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "minReleaseAmount_", + "type": "uint256" + } + ], + "name": "_setVAIVaultInfo", + "outputs": [], + "payable": false, + "stateMutability": "nonpayable", + "type": "function" + }, + { + "constant": false, + "inputs": [ + { + "internalType": "contract VToken[]", + "name": "vTokens", + "type": "address[]" + }, + { + "internalType": "uint256[]", + "name": "supplySpeeds", + "type": "uint256[]" + }, + { + "internalType": "uint256[]", + "name": "borrowSpeeds", + "type": "uint256[]" + } + ], + "name": "_setVenusSpeeds", + "outputs": [], + "payable": false, + "stateMutability": "nonpayable", + "type": "function" + }, + { + "constant": false, + "inputs": [ + { + "internalType": "uint256", + "name": "venusVAIVaultRate_", + "type": "uint256" + } + ], + "name": "_setVenusVAIVaultRate", + "outputs": [], + "payable": false, + "stateMutability": "nonpayable", + "type": "function" + }, + { + "constant": false, + "inputs": [ + { + "internalType": "contract VToken", + "name": "vToken", + "type": "address" + } + ], + "name": "_supportMarket", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "payable": false, + "stateMutability": "nonpayable", + "type": "function" + }, + { + "constant": true, + "inputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + }, + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "name": "accountAssets", + "outputs": [ + { + "internalType": "contract VToken", + "name": "", + "type": "address" + } + ], + "payable": false, + "stateMutability": "view", + "type": "function" + }, + { + "constant": true, + "inputs": [ + { + "internalType": "address", + "name": "market", + "type": "address" + }, + { + "internalType": "enum ComptrollerV9Storage.Action", + "name": "action", + "type": "uint8" + } + ], + "name": "actionPaused", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "payable": false, + "stateMutability": "view", + "type": "function" + }, + { + "constant": true, + "inputs": [], + "name": "admin", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "payable": false, + "stateMutability": "view", + "type": "function" + }, + { + "constant": true, + "inputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "name": "allMarkets", + "outputs": [ + { + "internalType": "contract VToken", + "name": "", + "type": "address" + } + ], + "payable": false, + "stateMutability": "view", + "type": "function" + }, + { + "constant": true, + "inputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + }, + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "name": "approvedDelegates", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "payable": false, + "stateMutability": "view", + "type": "function" + }, + { + "constant": false, + "inputs": [ + { + "internalType": "address", + "name": "vToken", + "type": "address" + }, + { + "internalType": "address", + "name": "borrower", + "type": "address" + }, + { + "internalType": "uint256", + "name": "borrowAmount", + "type": "uint256" + } + ], + "name": "borrowAllowed", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "payable": false, + "stateMutability": "nonpayable", + "type": "function" + }, + { + "constant": true, + "inputs": [], + "name": "borrowCapGuardian", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "payable": false, + "stateMutability": "view", + "type": "function" + }, + { + "constant": true, + "inputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "name": "borrowCaps", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "payable": false, + "stateMutability": "view", + "type": "function" + }, + { + "constant": false, + "inputs": [ + { + "internalType": "address", + "name": "vToken", + "type": "address" + }, + { + "internalType": "address", + "name": "borrower", + "type": "address" + }, + { + "internalType": "uint256", + "name": "borrowAmount", + "type": "uint256" + } + ], + "name": "borrowVerify", + "outputs": [], + "payable": false, + "stateMutability": "nonpayable", + "type": "function" + }, + { + "constant": true, + "inputs": [ + { + "internalType": "address", + "name": "account", + "type": "address" + }, + { + "internalType": "contract VToken", + "name": "vToken", + "type": "address" + } + ], + "name": "checkMembership", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "payable": false, + "stateMutability": "view", + "type": "function" + }, + { + "constant": false, + "inputs": [ + { + "internalType": "address[]", + "name": "holders", + "type": "address[]" + }, + { + "internalType": "contract VToken[]", + "name": "vTokens", + "type": "address[]" + }, + { + "internalType": "bool", + "name": "borrowers", + "type": "bool" + }, + { + "internalType": "bool", + "name": "suppliers", + "type": "bool" + }, + { + "internalType": "bool", + "name": "collateral", + "type": "bool" + } + ], + "name": "claimVenus", + "outputs": [], + "payable": false, + "stateMutability": "nonpayable", + "type": "function" + }, + { + "constant": false, + "inputs": [ + { + "internalType": "address", + "name": "holder", + "type": "address" + }, + { + "internalType": "contract VToken[]", + "name": "vTokens", + "type": "address[]" + } + ], + "name": "claimVenus", + "outputs": [], + "payable": false, + "stateMutability": "nonpayable", + "type": "function" + }, + { + "constant": false, + "inputs": [ + { + "internalType": "address", + "name": "holder", + "type": "address" + } + ], + "name": "claimVenus", + "outputs": [], + "payable": false, + "stateMutability": "nonpayable", + "type": "function" + }, + { + "constant": false, + "inputs": [ + { + "internalType": "address[]", + "name": "holders", + "type": "address[]" + }, + { + "internalType": "contract VToken[]", + "name": "vTokens", + "type": "address[]" + }, + { + "internalType": "bool", + "name": "borrowers", + "type": "bool" + }, + { + "internalType": "bool", + "name": "suppliers", + "type": "bool" + } + ], + "name": "claimVenus", + "outputs": [], + "payable": false, + "stateMutability": "nonpayable", + "type": "function" + }, + { + "constant": false, + "inputs": [ + { + "internalType": "address", + "name": "holder", + "type": "address" + } + ], + "name": "claimVenusAsCollateral", + "outputs": [], + "payable": false, + "stateMutability": "nonpayable", + "type": "function" + }, + { + "constant": true, + "inputs": [], + "name": "closeFactorMantissa", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "payable": false, + "stateMutability": "view", + "type": "function" + }, + { + "constant": true, + "inputs": [], + "name": "comptrollerImplementation", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "payable": false, + "stateMutability": "view", + "type": "function" + }, + { + "constant": true, + "inputs": [], + "name": "comptrollerLens", + "outputs": [ + { + "internalType": "contract ComptrollerLensInterface", + "name": "", + "type": "address" + } + ], + "payable": false, + "stateMutability": "view", + "type": "function" + }, + { + "constant": false, + "inputs": [ + { + "components": [ + { + "internalType": "address", + "name": "facetAddress", + "type": "address" + }, + { + "internalType": "enum IDiamondCut.FacetCutAction", + "name": "action", + "type": "uint8" + }, + { + "internalType": "bytes4[]", + "name": "functionSelectors", + "type": "bytes4[]" + } + ], + "internalType": "struct IDiamondCut.FacetCut[]", + "name": "diamondCut_", + "type": "tuple[]" + } + ], + "name": "diamondCut", + "outputs": [], + "payable": false, + "stateMutability": "nonpayable", + "type": "function" + }, + { + "constant": false, + "inputs": [ + { + "internalType": "address[]", + "name": "vTokens", + "type": "address[]" + } + ], + "name": "enterMarkets", + "outputs": [ + { + "internalType": "uint256[]", + "name": "", + "type": "uint256[]" + } + ], + "payable": false, + "stateMutability": "nonpayable", + "type": "function" + }, + { + "constant": false, + "inputs": [ + { + "internalType": "address", + "name": "vTokenAddress", + "type": "address" + } + ], + "name": "exitMarket", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "payable": false, + "stateMutability": "nonpayable", + "type": "function" + }, + { + "constant": true, + "inputs": [ + { + "internalType": "bytes4", + "name": "functionSelector", + "type": "bytes4" + } + ], + "name": "facetAddress", + "outputs": [ + { + "components": [ + { + "internalType": "address", + "name": "facetAddress", + "type": "address" + }, + { + "internalType": "uint96", + "name": "functionSelectorPosition", + "type": "uint96" + } + ], + "internalType": "struct ComptrollerV13Storage.FacetAddressAndPosition", + "name": "", + "type": "tuple" + } + ], + "payable": false, + "stateMutability": "view", + "type": "function" + }, + { + "constant": true, + "inputs": [], + "name": "facetAddresses", + "outputs": [ + { + "internalType": "address[]", + "name": "", + "type": "address[]" + } + ], + "payable": false, + "stateMutability": "view", + "type": "function" + }, + { + "constant": true, + "inputs": [ + { + "internalType": "address", + "name": "facet", + "type": "address" + } + ], + "name": "facetFunctionSelectors", + "outputs": [ + { + "internalType": "bytes4[]", + "name": "", + "type": "bytes4[]" + } + ], + "payable": false, + "stateMutability": "view", + "type": "function" + }, + { + "constant": true, + "inputs": [ + { + "internalType": "address", + "name": "facet", + "type": "address" + } + ], + "name": "facetPosition", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "payable": false, + "stateMutability": "view", + "type": "function" + }, + { + "constant": true, + "inputs": [], + "name": "facets", + "outputs": [ + { + "components": [ + { + "internalType": "address", + "name": "facetAddress", + "type": "address" + }, + { + "internalType": "bytes4[]", + "name": "functionSelectors", + "type": "bytes4[]" + } + ], + "internalType": "struct Diamond.Facet[]", + "name": "", + "type": "tuple[]" + } + ], + "payable": false, + "stateMutability": "view", + "type": "function" + }, + { + "constant": true, + "inputs": [ + { + "internalType": "address", + "name": "account", + "type": "address" + } + ], + "name": "getAccountLiquidity", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "payable": false, + "stateMutability": "view", + "type": "function" + }, + { + "constant": true, + "inputs": [], + "name": "getAllMarkets", + "outputs": [ + { + "internalType": "contract VToken[]", + "name": "", + "type": "address[]" + } + ], + "payable": false, + "stateMutability": "view", + "type": "function" + }, + { + "constant": true, + "inputs": [ + { + "internalType": "address", + "name": "account", + "type": "address" + } + ], + "name": "getAssetsIn", + "outputs": [ + { + "internalType": "contract VToken[]", + "name": "", + "type": "address[]" + } + ], + "payable": false, + "stateMutability": "view", + "type": "function" + }, + { + "constant": true, + "inputs": [ + { + "internalType": "address", + "name": "account", + "type": "address" + }, + { + "internalType": "address", + "name": "vTokenModify", + "type": "address" + }, + { + "internalType": "uint256", + "name": "redeemTokens", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "borrowAmount", + "type": "uint256" + } + ], + "name": "getHypotheticalAccountLiquidity", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "payable": false, + "stateMutability": "view", + "type": "function" + }, + { + "constant": true, + "inputs": [], + "name": "getXVSAddress", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "payable": false, + "stateMutability": "pure", + "type": "function" + }, + { + "constant": true, + "inputs": [], + "name": "getXVSVTokenAddress", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "payable": false, + "stateMutability": "pure", + "type": "function" + }, + { + "constant": true, + "inputs": [], + "name": "isComptroller", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "payable": false, + "stateMutability": "pure", + "type": "function" + }, + { + "constant": true, + "inputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "name": "isForcedLiquidationEnabled", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "payable": false, + "stateMutability": "view", + "type": "function" + }, + { + "constant": true, + "inputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + }, + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "name": "isForcedLiquidationEnabledForUser", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "payable": false, + "stateMutability": "view", + "type": "function" + }, + { + "constant": true, + "inputs": [ + { + "internalType": "address", + "name": "vTokenBorrowed", + "type": "address" + }, + { + "internalType": "address", + "name": "vTokenCollateral", + "type": "address" + }, + { + "internalType": "address", + "name": "liquidator", + "type": "address" + }, + { + "internalType": "address", + "name": "borrower", + "type": "address" + }, + { + "internalType": "uint256", + "name": "repayAmount", + "type": "uint256" + } + ], + "name": "liquidateBorrowAllowed", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "payable": false, + "stateMutability": "view", + "type": "function" + }, + { + "constant": false, + "inputs": [ + { + "internalType": "address", + "name": "vTokenBorrowed", + "type": "address" + }, + { + "internalType": "address", + "name": "vTokenCollateral", + "type": "address" + }, + { + "internalType": "address", + "name": "liquidator", + "type": "address" + }, + { + "internalType": "address", + "name": "borrower", + "type": "address" + }, + { + "internalType": "uint256", + "name": "actualRepayAmount", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "seizeTokens", + "type": "uint256" + } + ], + "name": "liquidateBorrowVerify", + "outputs": [], + "payable": false, + "stateMutability": "nonpayable", + "type": "function" + }, + { + "constant": true, + "inputs": [ + { + "internalType": "address", + "name": "vTokenBorrowed", + "type": "address" + }, + { + "internalType": "address", + "name": "vTokenCollateral", + "type": "address" + }, + { + "internalType": "uint256", + "name": "actualRepayAmount", + "type": "uint256" + } + ], + "name": "liquidateCalculateSeizeTokens", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "payable": false, + "stateMutability": "view", + "type": "function" + }, + { + "constant": true, + "inputs": [ + { + "internalType": "address", + "name": "vTokenCollateral", + "type": "address" + }, + { + "internalType": "uint256", + "name": "actualRepayAmount", + "type": "uint256" + } + ], + "name": "liquidateVAICalculateSeizeTokens", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "payable": false, + "stateMutability": "view", + "type": "function" + }, + { + "constant": true, + "inputs": [], + "name": "liquidationIncentiveMantissa", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "payable": false, + "stateMutability": "view", + "type": "function" + }, + { + "constant": true, + "inputs": [], + "name": "liquidatorContract", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "payable": false, + "stateMutability": "view", + "type": "function" + }, + { + "constant": true, + "inputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "name": "markets", + "outputs": [ + { + "internalType": "bool", + "name": "isListed", + "type": "bool" + }, + { + "internalType": "uint256", + "name": "collateralFactorMantissa", + "type": "uint256" + }, + { + "internalType": "bool", + "name": "isVenus", + "type": "bool" + } + ], + "payable": false, + "stateMutability": "view", + "type": "function" + }, + { + "constant": true, + "inputs": [], + "name": "maxAssets", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "payable": false, + "stateMutability": "view", + "type": "function" + }, + { + "constant": true, + "inputs": [], + "name": "minReleaseAmount", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "payable": false, + "stateMutability": "view", + "type": "function" + }, + { + "constant": false, + "inputs": [ + { + "internalType": "address", + "name": "vToken", + "type": "address" + }, + { + "internalType": "address", + "name": "minter", + "type": "address" + }, + { + "internalType": "uint256", + "name": "mintAmount", + "type": "uint256" + } + ], + "name": "mintAllowed", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "payable": false, + "stateMutability": "nonpayable", + "type": "function" + }, + { + "constant": true, + "inputs": [], + "name": "mintVAIGuardianPaused", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "payable": false, + "stateMutability": "view", + "type": "function" + }, + { + "constant": false, + "inputs": [ + { + "internalType": "address", + "name": "vToken", + "type": "address" + }, + { + "internalType": "address", + "name": "minter", + "type": "address" + }, + { + "internalType": "uint256", + "name": "actualMintAmount", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "mintTokens", + "type": "uint256" + } + ], + "name": "mintVerify", + "outputs": [], + "payable": false, + "stateMutability": "nonpayable", + "type": "function" + }, + { + "constant": true, + "inputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "name": "mintedVAIs", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "payable": false, + "stateMutability": "view", + "type": "function" + }, + { + "constant": true, + "inputs": [], + "name": "oracle", + "outputs": [ + { + "internalType": "contract PriceOracle", + "name": "", + "type": "address" + } + ], + "payable": false, + "stateMutability": "view", + "type": "function" + }, + { + "constant": true, + "inputs": [], + "name": "pauseGuardian", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "payable": false, + "stateMutability": "view", + "type": "function" + }, + { + "constant": true, + "inputs": [], + "name": "pendingAdmin", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "payable": false, + "stateMutability": "view", + "type": "function" + }, + { + "constant": true, + "inputs": [], + "name": "pendingComptrollerImplementation", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "payable": false, + "stateMutability": "view", + "type": "function" + }, + { + "constant": true, + "inputs": [], + "name": "prime", + "outputs": [ + { + "internalType": "contract IPrime", + "name": "", + "type": "address" + } + ], + "payable": false, + "stateMutability": "view", + "type": "function" + }, + { + "constant": true, + "inputs": [], + "name": "protocolPaused", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "payable": false, + "stateMutability": "view", + "type": "function" + }, + { + "constant": false, + "inputs": [ + { + "internalType": "address", + "name": "vToken", + "type": "address" + }, + { + "internalType": "address", + "name": "redeemer", + "type": "address" + }, + { + "internalType": "uint256", + "name": "redeemTokens", + "type": "uint256" + } + ], + "name": "redeemAllowed", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "payable": false, + "stateMutability": "nonpayable", + "type": "function" + }, + { + "constant": false, + "inputs": [ + { + "internalType": "address", + "name": "vToken", + "type": "address" + }, + { + "internalType": "address", + "name": "redeemer", + "type": "address" + }, + { + "internalType": "uint256", + "name": "redeemAmount", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "redeemTokens", + "type": "uint256" + } + ], + "name": "redeemVerify", + "outputs": [], + "payable": false, + "stateMutability": "nonpayable", + "type": "function" + }, + { + "constant": true, + "inputs": [], + "name": "releaseStartBlock", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "payable": false, + "stateMutability": "view", + "type": "function" + }, + { + "constant": false, + "inputs": [ + { + "internalType": "address", + "name": "vToken", + "type": "address" + }, + { + "internalType": "address", + "name": "payer", + "type": "address" + }, + { + "internalType": "address", + "name": "borrower", + "type": "address" + }, + { + "internalType": "uint256", + "name": "repayAmount", + "type": "uint256" + } + ], + "name": "repayBorrowAllowed", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "payable": false, + "stateMutability": "nonpayable", + "type": "function" + }, + { + "constant": false, + "inputs": [ + { + "internalType": "address", + "name": "vToken", + "type": "address" + }, + { + "internalType": "address", + "name": "payer", + "type": "address" + }, + { + "internalType": "address", + "name": "borrower", + "type": "address" + }, + { + "internalType": "uint256", + "name": "actualRepayAmount", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "borrowerIndex", + "type": "uint256" + } + ], + "name": "repayBorrowVerify", + "outputs": [], + "payable": false, + "stateMutability": "nonpayable", + "type": "function" + }, + { + "constant": true, + "inputs": [], + "name": "repayVAIGuardianPaused", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "payable": false, + "stateMutability": "view", + "type": "function" + }, + { + "constant": false, + "inputs": [ + { + "internalType": "address", + "name": "vTokenCollateral", + "type": "address" + }, + { + "internalType": "address", + "name": "vTokenBorrowed", + "type": "address" + }, + { + "internalType": "address", + "name": "liquidator", + "type": "address" + }, + { + "internalType": "address", + "name": "borrower", + "type": "address" + }, + { + "internalType": "uint256", + "name": "seizeTokens", + "type": "uint256" + } + ], + "name": "seizeAllowed", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "payable": false, + "stateMutability": "nonpayable", + "type": "function" + }, + { + "constant": false, + "inputs": [ + { + "internalType": "address", + "name": "vTokenCollateral", + "type": "address" + }, + { + "internalType": "address", + "name": "vTokenBorrowed", + "type": "address" + }, + { + "internalType": "address", + "name": "liquidator", + "type": "address" + }, + { + "internalType": "address", + "name": "borrower", + "type": "address" + }, + { + "internalType": "uint256", + "name": "seizeTokens", + "type": "uint256" + } + ], + "name": "seizeVerify", + "outputs": [], + "payable": false, + "stateMutability": "nonpayable", + "type": "function" + }, + { + "constant": false, + "inputs": [ + { + "internalType": "address", + "name": "owner", + "type": "address" + }, + { + "internalType": "uint256", + "name": "amount", + "type": "uint256" + } + ], + "name": "setMintedVAIOf", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "payable": false, + "stateMutability": "nonpayable", + "type": "function" + }, + { + "constant": true, + "inputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "name": "supplyCaps", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "payable": false, + "stateMutability": "view", + "type": "function" + }, + { + "constant": false, + "inputs": [ + { + "internalType": "address", + "name": "vToken", + "type": "address" + }, + { + "internalType": "address", + "name": "src", + "type": "address" + }, + { + "internalType": "address", + "name": "dst", + "type": "address" + }, + { + "internalType": "uint256", + "name": "transferTokens", + "type": "uint256" + } + ], + "name": "transferAllowed", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "payable": false, + "stateMutability": "nonpayable", + "type": "function" + }, + { + "constant": false, + "inputs": [ + { + "internalType": "address", + "name": "vToken", + "type": "address" + }, + { + "internalType": "address", + "name": "src", + "type": "address" + }, + { + "internalType": "address", + "name": "dst", + "type": "address" + }, + { + "internalType": "uint256", + "name": "transferTokens", + "type": "uint256" + } + ], + "name": "transferVerify", + "outputs": [], + "payable": false, + "stateMutability": "nonpayable", + "type": "function" + }, + { + "constant": true, + "inputs": [], + "name": "treasuryAddress", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "payable": false, + "stateMutability": "view", + "type": "function" + }, + { + "constant": true, + "inputs": [], + "name": "treasuryGuardian", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "payable": false, + "stateMutability": "view", + "type": "function" + }, + { + "constant": true, + "inputs": [], + "name": "treasuryPercent", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "payable": false, + "stateMutability": "view", + "type": "function" + }, + { + "constant": false, + "inputs": [ + { + "internalType": "address", + "name": "delegate", + "type": "address" + }, + { + "internalType": "bool", + "name": "allowBorrows", + "type": "bool" + } + ], + "name": "updateDelegate", + "outputs": [], + "payable": false, + "stateMutability": "nonpayable", + "type": "function" + }, + { + "constant": true, + "inputs": [], + "name": "vaiController", + "outputs": [ + { + "internalType": "contract VAIControllerInterface", + "name": "", + "type": "address" + } + ], + "payable": false, + "stateMutability": "view", + "type": "function" + }, + { + "constant": true, + "inputs": [], + "name": "vaiMintRate", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "payable": false, + "stateMutability": "view", + "type": "function" + }, + { + "constant": true, + "inputs": [], + "name": "vaiVaultAddress", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "payable": false, + "stateMutability": "view", + "type": "function" + }, + { + "constant": true, + "inputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "name": "venusAccrued", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "payable": false, + "stateMutability": "view", + "type": "function" + }, + { + "constant": true, + "inputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "name": "venusBorrowSpeeds", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "payable": false, + "stateMutability": "view", + "type": "function" + }, + { + "constant": true, + "inputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "name": "venusBorrowState", + "outputs": [ + { + "internalType": "uint224", + "name": "index", + "type": "uint224" + }, + { + "internalType": "uint32", + "name": "block", + "type": "uint32" + } + ], + "payable": false, + "stateMutability": "view", + "type": "function" + }, + { + "constant": true, + "inputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + }, + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "name": "venusBorrowerIndex", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "payable": false, + "stateMutability": "view", + "type": "function" + }, + { + "constant": true, + "inputs": [], + "name": "venusInitialIndex", + "outputs": [ + { + "internalType": "uint224", + "name": "", + "type": "uint224" + } + ], + "payable": false, + "stateMutability": "view", + "type": "function" + }, + { + "constant": true, + "inputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + }, + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "name": "venusSupplierIndex", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "payable": false, + "stateMutability": "view", + "type": "function" + }, + { + "constant": true, + "inputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "name": "venusSupplySpeeds", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "payable": false, + "stateMutability": "view", + "type": "function" + }, + { + "constant": true, + "inputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "name": "venusSupplyState", + "outputs": [ + { + "internalType": "uint224", + "name": "index", + "type": "uint224" + }, + { + "internalType": "uint32", + "name": "block", + "type": "uint32" + } + ], + "payable": false, + "stateMutability": "view", + "type": "function" + }, + { + "constant": true, + "inputs": [], + "name": "venusVAIVaultRate", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "payable": false, + "stateMutability": "view", + "type": "function" + } +] diff --git a/simulations/vip-665/arbitrumone.ts b/simulations/vip-665/arbitrumone.ts new file mode 100644 index 000000000..f7ecc3a0f --- /dev/null +++ b/simulations/vip-665/arbitrumone.ts @@ -0,0 +1,4 @@ +import { runRemoteSim } from "./utils/remoteSim"; + +// Recent arbitrumone block where the CriticalTimelock still holds the permissions this VIP revokes. +runRemoteSim("arbitrumone", 483738162); diff --git a/simulations/vip-665/basemainnet.ts b/simulations/vip-665/basemainnet.ts new file mode 100644 index 000000000..7685ffd12 --- /dev/null +++ b/simulations/vip-665/basemainnet.ts @@ -0,0 +1,4 @@ +import { runRemoteSim } from "./utils/remoteSim"; + +// Recent basemainnet block where the CriticalTimelock still holds the permissions this VIP revokes. +runRemoteSim("basemainnet", 48618074); diff --git a/simulations/vip-665/bscmainnet.ts b/simulations/vip-665/bscmainnet.ts new file mode 100644 index 000000000..1ef5b6cf2 --- /dev/null +++ b/simulations/vip-665/bscmainnet.ts @@ -0,0 +1,264 @@ +import { TransactionResponse } from "@ethersproject/abstract-provider"; +import { expect } from "chai"; +import { Contract } from "ethers"; +import { ethers } from "hardhat"; +import { NETWORK_ADDRESSES } from "src/networkAddresses"; +import { expectEvents, initMainnetUser } from "src/utils"; +import { forking, testVip } from "src/vip-framework"; + +import vip665, { EXPECTED_ROLE_EVENTS } from "../../vips/vip-665/bscmainnet"; +import { BNB_ACM, BNB_CRITICAL, BNB_GUARDIANS, ZERO } from "../../vips/vip-665/data/addresses"; +import { CLEANUP_LEGACY_WILDCARD_REVOKES, REDUNDANT_REVOKES, STALE_ROWS } from "../../vips/vip-665/data/cleanup"; +import { BNB_ACTIONS, BNB_ACTION_LEGACY_WILDCARD_REVOKES } from "../../vips/vip-665/data/criticalChanges"; +import { + AGGREGATOR, + buildGrantPermissions, + buildRevokePermissions, + legacyWildcardRole, +} from "../../vips/vip-665/utils/commands"; +import { seedAggregator } from "../../vips/vip-665/utils/seed"; +import ACM_COMMANDS_AGGREGATOR_ABI from "./abi/ACMCommandsAggregator.json"; +import ACCESS_CONTROL_MANAGER_ABI from "./abi/AccessControlManager.json"; +import COMPTROLLER_ABI from "./abi/Comptroller.json"; + +const { bscmainnet } = NETWORK_ADDRESSES; +// Recent BSC block where the ACMCommandsAggregator batch lengths match GRANT_INDEX / REVOKE_INDEX. +const FORK_BLOCK = 109924798; + +// A long-listed core-pool market, used to exercise the swapped _setForcedLiquidation permission. +const vUSDT = "0xfD5840Cd36d94D7229439859C0112a4185BC0255"; + +const role = (at: string, fn: string) => ethers.utils.solidityKeccak256(["address", "string"], [at, fn]); + +// Critical maintenance rows, bucketed per action so each category is asserted explicitly and guarded +// non-empty (a data regression that empties a category fails loudly instead of passing vacuously). +const REVOKE = BNB_ACTIONS.filter(row => row.action === "revoke"); +const SWAP = BNB_ACTIONS.filter(row => row.action === "swap"); +const GRANT = BNB_ACTIONS.filter(row => row.action === "grant"); +// Cleanup: redundant (wildcard-shadowed) target-specific grants revoked as behavior-preserving cleanup. +const REDUNDANT = REDUNDANT_REVOKES.bscmainnet; + +forking(FORK_BLOCK, async () => { + const acm = () => new Contract(BNB_ACM, ACCESS_CONTROL_MANAGER_ABI, ethers.provider); + const holds = (who: string, row: { target: string; signature: string }) => + acm().hasRole(role(row.target, row.signature), who); + const guardianAddr = (row: { grantTo?: "guardian1" | "guardian2" | "guardian3" }) => + BNB_GUARDIANS[row.grantTo ?? "guardian1"]; + + before(async () => { + const signer = await initMainnetUser(bscmainnet.NORMAL_TIMELOCK, ethers.utils.parseEther("2")); + await seedAggregator( + signer, + AGGREGATOR.bscmainnet, + buildGrantPermissions("bscmainnet"), + buildRevokePermissions("bscmainnet"), + ); + }); + + // ================================================================================================== + // Critical maintenance — reduce CriticalTimelock privileges (revoke / swap / grant + legacy wildcard). + // ================================================================================================== + describe("VIP-665 Critical maintenance — pre-execution (bscmainnet)", () => { + it("covers the expected revoke / swap / grant counts", () => { + expect(REVOKE.length, "revoke rows").to.equal(26); + expect(SWAP.length, "swap rows").to.equal(4); + expect(GRANT.length, "grant rows").to.equal(1); + expect(BNB_ACTION_LEGACY_WILDCARD_REVOKES.length, "critical legacy wildcard revokes").to.equal(1); + }); + + it("revoke: Critical currently holds every permission to be revoked", async () => { + for (const row of REVOKE) + expect(await holds(BNB_CRITICAL, row), `pre critical ${row.signature}@${row.target}`).to.be.true; + }); + + it("swap: Critical holds each one and the target Guardian does not yet", async () => { + for (const row of SWAP) { + expect(await holds(BNB_CRITICAL, row), `pre critical ${row.signature}@${row.target}`).to.be.true; + expect(await holds(guardianAddr(row), row), `pre guardian ${row.signature}@${row.target}`).to.be.false; + } + }); + + it("grant: Critical holds each one and the target Guardian does not yet", async () => { + for (const row of GRANT) { + expect(await holds(BNB_CRITICAL, row), `pre critical ${row.signature}@${row.target}`).to.be.true; + expect(await holds(guardianAddr(row), row), `pre guardian ${row.signature}@${row.target}`).to.be.false; + } + }); + + it("legacy wildcard: Critical currently holds the 32-byte wildcard role", async () => { + for (const r of BNB_ACTION_LEGACY_WILDCARD_REVOKES) + expect( + await acm().hasRole(legacyWildcardRole(r.signature), r.account), + `pre legacy wildcard ${r.signature} ${r.account}`, + ).to.be.true; + }); + }); + + // ================================================================================================== + // Cleanup — ACM maintenance: stale/dangling grants, redundant wildcard-shadowed grants, and the retired + // risk-steward legacy-wildcard cap powers. (BNB has no syncCash normalization — that is remotes only.) + // ================================================================================================== + describe("VIP-665 Cleanup — pre-execution (bscmainnet)", () => { + describe("stale (dangling grants)", () => { + it("covers the expected count", () => expect(STALE_ROWS.length, "stale rows").to.equal(8)); + + it("every listed grantee currently holds the dangling permission", async () => { + for (const staleRow of STALE_ROWS) + for (const who of staleRow.revokeFrom) + expect(await holds(who, staleRow), `pre stale ${staleRow.signature}@${staleRow.target} ${who}`).to.be.true; + }); + }); + + describe("redundant (wildcard-shadowed grants)", () => { + it("covers the expected count", () => expect(REDUNDANT.length, "redundant rows").to.equal(15)); + + it("each target-specific grant is currently held by its account", async () => { + for (const r of REDUNDANT) + expect( + await acm().hasRole(role(r.contract, r.signature), r.account), + `pre redundant ${r.signature}@${r.contract} ${r.account}`, + ).to.be.true; + }); + }); + + describe("legacy wildcard (retired risk-steward caps)", () => { + it("covers the expected count", () => + expect(CLEANUP_LEGACY_WILDCARD_REVOKES.length, "cleanup legacy wildcard revokes").to.equal(2)); + + it("each grantee currently holds the 32-byte wildcard role", async () => { + for (const r of CLEANUP_LEGACY_WILDCARD_REVOKES) + expect( + await acm().hasRole(legacyWildcardRole(r.signature), r.account), + `pre legacy wildcard ${r.signature} ${r.account}`, + ).to.be.true; + }); + }); + }); + + const events = EXPECTED_ROLE_EVENTS.bscmainnet; + testVip("VIP-665 Reduce CriticalTimelock privileges — BNB Chain", await vip665(), { + callbackAfterExecution: async (txResponse: TransactionResponse) => { + await expectEvents( + txResponse, + [ACCESS_CONTROL_MANAGER_ABI], + ["RoleGranted", "RoleRevoked"], + [events.granted, events.revoked], + ); + await expectEvents( + txResponse, + [ACM_COMMANDS_AGGREGATOR_ABI], + ["GrantPermissionsExecuted", "RevokePermissionsExecuted"], + [1, 1], + ); + }, + }); + + // ================================================================================================== + // Critical maintenance — post-execution. + // ================================================================================================== + describe("VIP-665 Critical maintenance — post-execution (bscmainnet)", () => { + it("revoke: the CriticalTimelock lost each target-specific grant", async () => { + for (const row of REVOKE) + expect(await holds(BNB_CRITICAL, row), `post critical ${row.signature}@${row.target}`).to.be.false; + }); + + it("revoke behavioral: the CriticalTimelock can no longer call any revoked function", async () => { + for (const row of REVOKE) + expect( + await acm().isAllowedToCall(BNB_CRITICAL, row.signature, { from: row.target }), + `crit still allowed ${row.signature}@${row.target}`, + ).to.be.false; + }); + + it("swap: Critical lost each one and the target Guardian gained it", async () => { + for (const row of SWAP) { + expect(await holds(BNB_CRITICAL, row), `post critical ${row.signature}@${row.target}`).to.be.false; + expect(await holds(guardianAddr(row), row), `post guardian ${row.signature}@${row.target}`).to.be.true; + } + }); + + it("grant: the target Guardian gained each one while Critical kept it", async () => { + for (const row of GRANT) { + expect(await holds(BNB_CRITICAL, row), `post critical ${row.signature}@${row.target}`).to.be.true; + expect(await holds(guardianAddr(row), row), `post guardian ${row.signature}@${row.target}`).to.be.true; + } + }); + + it("legacy wildcard: Critical lost the 32-byte wildcard role", async () => { + for (const r of BNB_ACTION_LEGACY_WILDCARD_REVOKES) + expect( + await acm().hasRole(legacyWildcardRole(r.signature), r.account), + `post legacy wildcard ${r.signature} ${r.account}`, + ).to.be.false; + }); + + it("swap behavioral: every swapped function is allowed for the target Guardian and denied for Critical", async () => { + for (const row of SWAP.filter(swapRow => swapRow.target !== ZERO)) { + expect( + await acm().isAllowedToCall(BNB_CRITICAL, row.signature, { from: row.target }), + `crit !allowed ${row.signature}`, + ).to.be.false; + expect( + await acm().isAllowedToCall(guardianAddr(row), row.signature, { from: row.target }), + `guardian allowed ${row.signature}`, + ).to.be.true; + } + }); + + it("Guardian 1 can call Unitroller._setForcedLiquidation; the CriticalTimelock can no longer", async () => { + const guardianSigner = await initMainnetUser(BNB_GUARDIANS.guardian1, ethers.utils.parseEther("1")); + const criticalSigner = await initMainnetUser(bscmainnet.CRITICAL_TIMELOCK, ethers.utils.parseEther("1")); + const asGuardian = new Contract(bscmainnet.UNITROLLER, COMPTROLLER_ABI, guardianSigner); + const asCritical = new Contract(bscmainnet.UNITROLLER, COMPTROLLER_ABI, criticalSigner); + + await expect(asGuardian._setForcedLiquidation(vUSDT, true)).to.not.be.reverted; + await expect(asCritical._setForcedLiquidation(vUSDT, true)).to.be.reverted; + }); + }); + + // ================================================================================================== + // Cleanup — post-execution. + // ================================================================================================== + describe("VIP-665 Cleanup — post-execution (bscmainnet)", () => { + describe("stale (dangling grants)", () => { + it("every listed grantee lost the dangling permission", async () => { + for (const staleRow of STALE_ROWS) + for (const who of staleRow.revokeFrom) + expect(await holds(who, staleRow), `post stale ${staleRow.signature}@${staleRow.target} ${who}`).to.be + .false; + }); + }); + + describe("redundant (wildcard-shadowed grants)", () => { + it("each target-specific grant was revoked", async () => { + for (const r of REDUNDANT) + expect( + await acm().hasRole(role(r.contract, r.signature), r.account), + `post redundant ${r.signature}@${r.contract} ${r.account}`, + ).to.be.false; + }); + + it("behavioral: each account can still call its function via the surviving wildcard", async () => { + for (const r of REDUNDANT) { + // setCollateralFactor from Critical is the single case whose wildcard is also revoked, so its power + // is fully removed (asserted in the critical revoke test). Every other redundant revoke preserves it. + if (r.signature === "setCollateralFactor(address,uint256,uint256)" && r.account === BNB_CRITICAL) continue; + expect( + await acm().isAllowedToCall(r.account, r.signature, { from: r.contract }), + `still callable ${r.signature}@${r.contract} ${r.account}`, + ).to.be.true; + } + }); + }); + + describe("legacy wildcard (retired risk-steward caps)", () => { + it("each grantee lost the 32-byte wildcard role", async () => { + for (const r of CLEANUP_LEGACY_WILDCARD_REVOKES) + expect( + await acm().hasRole(legacyWildcardRole(r.signature), r.account), + `post legacy wildcard ${r.signature} ${r.account}`, + ).to.be.false; + }); + }); + }); +}); diff --git a/simulations/vip-665/ethereum.ts b/simulations/vip-665/ethereum.ts new file mode 100644 index 000000000..67e84075c --- /dev/null +++ b/simulations/vip-665/ethereum.ts @@ -0,0 +1,4 @@ +import { runRemoteSim } from "./utils/remoteSim"; + +// Recent ethereum block where the CriticalTimelock still holds the permissions this VIP revokes. +runRemoteSim("ethereum", 25530333); diff --git a/simulations/vip-665/opbnbmainnet.ts b/simulations/vip-665/opbnbmainnet.ts new file mode 100644 index 000000000..466858458 --- /dev/null +++ b/simulations/vip-665/opbnbmainnet.ts @@ -0,0 +1,4 @@ +import { runRemoteSim } from "./utils/remoteSim"; + +// Recent opbnbmainnet block where the CriticalTimelock still holds the permissions this VIP revokes. +runRemoteSim("opbnbmainnet", 163634294); diff --git a/simulations/vip-665/opmainnet.ts b/simulations/vip-665/opmainnet.ts new file mode 100644 index 000000000..c1399bf60 --- /dev/null +++ b/simulations/vip-665/opmainnet.ts @@ -0,0 +1,4 @@ +import { runRemoteSim } from "./utils/remoteSim"; + +// Recent opmainnet block where the CriticalTimelock still holds the permissions this VIP revokes. +runRemoteSim("opmainnet", 154213361); diff --git a/simulations/vip-665/unichainmainnet.ts b/simulations/vip-665/unichainmainnet.ts new file mode 100644 index 000000000..72a562377 --- /dev/null +++ b/simulations/vip-665/unichainmainnet.ts @@ -0,0 +1,4 @@ +import { runRemoteSim } from "./utils/remoteSim"; + +// Recent unichainmainnet block where the CriticalTimelock still holds the permissions this VIP revokes. +runRemoteSim("unichainmainnet", 53277143); diff --git a/simulations/vip-665/utils/remoteSim.ts b/simulations/vip-665/utils/remoteSim.ts new file mode 100644 index 000000000..d7655c47a --- /dev/null +++ b/simulations/vip-665/utils/remoteSim.ts @@ -0,0 +1,159 @@ +import { TransactionResponse } from "@ethersproject/abstract-provider"; +import { expect } from "chai"; +import { Contract } from "ethers"; +import { ethers } from "hardhat"; +import { NETWORK_ADDRESSES } from "src/networkAddresses"; +import { expectEvents, initMainnetUser } from "src/utils"; +import { forking, testForkedNetworkVipCommands } from "src/vip-framework"; + +import vip665, { EXPECTED_ROLE_EVENTS } from "../../../vips/vip-665/bscmainnet"; +import { REMOTE_ACM, SYNC_CASH_MARKETS, ZERO } from "../../../vips/vip-665/data/addresses"; +import { REDUNDANT_REVOKES, SYNC_CASH_SIG } from "../../../vips/vip-665/data/cleanup"; +import { remoteRowsFor } from "../../../vips/vip-665/data/criticalChanges"; +import { AGGREGATOR, Chain, buildGrantPermissions, buildRevokePermissions } from "../../../vips/vip-665/utils/commands"; +import { seedAggregator } from "../../../vips/vip-665/utils/seed"; +import ACCESS_CONTROL_MANAGER_ABI from "../abi/AccessControlManager.json"; + +type RemoteChain = Exclude; + +const role = (at: string, fn: string) => ethers.utils.solidityKeccak256(["address", "string"], [at, fn]); + +// RoleGranted / RoleRevoked are emitted by every ACM version. On remotes the grants are the wrapper +// grantRole(DEFAULT_ADMIN_ROLE) plus the single wildcard syncCash() grant; RoleRevoked = the Critical +// revokes + the per-market syncCash() revokes + the wrapper revokeRole. Expected counts come from the +// hardcoded EXPECTED_ROLE_EVENTS oracle. + +// Shared body for the 7 remote-chain simulations. Run each with its matching --fork. +export const runRemoteSim = (chain: RemoteChain, forkBlock: number) => { + const critical = NETWORK_ADDRESSES[chain].CRITICAL_TIMELOCK; + const normal = NETWORK_ADDRESSES[chain].NORMAL_TIMELOCK; + const syncCashMarkets = SYNC_CASH_MARKETS[chain]; + + forking(forkBlock, async () => { + before(async () => { + const signer = await initMainnetUser(NETWORK_ADDRESSES[chain].NORMAL_TIMELOCK, ethers.utils.parseEther("2")); + await seedAggregator(signer, AGGREGATOR[chain], buildGrantPermissions(chain), buildRevokePermissions(chain)); + }); + + const acm = () => new Contract(REMOTE_ACM[chain], ACCESS_CONTROL_MANAGER_ABI, ethers.provider); + const rows = remoteRowsFor(chain); + const redundant = REDUNDANT_REVOKES[chain]; + + // ================================================================================================ + // Critical maintenance — reduce CriticalTimelock privileges (remotes revoke only, no grants/swaps). + // ================================================================================================ + describe(`VIP-665 Critical maintenance — pre-execution (${chain})`, () => { + it("the plan has at least one revoke row for this chain", () => { + expect(rows.length, `revoke rows for ${chain}`).to.be.greaterThan(0); + }); + + it("revoke: Critical currently holds every permission to be revoked", async () => { + for (const row of rows) + expect( + await acm().hasRole(role(row.target, row.signature), critical), + `pre critical ${row.signature}@${row.target}`, + ).to.be.true; + }); + }); + + // ================================================================================================ + // Cleanup — ACM maintenance: syncCash() wildcard normalization and redundant wildcard-shadowed grants. + // (No stale/dangling grants on remotes — that is BNB only.) + // ================================================================================================ + describe(`VIP-665 Cleanup — pre-execution (${chain})`, () => { + describe("syncCash (wildcard normalization)", () => { + it("NormalTimelock holds every per-market grant and no wildcard grant yet", async () => { + expect(syncCashMarkets.length, `syncCash markets for ${chain}`).to.be.greaterThan(0); + expect(await acm().hasRole(role(ZERO, SYNC_CASH_SIG), normal), "pre wildcard syncCash").to.be.false; + for (const market of syncCashMarkets) + expect(await acm().hasRole(role(market, SYNC_CASH_SIG), normal), `pre normal syncCash@${market}`).to.be + .true; + }); + }); + + describe("redundant (wildcard-shadowed grants)", () => { + it("each target-specific grant is currently held by its account", async () => { + for (const r of redundant) + expect( + await acm().hasRole(role(r.contract, r.signature), r.account), + `pre redundant ${r.signature}@${r.contract} ${r.account}`, + ).to.be.true; + }); + }); + }); + + const events = EXPECTED_ROLE_EVENTS[chain]; + testForkedNetworkVipCommands(`VIP-665 ${chain}`, await vip665(), { + callbackAfterExecution: async (txResponse: TransactionResponse) => { + await expectEvents( + txResponse, + [ACCESS_CONTROL_MANAGER_ABI], + ["RoleGranted", "RoleRevoked"], + [events.granted, events.revoked], + ); + }, + }); + + // ================================================================================================ + // Critical maintenance — post-execution. + // ================================================================================================ + describe(`VIP-665 Critical maintenance — post-execution (${chain})`, () => { + it("revoke: Critical lost every revoked permission", async () => { + for (const row of rows) + expect( + await acm().hasRole(role(row.target, row.signature), critical), + `post critical ${row.signature}@${row.target}`, + ).to.be.false; + }); + + it("revoke behavioral: Critical is no longer allowed to call any revoked function", async () => { + for (const row of rows) + if (row.target !== ZERO) + expect( + await acm().isAllowedToCall(critical, row.signature, { from: row.target }), + `crit !allowed ${row.signature}`, + ).to.be.false; + }); + }); + + // ================================================================================================ + // Cleanup — post-execution. + // ================================================================================================ + describe(`VIP-665 Cleanup — post-execution (${chain})`, () => { + describe("syncCash (wildcard normalization)", () => { + it("NormalTimelock gained the wildcard grant and lost every per-market grant", async () => { + expect(await acm().hasRole(role(ZERO, SYNC_CASH_SIG), normal), "post wildcard syncCash").to.be.true; + for (const market of syncCashMarkets) + expect(await acm().hasRole(role(market, SYNC_CASH_SIG), normal), `post normal syncCash@${market}`).to.be + .false; + }); + + it("behavioral: NormalTimelock is still allowed to call syncCash on every market via wildcard", async () => { + for (const market of syncCashMarkets) + expect( + await acm().isAllowedToCall(normal, SYNC_CASH_SIG, { from: market }), + `normal allowed syncCash@${market}`, + ).to.be.true; + }); + }); + + describe("redundant (wildcard-shadowed grants)", () => { + it("each target-specific grant was revoked", async () => { + for (const r of redundant) + expect( + await acm().hasRole(role(r.contract, r.signature), r.account), + `post redundant ${r.signature}@${r.contract} ${r.account}`, + ).to.be.false; + }); + + it("behavioral: each account can still call the function via the surviving wildcard", async () => { + for (const r of redundant) + expect( + await acm().isAllowedToCall(r.account, r.signature, { from: r.contract }), + `post callable ${r.signature}@${r.contract} ${r.account}`, + ).to.be.true; + }); + }); + }); + }); +}; diff --git a/simulations/vip-665/zksyncmainnet.ts b/simulations/vip-665/zksyncmainnet.ts new file mode 100644 index 000000000..c33d4f995 --- /dev/null +++ b/simulations/vip-665/zksyncmainnet.ts @@ -0,0 +1,4 @@ +import { runRemoteSim } from "./utils/remoteSim"; + +// Recent zksyncmainnet block where the CriticalTimelock still holds the permissions this VIP revokes. +runRemoteSim("zksyncmainnet", 71165006); diff --git a/vips/vip-665/bscmainnet.ts b/vips/vip-665/bscmainnet.ts new file mode 100644 index 000000000..d5af98e90 --- /dev/null +++ b/vips/vip-665/bscmainnet.ts @@ -0,0 +1,108 @@ +import { Command, ProposalType } from "src/types"; +import { makeProposal } from "src/utils"; + +import { LZ_CHAIN_ID, RemoteChain } from "./data/criticalChanges"; +import { + AGGREGATOR, + Chain, + DEFAULT_ADMIN_ROLE, + GRANT_INDEX, + REVOKE_INDEX, + acmOf, + buildGrantPermissions, + buildRevokePermissions, + legacyWildcardCommands, +} from "./utils/commands"; + +// Expected RoleGranted / RoleRevoked counts per chain, asserted by the simulations after execution. +// granted = aggregator grants + wrapper grantRole(DEFAULT_ADMIN). revoked = aggregator revokes + wrapper +// revokeRole(DEFAULT_ADMIN) + redundant cleanup + (BNB only) the 3 direct legacy-wildcard revokeRole calls. +export const EXPECTED_ROLE_EVENTS: Record<"bscmainnet" | RemoteChain, { granted: number; revoked: number }> = { + bscmainnet: { granted: 6, revoked: 71 }, + ethereum: { granted: 2, revoked: 53 }, + arbitrumone: { granted: 2, revoked: 25 }, + basemainnet: { granted: 2, revoked: 20 }, + zksyncmainnet: { granted: 2, revoked: 22 }, + opmainnet: { granted: 2, revoked: 18 }, + unichainmainnet: { granted: 2, revoked: 18 }, + opbnbmainnet: { granted: 2, revoked: 12 }, +}; + +// The commands the VIP emits per chain: grant the aggregator DEFAULT_ADMIN_ROLE, execute the grant and/or +// revoke batch, revoke the role. Local on BNB; wrapped with dstChainId (LayerZero) on remotes. +const aggregatorCommands = (chain: Chain): Command[] => { + const acm = acmOf(chain); + const agg = AGGREGATOR[chain]; + const route = chain === "bscmainnet" ? {} : { dstChainId: LZ_CHAIN_ID[chain as RemoteChain] }; + const commands: Command[] = [ + { target: acm, signature: "grantRole(bytes32,address)", params: [DEFAULT_ADMIN_ROLE, agg], ...route }, + ]; + if (buildGrantPermissions(chain).length > 0) + commands.push({ + target: agg, + signature: "executeGrantPermissions(uint256)", + params: [GRANT_INDEX[chain]], + ...route, + }); + if (buildRevokePermissions(chain).length > 0) + commands.push({ + target: agg, + signature: "executeRevokePermissions(uint256)", + params: [REVOKE_INDEX[chain]], + ...route, + }); + commands.push({ target: acm, signature: "revokeRole(bytes32,address)", params: [DEFAULT_ADMIN_ROLE, agg], ...route }); + return commands; +}; + +const meta = { + version: "v2", + title: "VIP-665 [Multi-Chain] Reduce CriticalTimelock privileges", + description: `#### Summary + +This proposal hardens governance by removing high-severity powers from the CriticalTimelock on every mainnet, and — for a small set of BNB Chain functions where a Guardian multisig does not already hold them — moving them to the Guardian. It also cleans up dangling permissions left on removed setters and retired contracts, and normalizes the \`syncCash()\` permission on every remote chain to the wildcard convention used by all other vToken setters. + +#### Description + +A proposal routed through the Critical route executes in roughly 7 hours on BNB Chain and 8 hours on remote chains once voting, queue and timelock delays are counted. To shrink that fast-path attack surface, this VIP takes the high-severity powers off the CriticalTimelock across BNB Chain, Ethereum, Arbitrum One, Base, zkSync Era, OP Mainnet, Unichain and opBNB. The full per-contract, per-chain list of permissions changed is in the accompanying community post and the pull request. + +Actions per chain: revoke the listed permissions from the CriticalTimelock; on BNB Chain, grant a few functions to Guardian 1 (swap) and grant one to Guardian 1 while keeping Critical (add), and revoke dangling grants on removed setters and retired contracts from every current holder; on each remote chain, grant \`syncCash()\` on the wildcard target (address(0)) to the NormalTimelock and revoke the per-market \`syncCash()\` grants it currently holds. + +#### Execution model + +To keep the whole change in one proposal within the BNB Chain per-transaction gas limit, every chain is executed through its pre-seeded ACMCommandsAggregator. For each chain the proposal grants the aggregator the ACM DEFAULT_ADMIN_ROLE, calls executeGrantPermissions and/or executeRevokePermissions by batch index, and revokes the role — all in the same proposal. Each batch contains only the giveCallPermission / revokeCallPermission calls for that chain. + +#### Security and additional considerations + +- **Scoped privilege**: the aggregator's DEFAULT_ADMIN_ROLE grant is scoped to this proposal — granted, used once, and revoked in the same transaction sequence, per chain. +- **VIP execution simulation**: validated in a fork environment that every listed permission moved as intended (revoke / swap / grant), that "No change" permissions are untouched, and that the expected ACM events fire. +- **Cross-chain payload size**: each LayerZero message carries only 4 commands (grant the aggregator the ACM role, execute the grant batch, execute the revoke batch, revoke the role), well under the Relayer cap. + +#### Voting options + +- **For** — Execute the proposal +- **Against** — Do not execute the proposal +- **Abstain** — Indifferent to execution`, + 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", +}; + +export const vip665 = () => + makeProposal( + [ + ...aggregatorCommands("bscmainnet"), + ...legacyWildcardCommands(), + ...aggregatorCommands("ethereum"), + ...aggregatorCommands("arbitrumone"), + ...aggregatorCommands("basemainnet"), + ...aggregatorCommands("zksyncmainnet"), + ...aggregatorCommands("opmainnet"), + ...aggregatorCommands("unichainmainnet"), + ...aggregatorCommands("opbnbmainnet"), + ], + meta, + ProposalType.REGULAR, + ); + +export default vip665; diff --git a/vips/vip-665/data/addresses.ts b/vips/vip-665/data/addresses.ts new file mode 100644 index 000000000..769f507c4 --- /dev/null +++ b/vips/vip-665/data/addresses.ts @@ -0,0 +1,360 @@ +// All raw on-chain addresses used by VIP-665, organized as one block per chain. Within each block the +// entries are ordered `common` (categories every chain shares, so the shape cross-references the doc's +// common section) then `unique` (chain-specific values). criticalChanges.ts, cleanup.ts, the VIP and the +// simulations reference these named constants so the plan files stay free of literals. The per-chain +// blocks are the review surface; the `Record` exports at the bottom are just reassembled from +// them so consumers can keep indexing by chain key. +import { NETWORK_ADDRESSES } from "src/networkAddresses"; + +import type { RedundantChain } from "./cleanup"; +import type { RemoteChain } from "./criticalChanges"; + +const { bscmainnet } = NETWORK_ADDRESSES; + +export const ZERO = "0x0000000000000000000000000000000000000000"; + +// =================================================================================================== +// bscmainnet (governance hub) +// =================================================================================================== +// -- common: ACM + Critical timelock + Guardians -- +export const BNB_ACM = bscmainnet.ACCESS_CONTROL_MANAGER; +export const BNB_CRITICAL = bscmainnet.CRITICAL_TIMELOCK; +// Guardian 1 = CRITICAL_GUARDIAN (swap/grant target), 2 = GUARDIAN (pause), 3 = oracles. +export const BNB_GUARDIANS = { + guardian1: bscmainnet.CRITICAL_GUARDIAN, + guardian2: bscmainnet.GUARDIAN, + guardian3: "0x3a3284dC0FaFfb0b5F0d074c4C704D14326C98cF", +}; + +// -- unique: contracts this VIP touches on BNB -- +// Contracts the action plan is scoped to. +export const BNB_CONTRACTS = { + Liquidator: "0x0870793286aaDA55D39CE7f82fb2766e8004cF43", + PegStability_USDT: "0xC138aa4E424D1A8539e8F38Af5a754a2B7c3Cc36", + Prime: "0xBbCD063efE506c3D42a0Fa2dB5C08430288C71FC", + PrimeLiquidityProvider: "0x23c4F844ffDdC6161174eB32c770D4D8C07833F2", + PrimeV2: "0x059EabA8676b03e4e8f009eFb7F587C28450F50f", + Unitroller: "0xfD36E2c2a6789Db23113685031d7F16329158384", + VaiUnitroller: "0x004065D34C6b18cE4370ced1CeBDE94865DbFAFE", + VAIVaultProxy: "0x0667Eed0a0aAb930af74a3dfeDD263A73994f216", + VBNBAdmin: "0x9A7890534d9d91d473F28cB97962d176e2B65f1d", + VRTVaultProxy: "0x98bF4786D72AAEF6c714425126Dd92f149e3F334", + XVSVaultProxy: "0x051100480289e704d20e9DB4804837068f3f9204", + BinanceOracle: "0x594810b741d136f1960141C0d8Fb4a91bE78A820", + ChainlinkOracle: "0x1B2103441A0A108daD8848D8F5d790e4D402921F", + DeviationBoundedOracle: "0xc79Cb7efEBd121DC4B39eA141C214606595D665A", + PythOracle: "0xb893E38162f55fb80B18Aa44da76FaDf8E9B2262", + RedStoneOracle: "0x8455EFA4D7Ff63b8BFD96AdD889483Ea7d39B70a", + ResilientOracle: "0x6592b5DE802159F3E74B2486b091D11a8256ab8A", + USDTChainlinkOracle: "0x22Dc2BAEa32E95AB07C2F5B8F63336CbF61aB6b8", + BTCBPrimeConverter: "0xE8CeAa79f082768f99266dFd208d665d2Dd18f53", + ConverterNetwork: "0xF7Caad5CeB0209165f2dFE71c92aDe14d0F15995", + ETHPrimeConverter: "0xca430B8A97Ea918fF634162acb0b731445B8195E", + ProtocolShareReserve: "0xCa01D5A9A248a830E9D93231e791B1afFed7c446", + RiskFundConverter: "0xA5622D276CcbB8d9BBE3D1ffd1BB11a0032E53F0", + RiskFundV2: "0xdF31a28D68A2AB381D42b380649Ead7ae2A76E42", + USDCPrimeConverter: "0xa758c9C215B6c4198F0a0e3FA46395Fa15Db691b", + USDTPrimeConverter: "0xD9f101AA67F3D72662609a2703387242452078C3", + WBNBBurnConverter: "0x9eF79830e626C8ccA7e46DCEd1F90e51E7cFCeBE", + XVSVaultConverter: "0xd5b9AE835F4C59272032B3B954417179573331E0", + XVSVaultTreasury: "0x269ff7818DB317f60E386D2be0B259e1a324a40a", + Comptroller_DeFi: "0x3344417c9360b963ca93A4e8305361AEde340Ab9", + Comptroller_GameFi: "0x1b43ea8622e76627B81665B1eCeBB4867566B963", + Comptroller_LiquidStakedBNB: "0xd933909A4a2b7A4638903028f44D1d38ce27c352", + Comptroller_Stablecoins: "0x94c1495cD4c557f1560Cbd68EAB0d197e6291571", + Comptroller_Tron: "0x23b4404E4E5eC5FF5a6FFb70B7d14E3FabF237B0", + Shortfall: "0xf37530A8a810Fcb501AA0Ecd0B0699388F0F2209", + DeviationSentinel: "0x6599C15cc8407046CD91E5c0F8B7f765fF914870", + EBrake: "0x35eBaBB99c7Fb7ba0C90bCc26e5d55Cdf89C23Ec", + Executor: "0xDd541A1b065F9587b01815a390a4d4559D7b630F", + PancakeSwapOracle: "0x44B72078240A3509979faF450085Fa818401D32E", + PendlePTVaultAdapter: "0x60Db419d8ea13C5827072Cf693D13cA1Ec6E0B4a", + RelativePositionManager: "0x1525D804DFff218DcC8B9359940F423209356C42", + SentinelOracle: "0x58eae0Cf4215590E19860b66b146C5d539cb6f14", + UniswapOracle: "0x8FD05458faf220B2324c4BFbb29DBC4B3CF6f23f", + AuxiliaryCommandsAggregator: "0x528A428748dfE73DFcc844176B401475D1831057", + OmnichainProposalSender: "0x36a69dE601381be7b0DcAc5D5dD058825505F8f6", + RiskOracle: "0x0E3E51958b0Daa8C57c949675975CBEDd7b5a1a1", + RiskStewardReceiver: "0x47856bFa74B71d24a5545c7506862B8FddE52baB", + XVSBridgeAdmin: "0x70d644877b7b73800E9073BCFCE981eAaB6Dbc21", + InstitutionalVaultControllerProxy: "0x6D9e91cB766259af42619c14c994E694E57e6E85", + LiquidationAdapterProxy: "0x17A6222fB8b4b6D852cA54f5bc376a6A2c6224Bd", +}; + +// Retired / legacy contracts (absent from every deployment file) still holding grants. +export const RETIRED = { + RiskFundV2: "0xdF31a28D68A2AB381D42b380649Ead7ae2A76E42", + Unitroller: "0xfD36E2c2a6789Db23113685031d7F16329158384", + RiskConfig: "0xBa2a43279a228cf9cD94d072777d8d98e7e0a229", + RiskSteward: "0xE7252dccd79F2A555E314B9cdd440745b697D562", + TwapOracle: "0xea2f042e1A4f057EF8A5220e57733AD747ea8867", +}; + +// Redundant-grant cleanup contracts on BNB (shadowed by an identical wildcard grant). +const BNB_REDUNDANT: Record = { + EBrake: "0x35eBaBB99c7Fb7ba0C90bCc26e5d55Cdf89C23Ec", + Prime: "0xBbCD063efE506c3D42a0Fa2dB5C08430288C71FC", + PrimeLeaderboard: "0x55e2ccF68B7A276dc28AfA107997b8B1Be932c0b", + PrimeLiquidityProvider: "0x23c4F844ffDdC6161174eB32c770D4D8C07833F2", + PrimeV2: "0x059EabA8676b03e4e8f009eFb7F587C28450F50f", + Unitroller: "0xfD36E2c2a6789Db23113685031d7F16329158384", + VBNBAdmin: "0x9A7890534d9d91d473F28cB97962d176e2B65f1d", + VenusERC4626Factory: "0xC2f7924809830886EB04c6b40725Fd68F1891fA2", + vUNI: "0x27FF564707786720C71A2e5c1490A63266683612", +}; + +// =================================================================================================== +// ethereum +// =================================================================================================== +// -- common: ACM + XVS bridge admin + omnichain executor owner -- +const ETH_ACM = NETWORK_ADDRESSES.ethereum.ACCESS_CONTROL_MANAGER; +const ETH_XVS_BRIDGE_ADMIN = "0x9C6C95632A8FB3A74f2fB4B7FfC50B003c992b96"; +const ETH_OMNICHAIN_EXECUTOR_OWNER = "0x87Ed3Fd3a25d157637b955991fb1B41B566916Ba"; +const ETH_REDUNDANT: Record = { + EBrake: "0xCD09042c5DFFed762998Df9a058ec5944e39949B", + Prime: "0x14C4525f47A7f7C984474979c57a2Dccb8EACB39", + PrimeLiquidityProvider: "0x8ba6aFfd0e7Bcd0028D1639225C84DdCf53D8872", + VenusERC4626Factory: "0x39cb747453Be3416E659dAeA169540b6F000c885", +}; +// -- unique: SentinelOracle (hardcoded proxy, verified on-chain) + syncCash() markets -- +const ETH_SENTINEL_ORACLE = "0x444C53E194B40c272fAd683210e2cB1c16Ab132e"; +const ETH_SYNC_CASH_MARKETS = [ + "0x0792b9c60C728C1D2Fd6665b3D7A08762a9b28e0", + "0x0Ec5488e4F8f319213a14cab188E01fB8517Faa8", + "0x0c6B19287999f1e31a5c0a44393b24B62D2C0468", + "0x13eB80FDBe5C5f4a7039728E258A6f05fb3B912b", + "0x17142a05fe678e9584FA1d88EfAC1bF181bF7ABe", + "0x17C07e0c232f2f80DfDbd7a95b942D893A4C5ACb", + "0x256AdDBe0a387c98f487e44b85c29eb983413c5e", + "0x25C20e6e110A1cE3FEbaCC8b7E48368c7b2F0C91", + "0x2d499800239C4CD3012473Cb1EAE33562F0A6933", + "0x30aD10Bd5Be62CAb37863C2BfcC6E8fb4fD85BDa", + "0x325cEB02fe1C2fF816A83a5770eA0E88e2faEcF2", + "0x475d0C68a8CD275c15D1F01F4f291804E445F677", + "0x4a240F0ee138697726C8a3E43eFE6Ac3593432CB", + "0x4fAfbDc4F2a9876Bd1764827b26fb8dc4FD1dB95", + "0x520d67226Bc904aC122dcE66ed2f8f61AA1ED764", + "0x5e35C312862d53FD566737892aDCf010cb4928F7", + "0x62D9E2010Cff87Bae05B91d5E04605ef864ABc3B", + "0x672208C10aaAA2F9A6719F449C4C8227bc0BC202", + "0x76697f8eaeA4bE01C678376aAb97498Ee8f80D5C", + "0x7c8ff7d2A1372433726f879BD945fFb250B94c65", + "0x8716554364f20BCA783cb2BAA744d39361fd1D8d", + "0x8C3e3821259B82fFb32B2450A95d2dcbf161C24E", + "0xA854D35664c658280fFf27B6eDC6C4195c3229B3", + "0xCca202a95E8096315E3F19E46e19E1b326634889", + "0xDB6C345f864883a8F4cae87852Ac342589E76D1B", + "0xE0ee5dDeBFe0abe0a4Af50299D68b74Cec31668e", + "0xE36Ae842DbbD7aE372ebA02C8239cd431cC063d6", + "0xEF26C64bC06A8dE4CA5D31f119835f9A1d9433b9", + "0xF9E9Fe17C00a8B96a8ac20c4E344C8688D7b947E", + "0xa0EE2bAA024cC3AA1BC9395522D07B7970Ca75b3", + "0xa836ce315b7A6Bb19397Ee996551659B1D92298e", + "0xa8e7f9473635a5CB79646f14356a9Fc394CA111A", + "0xb4933AF59868986316Ed37fa865C829Eba2df0C7", + "0xba3916302cBA4aBcB51a01e706fC6051AaF272A0", + "0xc42E4bfb996ED35235bda505430cBE404Eb49F77", + "0xc82780Db1257C788F262FBbDA960B3706Dfdcaf2", + "0xd8AdD9B41D4E1cd64Edad8722AB0bA8D35536657", + "0xf87c0a64dc3a8622D6c63265FA29137788163879", +]; + +// =================================================================================================== +// arbitrumone +// =================================================================================================== +// -- common -- +const ARB_ACM = NETWORK_ADDRESSES.arbitrumone.ACCESS_CONTROL_MANAGER; +const ARB_XVS_BRIDGE_ADMIN = "0xf5d81C6F7DAA3F97A6265C8441f92eFda22Ad784"; +const ARB_OMNICHAIN_EXECUTOR_OWNER = "0xf72C1Aa0A1227B4bCcB28E1B1015F0616E2db7fD"; +const ARB_REDUNDANT: Record = { + EBrake: "0xFc4CE7Ca9BB5119705Cfb84d6e4476e8a4032b26", + Prime: "0xFE69720424C954A2da05648a0FAC84f9bf11Ef49", + PrimeLiquidityProvider: "0x86bf21dB200f29F21253080942Be8af61046Ec29", + VenusERC4626Factory: "0xC1422B928cb6FC9BA52880892078578a93aa5Cc7", +}; +// -- unique -- +const ARB_SENTINEL_ORACLE = "0x3563CAbc541a0432C66A64942ffB4070a9726226"; +const ARB_SYNC_CASH_MARKETS = [ + "0x246a35E79a3a0618535A469aDaF5091cAA9f7E88", + "0x39D6d13Ea59548637104E40e729E4aABE27FE106", + "0x4f3a73f318C5EA67A86eaaCE24309F29f89900dF", + "0x68a34332983f4Bf866768DD6D6E638b02eF5e1f0", + "0x7D8609f8da70fF9027E9bc5229Af4F6727662707", + "0x9bb8cEc9C0d46F53b4f2173BB2A0221F66c353cC", + "0x9df6B5132135f14719696bBAe3C54BAb272fDb16", + "0xAeB0FEd69354f34831fe1D16475D9A83ddaCaDA6", + "0xB9F9117d4200dC296F9AcD1e8bE1937df834a2fD", + "0xaDa57840B372D4c28623E87FC175dE8490792811", +]; + +// =================================================================================================== +// basemainnet +// =================================================================================================== +// -- common -- +const BASE_ACM = NETWORK_ADDRESSES.basemainnet.ACCESS_CONTROL_MANAGER; +const BASE_XVS_BRIDGE_ADMIN = "0x6303FEcee7161bF959d65df4Afb9e1ba5701f78e"; +const BASE_OMNICHAIN_EXECUTOR_OWNER = "0x8BA591f72a90fb379b9a82087b190d51b226F0a9"; +const BASE_REDUNDANT: Record = { + EBrake: "0x062C68Af7B9Fb059DCB7FA4B6b92E633350fb7c2", + Prime: "0xD2e84244f1e9Fca03Ff024af35b8f9612D5d7a30", + PrimeLiquidityProvider: "0xcB293EB385dEFF2CdeDa4E7060974BB90ee0B208", + VenusERC4626Factory: "0x1A430825B31DdA074751D6731Ce7Dca38D012D13", +}; +// -- unique -- +const BASE_SENTINEL_ORACLE = "0xCdD6D79Fd313C21967CED04C1b8bE70BDc27574D"; +const BASE_SYNC_CASH_MARKETS = [ + "0x133d3BCD77158D125B75A17Cb517fFD4B4BE64C5", + "0x3cb752d175740043Ec463673094e06ACDa2F9a2e", + "0x75201D81B3B0b9D17b179118837Be37f64fc4930", + "0x7bBd1005bB24Ec84705b04e1f2DfcCad533b6D72", + "0xEB8A79bD44cF4500943bf94a2b4434c95C008599", +]; + +// =================================================================================================== +// zksyncmainnet +// =================================================================================================== +// -- common -- +const ZK_ACM = NETWORK_ADDRESSES.zksyncmainnet.ACCESS_CONTROL_MANAGER; +const ZK_XVS_BRIDGE_ADMIN = "0x2471043F05Cc41A6051dd6714DC967C7BfC8F902"; +const ZK_OMNICHAIN_EXECUTOR_OWNER = "0xdfaed3E5d9707629Ed5c225b4fB980c064286771"; +const ZK_REDUNDANT: Record = { + Prime: "0xdFe62Dcba3Ce0A827439390d7d45Af8baE599978", + PrimeLiquidityProvider: "0x0EDE6d7fB474614C5D3d5a16581628bb96CB5dff", + VenusERC4626Factory: "0xDC59Dd76Dd7A64d743C764a9aa8C96Ff2Ea8BAc3", +}; +// -- unique (no SentinelOracle on this chain) -- +const ZK_SYNC_CASH_MARKETS = [ + "0x03CAd66259f7F34EE075f8B62D133563D249eDa4", + "0x183dE3C349fCf546aAe925E1c7F364EA6FB4033c", + "0x1Fa916C27c7C2c4602124A14C77Dbb40a5FF1BE8", + "0x1aF23bD57c62A99C59aD48236553D0Dd11e49D2D", + "0x697a70779C1A03Ba2BD28b7627a902BFf831b616", + "0x69cDA960E3b20DFD480866fFfd377Ebe40bd0A46", + "0x84064c058F2EFea4AB648bB6Bd7e40f83fFDe39a", + "0xAF8fD83cFCbe963211FAaf1847F0F217F80B4719", + "0xCEb7Da150d16aCE58F090754feF2775C23C8b631", +]; + +// =================================================================================================== +// opmainnet +// =================================================================================================== +// -- common (ACM not in networkAddresses — hardcoded, verified on-chain) -- +const OP_ACM = "0xD71b1F33f6B0259683f11174EE4Ddc2bb9cE4eD6"; +const OP_XVS_BRIDGE_ADMIN = "0x3c307DF1Bf3198a2417d9CA86806B307D147Ddf7"; +const OP_OMNICHAIN_EXECUTOR_OWNER = "0xe6d9Eb3A07a1dc4496fc71417D7A7b9d5666BaA3"; +const OP_REDUNDANT: Record = { + Prime: "0xE76d2173546Be97Fa6E18358027BdE9742a649f7", + PrimeLiquidityProvider: "0x6412f6cd58D0182aE150b90B5A99e285b91C1a12", + VenusERC4626Factory: "0xc801B471F00Dc22B9a7d7b839CBE87E46d70946F", +}; +// -- unique (no SentinelOracle on this chain) -- +const OP_SYNC_CASH_MARKETS = [ + "0x1C9406ee95B7af55F005996947b19F91B6D55b15", + "0x37ac9731B0B02df54975cd0c7240e0977a051721", + "0x66d5AE25731Ce99D46770745385e662C8e0B4025", + "0x6b846E3418455804C1920fA4CC7a31A51C659A2D", + "0x9EfdCfC2373f81D3DF24647B1c46e15268884c46", +]; + +// =================================================================================================== +// unichainmainnet +// =================================================================================================== +// -- common (ACM not in networkAddresses — hardcoded, verified on-chain) -- +const UNI_ACM = "0x1f12014c497a9d905155eB9BfDD9FaC6885e61d0"; +const UNI_XVS_BRIDGE_ADMIN = "0x2EAaa880f97C9B63d37b39b0b316022d93d43604"; +const UNI_OMNICHAIN_EXECUTOR_OWNER = "0x6E78a0d96257F8F2615d72F3ee48cb6fb2c970bd"; +const UNI_REDUNDANT: Record = { + Prime: "0x600aFf613d40D87C8Fe90Cb2e78e8e6667c0C872", + PrimeLiquidityProvider: "0x045a45603E1b073F444fe3Be7d5C7e0a5035afB7", + VenusERC4626Factory: "0x102fEb723C25c67dbdfDccCa3B1c1a6e1a662D2f", +}; +// -- unique (no SentinelOracle on this chain) -- +const UNI_SYNC_CASH_MARKETS = [ + "0x0170398083eb0D0387709523baFCA6426146C218", + "0x67716D6Bf76170Af816F5735e14c4d44D0B05eD2", + "0x68e2A6F7257FAc2F5a557b9E83E1fE6D5B408CE5", + "0xB953f92B9f759d97d2F2Dec10A8A3cf75fcE3A95", + "0xDa7Ce7Ba016d266645712e2e4Ebc6cC75eA8E4CD", + "0xbEC19Bef402C697a7be315d3e59E5F65b89Fa1BB", + "0xc219BC179C7cDb37eACB03f993f9fDc2495e3374", +]; + +// =================================================================================================== +// opbnbmainnet +// =================================================================================================== +// -- common (ACM not in networkAddresses — hardcoded, verified on-chain) -- +const OPBNB_ACM = "0xA60Deae5344F1152426cA440fb6552eA0e3005D6"; +const OPBNB_XVS_BRIDGE_ADMIN = "0x52fcE05aDbf6103d71ed2BA8Be7A317282731831"; +const OPBNB_OMNICHAIN_EXECUTOR_OWNER = "0xf7e4c81Cf4A03d52472a4d00c3d9Ef35aF127E45"; +const OPBNB_REDUNDANT: Record = { + VenusERC4626Factory: "0x89A5Ce0A6db7e66E53F148B50D879b700dEB81C8", +}; +// -- unique (no SentinelOracle on this chain) -- +const OPBNB_SYNC_CASH_MARKETS = [ + "0x13B492B8A03d072Bab5C54AC91Dba5b830a50917", + "0x509e81eF638D489936FA85BC58F52Df01190d26C", + "0x53d11cB8A0e5320Cd7229C3acc80d1A0707F2672", + "0xED827b80Bd838192EA95002C01B5c6dA8354219a", + "0xb7a01Ba126830692238521a1aA7E7A7509410b8e", +]; + +// =================================================================================================== +// Reassembled per-chain records — indexed by chain key by consumers. Edit the per-chain blocks above, +// not these. +// =================================================================================================== +export const REMOTE_ACM: Record = { + ethereum: ETH_ACM, + arbitrumone: ARB_ACM, + basemainnet: BASE_ACM, + zksyncmainnet: ZK_ACM, + opmainnet: OP_ACM, + unichainmainnet: UNI_ACM, + opbnbmainnet: OPBNB_ACM, +}; + +export const XVS_BRIDGE_ADMIN: Record = { + ethereum: ETH_XVS_BRIDGE_ADMIN, + arbitrumone: ARB_XVS_BRIDGE_ADMIN, + basemainnet: BASE_XVS_BRIDGE_ADMIN, + zksyncmainnet: ZK_XVS_BRIDGE_ADMIN, + opmainnet: OP_XVS_BRIDGE_ADMIN, + unichainmainnet: UNI_XVS_BRIDGE_ADMIN, + opbnbmainnet: OPBNB_XVS_BRIDGE_ADMIN, +}; + +export const OMNICHAIN_EXECUTOR_OWNER: Record = { + ethereum: ETH_OMNICHAIN_EXECUTOR_OWNER, + arbitrumone: ARB_OMNICHAIN_EXECUTOR_OWNER, + basemainnet: BASE_OMNICHAIN_EXECUTOR_OWNER, + zksyncmainnet: ZK_OMNICHAIN_EXECUTOR_OWNER, + opmainnet: OP_OMNICHAIN_EXECUTOR_OWNER, + unichainmainnet: UNI_OMNICHAIN_EXECUTOR_OWNER, + opbnbmainnet: OPBNB_OMNICHAIN_EXECUTOR_OWNER, +}; + +export const SENTINEL_ORACLE: Partial> = { + ethereum: ETH_SENTINEL_ORACLE, + arbitrumone: ARB_SENTINEL_ORACLE, + basemainnet: BASE_SENTINEL_ORACLE, +}; + +export const SYNC_CASH_MARKETS: Record = { + ethereum: ETH_SYNC_CASH_MARKETS, + arbitrumone: ARB_SYNC_CASH_MARKETS, + basemainnet: BASE_SYNC_CASH_MARKETS, + zksyncmainnet: ZK_SYNC_CASH_MARKETS, + opmainnet: OP_SYNC_CASH_MARKETS, + unichainmainnet: UNI_SYNC_CASH_MARKETS, + opbnbmainnet: OPBNB_SYNC_CASH_MARKETS, +}; + +export const REDUNDANT_CONTRACTS: Record> = { + bscmainnet: BNB_REDUNDANT, + ethereum: ETH_REDUNDANT, + arbitrumone: ARB_REDUNDANT, + basemainnet: BASE_REDUNDANT, + opmainnet: OP_REDUNDANT, + unichainmainnet: UNI_REDUNDANT, + zksyncmainnet: ZK_REDUNDANT, + opbnbmainnet: OPBNB_REDUNDANT, +}; diff --git a/vips/vip-665/data/cleanup.ts b/vips/vip-665/data/cleanup.ts new file mode 100644 index 000000000..6e29ef56b --- /dev/null +++ b/vips/vip-665/data/cleanup.ts @@ -0,0 +1,295 @@ +// VIP-665 — ACM maintenance cleanup on all mainnets: +// 1. syncCash() normalized to the wildcard (address(0)) convention used by every other vToken setter. +// 2. Stale/dangling grants on removed setters and retired contracts. +// 3. Redundant target-specific grants shadowed by an identical wildcard grant (behavior-preserving). +import { NETWORK_ADDRESSES } from "src/networkAddresses"; + +import type { Permission } from "../utils/commands"; +import { RemoteChain } from "./criticalChanges"; +import { BNB_GUARDIANS, REDUNDANT_CONTRACTS as CONTRACTS, RETIRED, SYNC_CASH_MARKETS, ZERO } from "./addresses"; + +const { bscmainnet } = NETWORK_ADDRESSES; + +// =================================================================================================== +// 1. syncCash() wildcard normalization (remote chains) +// =================================================================================================== +export const SYNC_CASH_SIG = "syncCash()"; + +// The single wildcard grant: syncCash() on address(0) → NormalTimelock. +export const syncCashGrants = (chain: RemoteChain): Permission[] => { + const normalTimelock = (NETWORK_ADDRESSES[chain] as Record).NORMAL_TIMELOCK; + return [{ contractAddress: ZERO, functionSig: SYNC_CASH_SIG, account: normalTimelock }]; +}; + +// The per-market revokes: syncCash() → NormalTimelock, one per market. +export const syncCashRevokes = (chain: RemoteChain): Permission[] => { + const normalTimelock = (NETWORK_ADDRESSES[chain] as Record).NORMAL_TIMELOCK; + return SYNC_CASH_MARKETS[chain].map(market => ({ + contractAddress: market, + functionSig: SYNC_CASH_SIG, + account: normalTimelock, + })); +}; + +// =================================================================================================== +// 2. Stale / dangling grants (bscmainnet) +// =================================================================================================== +const NORMAL = bscmainnet.NORMAL_TIMELOCK; +const FASTTRACK = bscmainnet.FAST_TRACK_TIMELOCK; +const CRITICAL = bscmainnet.CRITICAL_TIMELOCK; +const G1 = BNB_GUARDIANS.guardian1; +const G2 = BNB_GUARDIANS.guardian2; +const G3 = BNB_GUARDIANS.guardian3; +const RISK_FUND_V2 = RETIRED.RiskFundV2; +const UNITROLLER = RETIRED.Unitroller; +const RETIRED_RISK_CONFIG = RETIRED.RiskConfig; +const RETIRED_RISK_STEWARD = RETIRED.RiskSteward; +const RETIRED_TWAP_ORACLE = RETIRED.TwapOracle; + +// target = contract the permission is scoped to (ZERO = wildcard); signature = function signature; +// revokeFrom = grantees to drop it from. +export const STALE_ROWS: { target: string; signature: string; revokeFrom: string[] }[] = [ + // Removed setter — the current RiskFundV2 implementation no longer has this function. + { + target: RISK_FUND_V2, + signature: "sweepTokenFromPool(address,address,address,uint256)", + revokeFrom: [NORMAL, FASTTRACK, CRITICAL], + }, + // Legacy 2-arg setter; Guardian 1 already holds the current 3/4-arg setters. + { target: UNITROLLER, signature: "_setCollateralFactor(address,uint256)", revokeFrom: [G1] }, + // Retired risk-config contract — dangling target grants. Holders confirmed on-chain: + // pause/unpause held by Normal+FastTrack+Critical+G2; setRiskParameterConfig only by Normal; + // toggleConfigActive by Normal+FastTrack+Critical (not G2). + { target: RETIRED_RISK_CONFIG, signature: "pause()", revokeFrom: [NORMAL, FASTTRACK, CRITICAL, G2] }, + { target: RETIRED_RISK_CONFIG, signature: "unpause()", revokeFrom: [NORMAL, FASTTRACK, CRITICAL, G2] }, + { target: RETIRED_RISK_CONFIG, signature: "setRiskParameterConfig(string,address,uint256)", revokeFrom: [NORMAL] }, + { target: RETIRED_RISK_CONFIG, signature: "toggleConfigActive(string)", revokeFrom: [NORMAL, FASTTRACK, CRITICAL] }, + // Retired risk steward — dangling target grant. + { target: RETIRED_RISK_STEWARD, signature: "setMaxDeltaBps(uint256)", revokeFrom: [NORMAL, FASTTRACK, CRITICAL] }, + // Retired oracle — dangling target grant. + { + target: RETIRED_TWAP_ORACLE, + signature: "setTokenConfig(TokenConfig)", + revokeFrom: [NORMAL, FASTTRACK, CRITICAL, G3], + }, +]; + +// Retired risk steward still holds live wildcard cap powers as a grantee. On the legacy BNB ACM these +// wildcard grants must be cleared with a direct ACM.revokeRole (see legacyWildcardRole in utils/commands.ts). +export const CLEANUP_LEGACY_WILDCARD_REVOKES: { signature: string; account: string }[] = [ + { signature: "setMarketBorrowCaps(address[],uint256[])", account: RETIRED_RISK_STEWARD }, + { signature: "setMarketSupplyCaps(address[],uint256[])", account: RETIRED_RISK_STEWARD }, +]; + +// =================================================================================================== +// 3. Redundant target-specific grants shadowed by an identical wildcard grant +// =================================================================================================== +export type RedundantChain = + | "bscmainnet" + | "ethereum" + | "arbitrumone" + | "basemainnet" + | "opmainnet" + | "unichainmainnet" + | "zksyncmainnet" + | "opbnbmainnet"; + +// Timelocks / guardians per chain, keyed by networkAddresses (cast for uniform string access). +const na = NETWORK_ADDRESSES as unknown as Record>; + +// A single target-specific grant to revoke. +export interface RedundantRevoke { + contract: string; + signature: string; + account: string; +} + +// The 75 redundant target-specific grants, per chain. +export const REDUNDANT_REVOKES: Record = { + bscmainnet: [ + { + contract: CONTRACTS.bscmainnet.Prime, + signature: "setMaxLoopsLimit(uint256)", + account: na.bscmainnet.NORMAL_TIMELOCK, + }, + { + contract: CONTRACTS.bscmainnet.PrimeLeaderboard, + signature: "setMaxLoopsLimit(uint256)", + account: na.bscmainnet.NORMAL_TIMELOCK, + }, + { + contract: CONTRACTS.bscmainnet.PrimeLiquidityProvider, + signature: "setMaxLoopsLimit(uint256)", + account: na.bscmainnet.NORMAL_TIMELOCK, + }, + { + contract: CONTRACTS.bscmainnet.PrimeV2, + signature: "setMaxLoopsLimit(uint256)", + account: na.bscmainnet.NORMAL_TIMELOCK, + }, + { + contract: CONTRACTS.bscmainnet.Unitroller, + signature: "setCollateralFactor(address,uint256,uint256)", + account: na.bscmainnet.NORMAL_TIMELOCK, + }, + { + contract: CONTRACTS.bscmainnet.Unitroller, + signature: "setCollateralFactor(address,uint256,uint256)", + account: na.bscmainnet.FAST_TRACK_TIMELOCK, + }, + { + contract: CONTRACTS.bscmainnet.Unitroller, + signature: "setCollateralFactor(address,uint256,uint256)", + account: na.bscmainnet.CRITICAL_TIMELOCK, + }, + { + contract: CONTRACTS.bscmainnet.Unitroller, + signature: "setCollateralFactor(address,uint256,uint256)", + account: na.bscmainnet.CRITICAL_GUARDIAN, + }, + { + contract: CONTRACTS.bscmainnet.Unitroller, + signature: "unlistMarket(address)", + account: na.bscmainnet.NORMAL_TIMELOCK, + }, + { + contract: CONTRACTS.bscmainnet.Unitroller, + signature: "unlistMarket(address)", + account: na.bscmainnet.FAST_TRACK_TIMELOCK, + }, + { + contract: CONTRACTS.bscmainnet.Unitroller, + signature: "unlistMarket(address)", + account: na.bscmainnet.CRITICAL_TIMELOCK, + }, + { + contract: CONTRACTS.bscmainnet.Unitroller, + signature: "unlistMarket(address)", + account: na.bscmainnet.GUARDIAN, + }, + { + contract: CONTRACTS.bscmainnet.VenusERC4626Factory, + signature: "setMaxLoopsLimit(uint256)", + account: na.bscmainnet.NORMAL_TIMELOCK, + }, + { + contract: CONTRACTS.bscmainnet.vUNI, + signature: "_setReserveFactor(uint256)", + account: na.bscmainnet.NORMAL_TIMELOCK, + }, + { + contract: CONTRACTS.bscmainnet.vUNI, + signature: "setReduceReservesBlockDelta(uint256)", + account: na.bscmainnet.NORMAL_TIMELOCK, + }, + ], + ethereum: [ + { + contract: CONTRACTS.ethereum.Prime, + signature: "setMaxLoopsLimit(uint256)", + account: na.ethereum.NORMAL_TIMELOCK, + }, + { + contract: CONTRACTS.ethereum.PrimeLiquidityProvider, + signature: "setMaxLoopsLimit(uint256)", + account: na.ethereum.NORMAL_TIMELOCK, + }, + { + contract: CONTRACTS.ethereum.VenusERC4626Factory, + signature: "setMaxLoopsLimit(uint256)", + account: na.ethereum.NORMAL_TIMELOCK, + }, + ], + arbitrumone: [ + { + contract: CONTRACTS.arbitrumone.Prime, + signature: "setMaxLoopsLimit(uint256)", + account: na.arbitrumone.NORMAL_TIMELOCK, + }, + { + contract: CONTRACTS.arbitrumone.PrimeLiquidityProvider, + signature: "setMaxLoopsLimit(uint256)", + account: na.arbitrumone.NORMAL_TIMELOCK, + }, + { + contract: CONTRACTS.arbitrumone.VenusERC4626Factory, + signature: "setMaxLoopsLimit(uint256)", + account: na.arbitrumone.NORMAL_TIMELOCK, + }, + ], + basemainnet: [ + { + contract: CONTRACTS.basemainnet.Prime, + signature: "setMaxLoopsLimit(uint256)", + account: na.basemainnet.NORMAL_TIMELOCK, + }, + { + contract: CONTRACTS.basemainnet.PrimeLiquidityProvider, + signature: "setMaxLoopsLimit(uint256)", + account: na.basemainnet.NORMAL_TIMELOCK, + }, + { + contract: CONTRACTS.basemainnet.VenusERC4626Factory, + signature: "setMaxLoopsLimit(uint256)", + account: na.basemainnet.NORMAL_TIMELOCK, + }, + ], + opmainnet: [ + { + contract: CONTRACTS.opmainnet.Prime, + signature: "setMaxLoopsLimit(uint256)", + account: na.opmainnet.NORMAL_TIMELOCK, + }, + { + contract: CONTRACTS.opmainnet.PrimeLiquidityProvider, + signature: "setMaxLoopsLimit(uint256)", + account: na.opmainnet.NORMAL_TIMELOCK, + }, + { + contract: CONTRACTS.opmainnet.VenusERC4626Factory, + signature: "setMaxLoopsLimit(uint256)", + account: na.opmainnet.NORMAL_TIMELOCK, + }, + ], + unichainmainnet: [ + { + contract: CONTRACTS.unichainmainnet.Prime, + signature: "setMaxLoopsLimit(uint256)", + account: na.unichainmainnet.NORMAL_TIMELOCK, + }, + { + contract: CONTRACTS.unichainmainnet.PrimeLiquidityProvider, + signature: "setMaxLoopsLimit(uint256)", + account: na.unichainmainnet.NORMAL_TIMELOCK, + }, + { + contract: CONTRACTS.unichainmainnet.VenusERC4626Factory, + signature: "setMaxLoopsLimit(uint256)", + account: na.unichainmainnet.NORMAL_TIMELOCK, + }, + ], + zksyncmainnet: [ + { + contract: CONTRACTS.zksyncmainnet.Prime, + signature: "setMaxLoopsLimit(uint256)", + account: na.zksyncmainnet.NORMAL_TIMELOCK, + }, + { + contract: CONTRACTS.zksyncmainnet.PrimeLiquidityProvider, + signature: "setMaxLoopsLimit(uint256)", + account: na.zksyncmainnet.NORMAL_TIMELOCK, + }, + { + contract: CONTRACTS.zksyncmainnet.VenusERC4626Factory, + signature: "setMaxLoopsLimit(uint256)", + account: na.zksyncmainnet.NORMAL_TIMELOCK, + }, + ], + opbnbmainnet: [ + { + contract: CONTRACTS.opbnbmainnet.VenusERC4626Factory, + signature: "setMaxLoopsLimit(uint256)", + account: na.opbnbmainnet.NORMAL_TIMELOCK, + }, + ], +}; diff --git a/vips/vip-665/data/criticalChanges.ts b/vips/vip-665/data/criticalChanges.ts new file mode 100644 index 000000000..46026311c --- /dev/null +++ b/vips/vip-665/data/criticalChanges.ts @@ -0,0 +1,269 @@ +// VIP-665 — CriticalTimelock privilege changes across BNB Chain and the 7 remote mainnets. +// This file is the review surface: only permissions the VIP actually changes. Untouched ("No change") +// permissions are intentionally not listed — the VIP does not touch them, so there is nothing to assert. +import { NETWORK_ADDRESSES } from "src/networkAddresses"; +import { LzChainId } from "src/types"; + +import { + BNB_CRITICAL, + BNB_CONTRACTS as CONTRACTS, + OMNICHAIN_EXECUTOR_OWNER, + SENTINEL_ORACLE, + XVS_BRIDGE_ADMIN, + ZERO, +} from "./addresses"; + +// =================================================================================================== +// BNB Chain +// =================================================================================================== +// revoke = remove from Critical · swap = grant to the Guardian then revoke from Critical · grant = add to +// the Guardian while Critical keeps it. grantTo = destination Guardian for swap/grant. +export type Action = "revoke" | "swap" | "grant"; + +export interface BnbAction { + target: string; + signature: string; + action: Action; + grantTo?: "guardian1" | "guardian2" | "guardian3"; +} + +export const BNB_ACTIONS: BnbAction[] = [ + { target: CONTRACTS.Unitroller, signature: "setCollateralFactor(address,uint256,uint256)", action: "revoke" }, + { target: CONTRACTS.Unitroller, signature: "setCollateralFactor(uint96,address,uint256,uint256)", action: "revoke" }, + { target: CONTRACTS.Unitroller, signature: "setWhiteListFlashLoanAccount(address,bool)", action: "revoke" }, + { target: CONTRACTS.Unitroller, signature: "_setMarketBorrowCaps(address[],uint256[])", action: "revoke" }, + { target: CONTRACTS.Unitroller, signature: "_setMarketSupplyCaps(address[],uint256[])", action: "revoke" }, + { target: CONTRACTS.Unitroller, signature: "setIsBorrowAllowed(uint96,address,bool)", action: "revoke" }, + { + target: CONTRACTS.Unitroller, + signature: "_setForcedLiquidation(address,bool)", + action: "swap", + grantTo: "guardian1", + }, + { + target: CONTRACTS.Unitroller, + signature: "_setForcedLiquidationForUser(address,address,bool)", + action: "swap", + grantTo: "guardian1", + }, + { target: CONTRACTS.XVSVaultProxy, signature: "resume()", action: "revoke" }, + { target: CONTRACTS.ChainlinkOracle, signature: "setDirectPrice(address,uint256)", action: "revoke" }, + { target: CONTRACTS.ChainlinkOracle, signature: "setTokenConfig(TokenConfig)", action: "revoke" }, + { target: CONTRACTS.ChainlinkOracle, signature: "setUnderlyingPrice(address,uint256)", action: "revoke" }, + { + target: CONTRACTS.DeviationBoundedOracle, + signature: "setThresholds(address,uint256,uint256)", + action: "grant", + grantTo: "guardian1", + }, + { target: CONTRACTS.PythOracle, signature: "setTokenConfig(TokenConfig)", action: "revoke" }, + { target: CONTRACTS.RedStoneOracle, signature: "setDirectPrice(address,uint256)", action: "revoke" }, + { target: CONTRACTS.RedStoneOracle, signature: "setTokenConfig(TokenConfig)", action: "revoke" }, + { target: CONTRACTS.ResilientOracle, signature: "setTokenConfig(TokenConfig)", action: "revoke" }, + { target: CONTRACTS.USDTChainlinkOracle, signature: "setDirectPrice(address,uint256)", action: "revoke" }, + { target: CONTRACTS.USDTChainlinkOracle, signature: "setTokenConfig(TokenConfig)", action: "revoke" }, + { + target: CONTRACTS.ProtocolShareReserve, + signature: "addOrUpdateDistributionConfigs(DistributionConfig[])", + action: "revoke", + }, + { + target: CONTRACTS.RelativePositionManager, + signature: "executePositionAccountCall(address,address[],bytes[])", + action: "revoke", + }, + { target: CONTRACTS.SentinelOracle, signature: "setDirectPrice(address,uint256)", action: "revoke" }, + { target: CONTRACTS.SentinelOracle, signature: "setTokenOracleConfig(address,address)", action: "revoke" }, + { target: CONTRACTS.XVSBridgeAdmin, signature: "setWhitelist(address,bool)", action: "swap", grantTo: "guardian1" }, + { target: CONTRACTS.XVSBridgeAdmin, signature: "setMaxDailyLimit(uint16,uint256)", action: "revoke" }, + { target: CONTRACTS.XVSBridgeAdmin, signature: "setMaxDailyReceiveLimit(uint16,uint256)", action: "revoke" }, + { + target: CONTRACTS.XVSBridgeAdmin, + signature: "setMaxSingleReceiveTransactionLimit(uint16,uint256)", + action: "revoke", + }, + { target: CONTRACTS.XVSBridgeAdmin, signature: "setMaxSingleTransactionLimit(uint16,uint256)", action: "revoke" }, + { target: CONTRACTS.XVSBridgeAdmin, signature: "unpause()", action: "revoke" }, + { + target: CONTRACTS.InstitutionalVaultControllerProxy, + signature: "setLiquidationThreshold(address,uint256)", + action: "swap", + grantTo: "guardian1", + }, + { target: CONTRACTS.InstitutionalVaultControllerProxy, signature: "sweep(address,address)", action: "revoke" }, +]; + +// setCollateralFactor on the isolated-pools wildcard target is revoked from Critical via a direct +// ACM.revokeRole on the legacy BNB ACM (the aggregator cannot clear wildcard grants there — see +// legacyWildcardRole in utils/commands.ts). +export const BNB_ACTION_LEGACY_WILDCARD_REVOKES: { signature: string; account: string }[] = [ + { signature: "setCollateralFactor(address,uint256,uint256)", account: BNB_CRITICAL }, +]; + +// =================================================================================================== +// Remote chains — all Critical revokes +// =================================================================================================== +export type RemoteChain = + | "ethereum" + | "arbitrumone" + | "basemainnet" + | "zksyncmainnet" + | "opmainnet" + | "unichainmainnet" + | "opbnbmainnet"; + +export const REMOTE_CHAINS: RemoteChain[] = [ + "ethereum", + "arbitrumone", + "basemainnet", + "zksyncmainnet", + "opmainnet", + "unichainmainnet", + "opbnbmainnet", +]; + +export const LZ_CHAIN_ID: Record = { + ethereum: LzChainId.ethereum, + arbitrumone: LzChainId.arbitrumone, + basemainnet: LzChainId.basemainnet, + zksyncmainnet: LzChainId.zksyncmainnet, + opmainnet: LzChainId.opmainnet, + unichainmainnet: LzChainId.unichainmainnet, + opbnbmainnet: LzChainId.opbnbmainnet, +}; + +const addressesOf = (chain: RemoteChain) => NETWORK_ADDRESSES[chain] as Record; + +// Resolve a contract key to its address on a chain. On Arbitrum/OP the "CHAINLINK_ORACLE" key is the +// SequencerChainlinkOracle adapter. +export const remoteAddr = (chain: RemoteChain, c: string): string => { + switch (c) { + case "WILDCARD": + return ZERO; + case "ResilientOracle": + return addressesOf(chain).RESILIENT_ORACLE; + case "ChainlinkOracle": + return addressesOf(chain).CHAINLINK_ORACLE; + case "RedStoneOracle": + return addressesOf(chain).REDSTONE_ORACLE; + case "SequencerChainlinkOracle": + return addressesOf(chain).CHAINLINK_ORACLE; + case "SentinelOracle": + return SENTINEL_ORACLE[chain]!; + case "XVSBridgeAdmin": + return XVS_BRIDGE_ADMIN[chain]; + case "OmnichainExecutorOwner": + return OMNICHAIN_EXECUTOR_OWNER[chain]; + default: + throw new Error(`unknown remote contract key ${c}`); + } +}; + +// One revoke-from-Critical row. contract = contract key resolved per-chain by remoteAddr. +export interface RemoteRow { + signature: string; + contract: string; +} + +// Revoke rows written out explicitly per chain. Each block is ordered `common` (the 5 rows every remote +// shares — repeated in every block by design, for readability) then `chain-specific`. Editing here is the +// review surface; remoteRowsFor() just resolves each contract key to its address. +export const REMOTE_ROWS_BY_CHAIN: Record = { + ethereum: [ + // ── common to all remotes ── + { signature: "setTokenConfig(TokenConfig)", contract: "ResilientOracle" }, + { signature: "setCollateralFactor(address,uint256,uint256)", contract: "WILDCARD" }, + { signature: "addTimelocks(address[])", contract: "OmnichainExecutorOwner" }, + { signature: "setMaxDailyReceiveLimit(uint256)", contract: "OmnichainExecutorOwner" }, + { signature: "setWhitelist(address,bool)", contract: "XVSBridgeAdmin" }, + // ── chain-specific ── + { signature: "setDirectPrice(address,uint256)", contract: "ChainlinkOracle" }, + { signature: "setTokenConfig(TokenConfig)", contract: "ChainlinkOracle" }, + { signature: "setDirectPrice(address,uint256)", contract: "RedStoneOracle" }, + { signature: "setTokenConfig(TokenConfig)", contract: "RedStoneOracle" }, + { signature: "setDirectPrice(address,uint256)", contract: "SentinelOracle" }, + { signature: "setTokenOracleConfig(address,address)", contract: "SentinelOracle" }, + ], + arbitrumone: [ + // ── common to all remotes ── + { signature: "setTokenConfig(TokenConfig)", contract: "ResilientOracle" }, + { signature: "setCollateralFactor(address,uint256,uint256)", contract: "WILDCARD" }, + { signature: "addTimelocks(address[])", contract: "OmnichainExecutorOwner" }, + { signature: "setMaxDailyReceiveLimit(uint256)", contract: "OmnichainExecutorOwner" }, + { signature: "setWhitelist(address,bool)", contract: "XVSBridgeAdmin" }, + // ── chain-specific ── + { signature: "setDirectPrice(address,uint256)", contract: "RedStoneOracle" }, + { signature: "setTokenConfig(TokenConfig)", contract: "RedStoneOracle" }, + { signature: "setDirectPrice(address,uint256)", contract: "SequencerChainlinkOracle" }, + { signature: "setTokenConfig(TokenConfig)", contract: "SequencerChainlinkOracle" }, + { signature: "setDirectPrice(address,uint256)", contract: "SentinelOracle" }, + { signature: "setTokenOracleConfig(address,address)", contract: "SentinelOracle" }, + ], + basemainnet: [ + // ── common to all remotes ── + { signature: "setTokenConfig(TokenConfig)", contract: "ResilientOracle" }, + { signature: "setCollateralFactor(address,uint256,uint256)", contract: "WILDCARD" }, + { signature: "addTimelocks(address[])", contract: "OmnichainExecutorOwner" }, + { signature: "setMaxDailyReceiveLimit(uint256)", contract: "OmnichainExecutorOwner" }, + { signature: "setWhitelist(address,bool)", contract: "XVSBridgeAdmin" }, + // ── chain-specific ── + { signature: "setDirectPrice(address,uint256)", contract: "ChainlinkOracle" }, + { signature: "setTokenConfig(TokenConfig)", contract: "ChainlinkOracle" }, + { signature: "setDirectPrice(address,uint256)", contract: "RedStoneOracle" }, + { signature: "setTokenConfig(TokenConfig)", contract: "RedStoneOracle" }, + { signature: "setDirectPrice(address,uint256)", contract: "SentinelOracle" }, + { signature: "setTokenOracleConfig(address,address)", contract: "SentinelOracle" }, + ], + zksyncmainnet: [ + // ── common to all remotes ── + { signature: "setTokenConfig(TokenConfig)", contract: "ResilientOracle" }, + { signature: "setCollateralFactor(address,uint256,uint256)", contract: "WILDCARD" }, + { signature: "addTimelocks(address[])", contract: "OmnichainExecutorOwner" }, + { signature: "setMaxDailyReceiveLimit(uint256)", contract: "OmnichainExecutorOwner" }, + { signature: "setWhitelist(address,bool)", contract: "XVSBridgeAdmin" }, + // ── chain-specific ── + { signature: "setDirectPrice(address,uint256)", contract: "ChainlinkOracle" }, + { signature: "setTokenConfig(TokenConfig)", contract: "ChainlinkOracle" }, + { signature: "setDirectPrice(address,uint256)", contract: "RedStoneOracle" }, + { signature: "setTokenConfig(TokenConfig)", contract: "RedStoneOracle" }, + ], + opmainnet: [ + // ── common to all remotes ── + { signature: "setTokenConfig(TokenConfig)", contract: "ResilientOracle" }, + { signature: "setCollateralFactor(address,uint256,uint256)", contract: "WILDCARD" }, + { signature: "addTimelocks(address[])", contract: "OmnichainExecutorOwner" }, + { signature: "setMaxDailyReceiveLimit(uint256)", contract: "OmnichainExecutorOwner" }, + { signature: "setWhitelist(address,bool)", contract: "XVSBridgeAdmin" }, + // ── chain-specific ── + { signature: "setDirectPrice(address,uint256)", contract: "RedStoneOracle" }, + { signature: "setTokenConfig(TokenConfig)", contract: "RedStoneOracle" }, + { signature: "setDirectPrice(address,uint256)", contract: "SequencerChainlinkOracle" }, + { signature: "setTokenConfig(TokenConfig)", contract: "SequencerChainlinkOracle" }, + ], + unichainmainnet: [ + // ── common to all remotes ── + { signature: "setTokenConfig(TokenConfig)", contract: "ResilientOracle" }, + { signature: "setCollateralFactor(address,uint256,uint256)", contract: "WILDCARD" }, + { signature: "addTimelocks(address[])", contract: "OmnichainExecutorOwner" }, + { signature: "setMaxDailyReceiveLimit(uint256)", contract: "OmnichainExecutorOwner" }, + { signature: "setWhitelist(address,bool)", contract: "XVSBridgeAdmin" }, + // ── chain-specific ── + { signature: "setDirectPrice(address,uint256)", contract: "RedStoneOracle" }, + { signature: "setTokenConfig(TokenConfig)", contract: "RedStoneOracle" }, + ], + opbnbmainnet: [ + // ── common to all remotes (no chain-specific rows) ── + { signature: "setTokenConfig(TokenConfig)", contract: "ResilientOracle" }, + { signature: "setCollateralFactor(address,uint256,uint256)", contract: "WILDCARD" }, + { signature: "addTimelocks(address[])", contract: "OmnichainExecutorOwner" }, + { signature: "setMaxDailyReceiveLimit(uint256)", contract: "OmnichainExecutorOwner" }, + { signature: "setWhitelist(address,bool)", contract: "XVSBridgeAdmin" }, + ], +}; + +// Rows applicable to one chain, with resolved target address. +export const remoteRowsFor = (chain: RemoteChain) => + REMOTE_ROWS_BY_CHAIN[chain].map(r => ({ + signature: r.signature, + target: remoteAddr(chain, r.contract), + })); diff --git a/vips/vip-665/scripts/seedAggregators.ts b/vips/vip-665/scripts/seedAggregators.ts new file mode 100644 index 000000000..73a7a1231 --- /dev/null +++ b/vips/vip-665/scripts/seedAggregators.ts @@ -0,0 +1,40 @@ +/** + * Seeds the VIP-665 grant / revoke batches onto a chain's ACMCommandsAggregator. + * + * Run once per chain, by a funded account, BEFORE the VIP is proposed. addGrant/RevokePermissions are + * permissionless, so any signer works. The printed indices must be wired into GRANT_INDEX / REVOKE_INDEX in + * vips/vip-665/utils/commands.ts. + * + * npx hardhat run vips/vip-665/scripts/seedAggregators.ts --network + * # for zksync add: --config hardhat.config.zksync.ts + */ +import { ethers, network } from "hardhat"; + +import { AGGREGATOR, Chain, buildGrantPermissions, buildRevokePermissions } from "../utils/commands"; +import { seedAggregator } from "../utils/seed"; + +async function main() { + const chain = network.name as Chain; + if (!(chain in AGGREGATOR)) throw new Error(`VIP-665 has no aggregator for network "${chain}"`); + + const [signer] = await ethers.getSigners(); + const grants = buildGrantPermissions(chain); + const revokes = buildRevokePermissions(chain); + + console.log(`Chain ${chain} — aggregator ${AGGREGATOR[chain]}`); + console.log(` seeding ${grants.length} grant(s) and ${revokes.length} revoke(s) from ${await signer.getAddress()}`); + + const { grantIndex, revokeIndex } = await seedAggregator(signer, AGGREGATOR[chain], grants, revokes); + + if (grantIndex !== undefined) console.log(` grant batch landed at index ${grantIndex} → set GRANT_INDEX.${chain}`); + if (revokeIndex !== undefined) + console.log(` revoke batch landed at index ${revokeIndex} → set REVOKE_INDEX.${chain}`); +} + +main().then( + () => process.exit(0), + err => { + console.error(err); + process.exit(1); + }, +); diff --git a/vips/vip-665/utils/commands.ts b/vips/vip-665/utils/commands.ts new file mode 100644 index 000000000..007af806c --- /dev/null +++ b/vips/vip-665/utils/commands.ts @@ -0,0 +1,178 @@ +import { ethers } from "ethers"; +import { NETWORK_ADDRESSES } from "src/networkAddresses"; +import { Command } from "src/types"; + +import { BNB_ACM, BNB_CRITICAL, BNB_GUARDIANS, REMOTE_ACM } from "../data/addresses"; +import { + CLEANUP_LEGACY_WILDCARD_REVOKES, + REDUNDANT_REVOKES, + RedundantChain, + STALE_ROWS, + syncCashGrants, + syncCashRevokes, +} from "../data/cleanup"; +import { + BNB_ACTIONS, + BNB_ACTION_LEGACY_WILDCARD_REVOKES, + REMOTE_CHAINS, + RemoteChain, + remoteRowsFor, +} from "../data/criticalChanges"; + +export type Chain = "bscmainnet" | RemoteChain; +export const CHAINS: Chain[] = ["bscmainnet", ...REMOTE_CHAINS]; + +export const DEFAULT_ADMIN_ROLE = "0x0000000000000000000000000000000000000000000000000000000000000000"; + +// ACMCommandsAggregator per chain +export const AGGREGATOR: Record = { + bscmainnet: "0x8b443Ea6726E56DF4C4F62f80F0556bB9B2a7c64", + ethereum: "0xb78772bed6995551b64e54Cdb8e09800d86C73ee", + arbitrumone: "0x74AFeA28456a683b8fF907699Ff77138edef00f3", + basemainnet: "0xB2770DBD5146f7ee0766Dc9E3931433bb697Aa06", + zksyncmainnet: "0x88B1452e512c8fcf83889DdCfe54dF37D561Db82", + opmainnet: "0xbbEBaF646e7a3E4064a899e68565B1b439eFdf70", + unichainmainnet: "0x904D11b00bdB2740d16176cc00DE139d0d626115", + opbnbmainnet: "0x6dB5e303289fea2E83F7d442470210045592AD93", +}; + +export const GRANT_INDEX: Record = { + bscmainnet: 4, + ethereum: 3, + arbitrumone: 2, + basemainnet: 2, + zksyncmainnet: 2, + opmainnet: 2, + unichainmainnet: 4, + opbnbmainnet: 2, +}; +export const REVOKE_INDEX: Record = { + bscmainnet: 0, + ethereum: 2, + arbitrumone: 2, + basemainnet: 1, + zksyncmainnet: 1, + opmainnet: 1, + unichainmainnet: 2, + opbnbmainnet: 2, +}; + +export const acmOf = (chain: Chain): string => (chain === "bscmainnet" ? BNB_ACM : REMOTE_ACM[chain]); +export const criticalOf = (chain: Chain): string => + chain === "bscmainnet" ? BNB_CRITICAL : NETWORK_ADDRESSES[chain].CRITICAL_TIMELOCK; + +// A single ACM permission the aggregator grants or revokes. Field names match the on-chain +// ACMCommandsAggregator.Permission struct (contractAddress, functionSig, account). +export interface Permission { + contractAddress: string; + functionSig: string; + account: string; +} + +// ==================================================================================================== +// Critical maintenance — the VIP's core intent: reduce CriticalTimelock privileges. +// ==================================================================================================== +// Grants: on BNB a swap/grant action hands the function to a Guardian. Remotes only revoke, so no grants. +export const criticalGrants = (chain: Chain): Permission[] => { + if (chain !== "bscmainnet") return []; + return BNB_ACTIONS.filter(r => r.action === "swap" || r.action === "grant").map(r => ({ + contractAddress: r.target, + functionSig: r.signature, + account: BNB_GUARDIANS[r.grantTo ?? "guardian1"], + })); +}; + +// Revokes: on BNB every revoke/swap action drops the function from Critical; on remotes every remote row +// from that chain's Critical. +export const criticalRevokes = (chain: Chain): Permission[] => { + if (chain === "bscmainnet") + return BNB_ACTIONS.filter(r => r.action === "revoke" || r.action === "swap").map(r => ({ + contractAddress: r.target, + functionSig: r.signature, + account: BNB_CRITICAL, + })); + const critical = criticalOf(chain); + return remoteRowsFor(chain).map(r => ({ + contractAddress: r.target, + functionSig: r.signature, + account: critical, + })); +}; + +// ==================================================================================================== +// Cleanup — ACM maintenance, independent of the Critical privilege reduction. Three sub-categories: +// syncCash() normalization (remotes), stale/dangling grants (BNB), and redundant wildcard-shadowed grants. +// ==================================================================================================== +// Grants: the single wildcard syncCash() grant to NormalTimelock (remotes only; BNB has no cleanup grant). +export const cleanupGrants = (chain: Chain): Permission[] => (chain === "bscmainnet" ? [] : syncCashGrants(chain)); + +// Redundant target-specific grants — shadowed by an identical wildcard grant held by the same account, so +// revoking them is behavior-preserving. +const redundantRevokes = (chain: Chain): Permission[] => + REDUNDANT_REVOKES[chain as RedundantChain].map(r => ({ + contractAddress: r.contract, + functionSig: r.signature, + account: r.account, + })); + +// Revokes: the stale/dangling grants (BNB) or the per-market syncCash() grants (remotes), then the redundant +// grants (all chains). De-duplication against the Critical revokes happens in buildRevokePermissions. +export const cleanupRevokes = (chain: Chain): Permission[] => { + const rows: Permission[] = []; + if (chain === "bscmainnet") { + for (const s of STALE_ROWS) + for (const who of s.revokeFrom) rows.push({ contractAddress: s.target, functionSig: s.signature, account: who }); + } else { + rows.push(...syncCashRevokes(chain)); + } + rows.push(...redundantRevokes(chain)); + return rows; +}; + +// ==================================================================================================== +// Combined — every grant / every revoke for a chain. These are what seed the ACM aggregator. +// ==================================================================================================== +// Key identifying a unique ACM revoke (contract + function + grantee). +const revokeKey = (p: Permission) => `${p.contractAddress.toLowerCase()}|${p.functionSig}|${p.account.toLowerCase()}`; + +// Every grant: Critical maintenance + cleanup. The two never overlap, so no de-duplication needed. +export const buildGrantPermissions = (chain: Chain): Permission[] => [ + ...criticalGrants(chain), + ...cleanupGrants(chain), +]; + +// Every revoke: Critical maintenance + cleanup, then de-duplicated. A redundant revoke can coincide with a +// Critical revoke (e.g. Unitroller.setCollateralFactor from Critical on BNB), and the aggregator must not +// revoke the same (contract, function, account) twice. +export const buildRevokePermissions = (chain: Chain): Permission[] => { + const seen = new Set(); + return [...criticalRevokes(chain), ...cleanupRevokes(chain)].filter(p => { + const k = revokeKey(p); + if (seen.has(k)) return false; + seen.add(k); + return true; + }); +}; + +// BNB legacy-ACM wildcard quirk. The AccessControlManager on bscmainnet derives a wildcard ("any contract") +// role from the 32-byte DEFAULT_ADMIN_ROLE constant, not the 20-byte address(0). revokeCallPermission hashes +// the 20-byte zero, so it targets a different role and can never clear a wildcard grant — and the +// ACMCommandsAggregator only calls give/revokeCallPermission. These grants are cleared with a direct +// ACM.revokeRole(legacyRole, account), which the NormalTimelock (proposal executor and ACM DEFAULT_ADMIN) can call. +export const legacyWildcardRole = (signature: string): string => + ethers.utils.solidityKeccak256(["bytes32", "string"], [ethers.constants.HashZero, signature]); + +// Wildcard grants on the bscmainnet legacy ACM the plan revokes but the aggregator cannot reach: the +// setCollateralFactor revoke from Critical (action plan) and the retired risk steward cap powers (cleanup). +export const BNB_LEGACY_WILDCARD_REVOKES: { signature: string; account: string }[] = [ + ...BNB_ACTION_LEGACY_WILDCARD_REVOKES, + ...CLEANUP_LEGACY_WILDCARD_REVOKES, +]; + +// Direct ACM.revokeRole calls for the legacy wildcard grants, executed by the NormalTimelock. +export const legacyWildcardCommands = (): Command[] => + BNB_LEGACY_WILDCARD_REVOKES.map(r => ({ + target: BNB_ACM, + signature: "revokeRole(bytes32,address)", + params: [legacyWildcardRole(r.signature), r.account], + })); diff --git a/vips/vip-665/utils/seed.ts b/vips/vip-665/utils/seed.ts new file mode 100644 index 000000000..8d3cda114 --- /dev/null +++ b/vips/vip-665/utils/seed.ts @@ -0,0 +1,33 @@ +import { Contract, Signer } from "ethers"; + +import ACM_COMMANDS_AGGREGATOR_ABI from "../../../simulations/vip-665/abi/ACMCommandsAggregator.json"; +import { Permission } from "./commands"; + +// Append the VIP's grant and revoke batches to a chain's ACMCommandsAggregator and return the indices they +// landed at (read from the emitted *PermissionsAdded events). addGrant/RevokePermissions are permissionless, +// so any funded signer works — the fork simulations pass an impersonated signer, scripts/seedAggregators.ts +// passes the configured deployer. +export const seedAggregator = async ( + signer: Signer, + aggregator: string, + grants: Permission[], + revokes: Permission[], +): Promise<{ grantIndex?: number; revokeIndex?: number }> => { + const agg = new Contract(aggregator, ACM_COMMANDS_AGGREGATOR_ABI, signer); + const result: { grantIndex?: number; revokeIndex?: number } = {}; + + if (grants.length > 0) { + const receipt = await (await agg.addGrantPermissions(grants)).wait(); + result.grantIndex = receipt.events + ?.find((e: { event?: string }) => e.event === "GrantPermissionsAdded") + ?.args?.index.toNumber(); + } + if (revokes.length > 0) { + const receipt = await (await agg.addRevokePermissions(revokes)).wait(); + result.revokeIndex = receipt.events + ?.find((e: { event?: string }) => e.event === "RevokePermissionsAdded") + ?.args?.index.toNumber(); + } + + return result; +};