From 151d37ae2fb19de77e5bc541aa007baaaf59833d Mon Sep 17 00:00:00 2001 From: Debugger022 Date: Wed, 1 Apr 2026 19:24:24 +0530 Subject: [PATCH 01/11] feat: add VIP-661 configure EBrake-integrated DeviationSentinel on BSC testnet - Upgrade DeviationSentinel proxy to new EBrake-routing implementation - Grant EBrake ACM permissions on comptrollers (_setActionsPaused, setCollateralFactor, borrow/supply caps) - Grant DeviationSentinel permissions to call EBrake (pauseBorrow, pauseSupply, setCFZero) - Revoke old direct comptroller permissions from DeviationSentinel - Add simulation tests verifying pre/post VIP state --- .../vip-661/abi/AccessControlManager.json | 360 +++++++++++ .../vip-661/abi/DeviationSentinel.json | 589 ++++++++++++++++++ simulations/vip-661/abi/EBrake.json | 586 +++++++++++++++++ simulations/vip-661/abi/ProxyAdmin.json | 151 +++++ simulations/vip-661/bsctestnet.ts | 200 ++++++ vips/vip-661/bsctestnet.ts | 130 ++++ 6 files changed, 2016 insertions(+) create mode 100644 simulations/vip-661/abi/AccessControlManager.json create mode 100644 simulations/vip-661/abi/DeviationSentinel.json create mode 100644 simulations/vip-661/abi/EBrake.json create mode 100644 simulations/vip-661/abi/ProxyAdmin.json create mode 100644 simulations/vip-661/bsctestnet.ts create mode 100644 vips/vip-661/bsctestnet.ts diff --git a/simulations/vip-661/abi/AccessControlManager.json b/simulations/vip-661/abi/AccessControlManager.json new file mode 100644 index 000000000..4a118fcc4 --- /dev/null +++ b/simulations/vip-661/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-661/abi/DeviationSentinel.json b/simulations/vip-661/abi/DeviationSentinel.json new file mode 100644 index 000000000..c12ea017a --- /dev/null +++ b/simulations/vip-661/abi/DeviationSentinel.json @@ -0,0 +1,589 @@ +[ + { + "inputs": [ + { + "internalType": "contract IEBrake", + "name": "eBrake_", + "type": "address" + }, + { + "internalType": "contract ResilientOracleInterface", + "name": "resilientOracle_", + "type": "address" + }, + { + "internalType": "contract OracleInterface", + "name": "sentinelOracle_", + "type": "address" + } + ], + "stateMutability": "nonpayable", + "type": "constructor" + }, + { + "inputs": [], + "name": "ExceedsMaxDeviation", + "type": "error" + }, + { + "inputs": [], + "name": "MarketNotConfigured", + "type": "error" + }, + { + "inputs": [], + "name": "TokenMonitoringDisabled", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "sender", + "type": "address" + }, + { + "internalType": "address", + "name": "calledContract", + "type": "address" + }, + { + "internalType": "string", + "name": "methodSignature", + "type": "string" + } + ], + "name": "Unauthorized", + "type": "error" + }, + { + "inputs": [], + "name": "UnauthorizedKeeper", + "type": "error" + }, + { + "inputs": [], + "name": "ZeroAddress", + "type": "error" + }, + { + "inputs": [], + "name": "ZeroDeviation", + "type": "error" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "market", + "type": "address" + } + ], + "name": "BorrowPaused", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "uint8", + "name": "version", + "type": "uint8" + } + ], + "name": "Initialized", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "market", + "type": "address" + } + ], + "name": "MarketStateReset", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "address", + "name": "oldAccessControlManager", + "type": "address" + }, + { + "indexed": false, + "internalType": "address", + "name": "newAccessControlManager", + "type": "address" + } + ], + "name": "NewAccessControlManager", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "previousOwner", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "newOwner", + "type": "address" + } + ], + "name": "OwnershipTransferStarted", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "previousOwner", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "newOwner", + "type": "address" + } + ], + "name": "OwnershipTransferred", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "market", + "type": "address" + } + ], + "name": "SupplyPaused", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "token", + "type": "address" + }, + { + "components": [ + { + "internalType": "uint8", + "name": "deviation", + "type": "uint8" + }, + { + "internalType": "bool", + "name": "enabled", + "type": "bool" + } + ], + "indexed": false, + "internalType": "struct DeviationSentinel.DeviationConfig", + "name": "config", + "type": "tuple" + } + ], + "name": "TokenConfigUpdated", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "token", + "type": "address" + }, + { + "indexed": false, + "internalType": "bool", + "name": "enabled", + "type": "bool" + } + ], + "name": "TokenMonitoringStatusChanged", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "keeper", + "type": "address" + }, + { + "indexed": false, + "internalType": "bool", + "name": "isTrusted", + "type": "bool" + } + ], + "name": "TrustedKeeperUpdated", + "type": "event" + }, + { + "inputs": [], + "name": "EBRAKE", + "outputs": [ + { + "internalType": "contract IEBrake", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "MAX_DEVIATION", + "outputs": [ + { + "internalType": "uint8", + "name": "", + "type": "uint8" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "RESILIENT_ORACLE", + "outputs": [ + { + "internalType": "contract ResilientOracleInterface", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "SENTINEL_ORACLE", + "outputs": [ + { + "internalType": "contract OracleInterface", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "acceptOwnership", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "accessControlManager", + "outputs": [ + { + "internalType": "contract IAccessControlManagerV8", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "contract IVToken", + "name": "market", + "type": "address" + } + ], + "name": "checkPriceDeviation", + "outputs": [ + { + "internalType": "bool", + "name": "hasDeviation", + "type": "bool" + }, + { + "internalType": "uint256", + "name": "oraclePrice", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "sentinelPrice", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "deviationPercent", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "contract IVToken", + "name": "market", + "type": "address" + } + ], + "name": "handleDeviation", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "accessControlManager_", + "type": "address" + } + ], + "name": "initialize", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "name": "marketStates", + "outputs": [ + { + "internalType": "bool", + "name": "borrowPaused", + "type": "bool" + }, + { + "internalType": "bool", + "name": "cfModifiedAndSupplyPaused", + "type": "bool" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "owner", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "pendingOwner", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "renounceOwnership", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "contract IVToken", + "name": "market", + "type": "address" + } + ], + "name": "resetMarketState", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "accessControlManager_", + "type": "address" + } + ], + "name": "setAccessControlManager", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "token", + "type": "address" + }, + { + "components": [ + { + "internalType": "uint8", + "name": "deviation", + "type": "uint8" + }, + { + "internalType": "bool", + "name": "enabled", + "type": "bool" + } + ], + "internalType": "struct DeviationSentinel.DeviationConfig", + "name": "config", + "type": "tuple" + } + ], + "name": "setTokenConfig", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "token", + "type": "address" + }, + { + "internalType": "bool", + "name": "enabled", + "type": "bool" + } + ], + "name": "setTokenMonitoringEnabled", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "keeper", + "type": "address" + }, + { + "internalType": "bool", + "name": "isTrusted", + "type": "bool" + } + ], + "name": "setTrustedKeeper", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "name": "tokenConfigs", + "outputs": [ + { + "internalType": "uint8", + "name": "deviation", + "type": "uint8" + }, + { + "internalType": "bool", + "name": "enabled", + "type": "bool" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "newOwner", + "type": "address" + } + ], + "name": "transferOwnership", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "name": "trustedKeepers", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "view", + "type": "function" + } +] diff --git a/simulations/vip-661/abi/EBrake.json b/simulations/vip-661/abi/EBrake.json new file mode 100644 index 000000000..5d568cbd8 --- /dev/null +++ b/simulations/vip-661/abi/EBrake.json @@ -0,0 +1,586 @@ +[ + { + "inputs": [ + { + "internalType": "contract ICorePoolComptroller", + "name": "corePoolComptroller_", + "type": "address" + }, + { + "internalType": "address", + "name": "accessControlManager_", + "type": "address" + }, + { + "internalType": "bool", + "name": "isIsolatedPool_", + "type": "bool" + } + ], + "stateMutability": "nonpayable", + "type": "constructor" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "expected", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "actual", + "type": "uint256" + } + ], + "name": "ArrayLengthMismatch", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "market", + "type": "address" + }, + { + "internalType": "uint256", + "name": "currentCap", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "requestedCap", + "type": "uint256" + } + ], + "name": "CapExceedsCurrent", + "type": "error" + }, + { + "inputs": [], + "name": "EmptyArray", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "enum IComptroller.Action", + "name": "action", + "type": "uint8" + } + ], + "name": "ForbiddenAction", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "uint96", + "name": "poolId", + "type": "uint96" + }, + { + "internalType": "address", + "name": "market", + "type": "address" + } + ], + "name": "MarketNotListed", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "errorCode", + "type": "uint256" + } + ], + "name": "SetCollateralFactorFailed", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "sender", + "type": "address" + }, + { + "internalType": "address", + "name": "calledContract", + "type": "address" + }, + { + "internalType": "string", + "name": "methodSignature", + "type": "string" + } + ], + "name": "Unauthorized", + "type": "error" + }, + { + "inputs": [], + "name": "ZeroAddress", + "type": "error" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "caller", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "market", + "type": "address" + }, + { + "indexed": false, + "internalType": "enum IComptroller.Action", + "name": "action", + "type": "uint8" + } + ], + "name": "ActionPaused", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "caller", + "type": "address" + }, + { + "indexed": false, + "internalType": "address[]", + "name": "markets", + "type": "address[]" + }, + { + "indexed": false, + "internalType": "enum IComptroller.Action[]", + "name": "actions", + "type": "uint8[]" + } + ], + "name": "ActionsPaused", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "caller", + "type": "address" + }, + { + "indexed": false, + "internalType": "address[]", + "name": "markets", + "type": "address[]" + }, + { + "indexed": false, + "internalType": "uint256[]", + "name": "newBorrowCaps", + "type": "uint256[]" + } + ], + "name": "BorrowCapsDecreased", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "caller", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "market", + "type": "address" + }, + { + "indexed": true, + "internalType": "uint96", + "name": "poolId", + "type": "uint96" + } + ], + "name": "CollateralFactorZeroed", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "caller", + "type": "address" + } + ], + "name": "FlashLoanPaused", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "uint8", + "name": "version", + "type": "uint8" + } + ], + "name": "Initialized", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "address", + "name": "oldAccessControlManager", + "type": "address" + }, + { + "indexed": false, + "internalType": "address", + "name": "newAccessControlManager", + "type": "address" + } + ], + "name": "NewAccessControlManager", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "previousOwner", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "newOwner", + "type": "address" + } + ], + "name": "OwnershipTransferStarted", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "previousOwner", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "newOwner", + "type": "address" + } + ], + "name": "OwnershipTransferred", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "caller", + "type": "address" + }, + { + "indexed": false, + "internalType": "address[]", + "name": "markets", + "type": "address[]" + }, + { + "indexed": false, + "internalType": "uint256[]", + "name": "newSupplyCaps", + "type": "uint256[]" + } + ], + "name": "SupplyCapsDecreased", + "type": "event" + }, + { + "inputs": [], + "name": "COMPTROLLER", + "outputs": [ + { + "internalType": "contract ICorePoolComptroller", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "IS_ISOLATED_POOL", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "acceptOwnership", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "accessControlManager", + "outputs": [ + { + "internalType": "contract IAccessControlManagerV8", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "owner", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address[]", + "name": "markets", + "type": "address[]" + }, + { + "internalType": "enum IComptroller.Action[]", + "name": "actions", + "type": "uint8[]" + } + ], + "name": "pauseActions", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "market", + "type": "address" + } + ], + "name": "pauseBorrow", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "pauseFlashLoan", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "market", + "type": "address" + } + ], + "name": "pauseRedeem", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "market", + "type": "address" + } + ], + "name": "pauseSupply", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "market", + "type": "address" + } + ], + "name": "pauseTransfer", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "pendingOwner", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "renounceOwnership", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "accessControlManager_", + "type": "address" + } + ], + "name": "setAccessControlManager", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "market", + "type": "address" + } + ], + "name": "setCFZero", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "market", + "type": "address" + }, + { + "internalType": "uint96", + "name": "poolId", + "type": "uint96" + } + ], + "name": "setCFZero", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address[]", + "name": "markets", + "type": "address[]" + }, + { + "internalType": "uint256[]", + "name": "newBorrowCaps", + "type": "uint256[]" + } + ], + "name": "setMarketBorrowCaps", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address[]", + "name": "markets", + "type": "address[]" + }, + { + "internalType": "uint256[]", + "name": "newSupplyCaps", + "type": "uint256[]" + } + ], + "name": "setMarketSupplyCaps", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "newOwner", + "type": "address" + } + ], + "name": "transferOwnership", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + } +] diff --git a/simulations/vip-661/abi/ProxyAdmin.json b/simulations/vip-661/abi/ProxyAdmin.json new file mode 100644 index 000000000..b4c51d8df --- /dev/null +++ b/simulations/vip-661/abi/ProxyAdmin.json @@ -0,0 +1,151 @@ +[ + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "previousOwner", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "newOwner", + "type": "address" + } + ], + "name": "OwnershipTransferred", + "type": "event" + }, + { + "inputs": [ + { + "internalType": "contract TransparentUpgradeableProxy", + "name": "proxy", + "type": "address" + }, + { + "internalType": "address", + "name": "newAdmin", + "type": "address" + } + ], + "name": "changeProxyAdmin", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "contract TransparentUpgradeableProxy", + "name": "proxy", + "type": "address" + } + ], + "name": "getProxyAdmin", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "contract TransparentUpgradeableProxy", + "name": "proxy", + "type": "address" + } + ], + "name": "getProxyImplementation", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "owner", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "renounceOwnership", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "newOwner", + "type": "address" + } + ], + "name": "transferOwnership", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "contract TransparentUpgradeableProxy", + "name": "proxy", + "type": "address" + }, + { + "internalType": "address", + "name": "implementation", + "type": "address" + } + ], + "name": "upgrade", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "contract TransparentUpgradeableProxy", + "name": "proxy", + "type": "address" + }, + { + "internalType": "address", + "name": "implementation", + "type": "address" + }, + { + "internalType": "bytes", + "name": "data", + "type": "bytes" + } + ], + "name": "upgradeAndCall", + "outputs": [], + "stateMutability": "payable", + "type": "function" + } +] diff --git a/simulations/vip-661/bsctestnet.ts b/simulations/vip-661/bsctestnet.ts new file mode 100644 index 000000000..f8777a567 --- /dev/null +++ b/simulations/vip-661/bsctestnet.ts @@ -0,0 +1,200 @@ +import { expect } from "chai"; +import { Contract } from "ethers"; +import { ethers } from "hardhat"; +import { expectEvents } from "src/utils"; +import { forking, testVip } from "src/vip-framework"; + +import { + ACM, + DEVIATION_SENTINEL, + EBRAKE, + NEW_DEVIATION_SENTINEL_IMPL, + PROXY_ADMIN, + vip661Testnet, +} from "../../vips/vip-661/bsctestnet"; +import ACCESS_CONTROL_MANAGER_ABI from "./abi/AccessControlManager.json"; +import DEVIATION_SENTINEL_ABI from "./abi/DeviationSentinel.json"; +import PROXY_ADMIN_ABI from "./abi/ProxyAdmin.json"; + +forking(99131750, async () => { + let accessControlManager: Contract; + let proxyAdmin: Contract; + let deviationSentinel: Contract; + + before(async () => { + accessControlManager = await ethers.getContractAt(ACCESS_CONTROL_MANAGER_ABI, ACM); + proxyAdmin = new ethers.Contract(PROXY_ADMIN, PROXY_ADMIN_ABI, ethers.provider); + deviationSentinel = await ethers.getContractAt(DEVIATION_SENTINEL_ABI, DEVIATION_SENTINEL); + }); + + describe("Pre-VIP behavior", () => { + it("DeviationSentinel proxy should not point to new implementation", async () => { + const impl = await proxyAdmin.getProxyImplementation(DEVIATION_SENTINEL); + expect(impl).to.not.equal(NEW_DEVIATION_SENTINEL_IMPL); + }); + + it("EBrake should not have comptroller permissions", async () => { + expect( + await accessControlManager.hasPermission( + EBRAKE, + ethers.constants.AddressZero, + "_setActionsPaused(address[],uint8[],bool)", + ), + ).to.equal(false); + expect( + await accessControlManager.hasPermission( + EBRAKE, + ethers.constants.AddressZero, + "setCollateralFactor(uint96,address,uint256,uint256)", + ), + ).to.equal(false); + expect( + await accessControlManager.hasPermission( + EBRAKE, + ethers.constants.AddressZero, + "_setMarketBorrowCaps(address[],uint256[])", + ), + ).to.equal(false); + expect( + await accessControlManager.hasPermission( + EBRAKE, + ethers.constants.AddressZero, + "_setMarketSupplyCaps(address[],uint256[])", + ), + ).to.equal(false); + }); + + it("DeviationSentinel should not have permissions on EBrake", async () => { + expect(await accessControlManager.hasPermission(DEVIATION_SENTINEL, EBRAKE, "pauseBorrow(address)")).to.equal( + false, + ); + expect(await accessControlManager.hasPermission(DEVIATION_SENTINEL, EBRAKE, "pauseSupply(address)")).to.equal( + false, + ); + expect(await accessControlManager.hasPermission(DEVIATION_SENTINEL, EBRAKE, "setCFZero(address)")).to.equal( + false, + ); + }); + + it("DeviationSentinel should have old direct comptroller permissions", async () => { + expect( + await accessControlManager.hasPermission( + DEVIATION_SENTINEL, + ethers.constants.AddressZero, + "setActionsPaused(address[],uint8[],bool)", + ), + ).to.equal(true); + expect( + await accessControlManager.hasPermission( + DEVIATION_SENTINEL, + ethers.constants.AddressZero, + "_setActionsPaused(address[],uint8[],bool)", + ), + ).to.equal(true); + expect( + await accessControlManager.hasPermission( + DEVIATION_SENTINEL, + ethers.constants.AddressZero, + "setCollateralFactor(address,uint256,uint256)", + ), + ).to.equal(true); + expect( + await accessControlManager.hasPermission( + DEVIATION_SENTINEL, + ethers.constants.AddressZero, + "setCollateralFactor(uint96,address,uint256,uint256)", + ), + ).to.equal(true); + }); + }); + + testVip("VIP-661 Configure EBrake-integrated DeviationSentinel", await vip661Testnet(), { + callbackAfterExecution: async txResponse => { + await expectEvents(txResponse, [ACCESS_CONTROL_MANAGER_ABI], ["PermissionGranted"], [7]); + await expectEvents(txResponse, [ACCESS_CONTROL_MANAGER_ABI], ["PermissionRevoked"], [4]); + }, + }); + + describe("Post-VIP behavior", () => { + it("DeviationSentinel proxy should point to new implementation", async () => { + const impl = await proxyAdmin.getProxyImplementation(DEVIATION_SENTINEL); + expect(impl).to.equal(NEW_DEVIATION_SENTINEL_IMPL); + }); + + it("DeviationSentinel should return correct EBrake address", async () => { + expect(await deviationSentinel.EBRAKE()).to.equal(EBRAKE); + }); + + it("EBrake should have comptroller permissions", async () => { + expect( + await accessControlManager.hasPermission( + EBRAKE, + ethers.constants.AddressZero, + "_setActionsPaused(address[],uint8[],bool)", + ), + ).to.equal(true); + expect( + await accessControlManager.hasPermission( + EBRAKE, + ethers.constants.AddressZero, + "setCollateralFactor(uint96,address,uint256,uint256)", + ), + ).to.equal(true); + expect( + await accessControlManager.hasPermission( + EBRAKE, + ethers.constants.AddressZero, + "_setMarketBorrowCaps(address[],uint256[])", + ), + ).to.equal(true); + expect( + await accessControlManager.hasPermission( + EBRAKE, + ethers.constants.AddressZero, + "_setMarketSupplyCaps(address[],uint256[])", + ), + ).to.equal(true); + }); + + it("DeviationSentinel should have permissions on EBrake", async () => { + expect(await accessControlManager.hasPermission(DEVIATION_SENTINEL, EBRAKE, "pauseBorrow(address)")).to.equal( + true, + ); + expect(await accessControlManager.hasPermission(DEVIATION_SENTINEL, EBRAKE, "pauseSupply(address)")).to.equal( + true, + ); + expect(await accessControlManager.hasPermission(DEVIATION_SENTINEL, EBRAKE, "setCFZero(address)")).to.equal(true); + }); + + it("DeviationSentinel should no longer have direct comptroller permissions", async () => { + expect( + await accessControlManager.hasPermission( + DEVIATION_SENTINEL, + ethers.constants.AddressZero, + "setActionsPaused(address[],uint8[],bool)", + ), + ).to.equal(false); + expect( + await accessControlManager.hasPermission( + DEVIATION_SENTINEL, + ethers.constants.AddressZero, + "_setActionsPaused(address[],uint8[],bool)", + ), + ).to.equal(false); + expect( + await accessControlManager.hasPermission( + DEVIATION_SENTINEL, + ethers.constants.AddressZero, + "setCollateralFactor(address,uint256,uint256)", + ), + ).to.equal(false); + expect( + await accessControlManager.hasPermission( + DEVIATION_SENTINEL, + ethers.constants.AddressZero, + "setCollateralFactor(uint96,address,uint256,uint256)", + ), + ).to.equal(false); + }); + }); +}); diff --git a/vips/vip-661/bsctestnet.ts b/vips/vip-661/bsctestnet.ts new file mode 100644 index 000000000..0bf42e349 --- /dev/null +++ b/vips/vip-661/bsctestnet.ts @@ -0,0 +1,130 @@ +import { ethers } from "hardhat"; +import { ProposalType } from "src/types"; +import { makeProposal } from "src/utils"; + +// New deployments +export const EBRAKE = "0x73f0d19A34e466286f909346F2EF08A18D0228D2"; +export const NEW_DEVIATION_SENTINEL_IMPL = "0x10716E3Bde7770BD84C4A3d7EC06BB0885C0a891"; + +// Existing testnet addresses +export const DEVIATION_SENTINEL = "0x9245d72712548707809D66848e63B8E2B169F3c1"; +export const PROXY_ADMIN = "0x7877ffd62649b6a1557b55d4c20fcbab17344c91"; +export const ACM = "0x45f8a08F534f34A97187626E05d4b6648Eeaa9AA"; + +export const vip661Testnet = () => { + const meta = { + version: "v2", + title: "VIP-661 Configure EBrake-integrated DeviationSentinel on BSC Testnet", + description: `#### Summary + +This VIP configures the EBrake-integrated DeviationSentinel system on BSC Testnet by: + +1. Upgrading the DeviationSentinel proxy to the new implementation that routes actions through EBrake +2. Granting EBrake permissions on comptrollers (so EBrake can pause actions and set collateral factors) +3. Granting DeviationSentinel permissions on EBrake (so sentinel can trigger emergency actions via EBrake) +4. Revoking old direct comptroller permissions from DeviationSentinel (sentinel now goes through EBrake) + +#### Description + +**DeviationSentinel** has been refactored to route all emergency actions through the **EBrake** contract instead of calling comptrollers directly. When a price deviation is detected, DeviationSentinel calls EBrake functions (pauseBorrow, pauseSupply, setCFZero), and EBrake in turn calls the appropriate comptroller functions. + +This architecture change adds an additional safety layer and standardizes emergency action routing. + +#### References + +- [DeviationSentinel Proxy](https://testnet.bscscan.com/address/${DEVIATION_SENTINEL}) +- [New Implementation](https://testnet.bscscan.com/address/${NEW_DEVIATION_SENTINEL_IMPL}) +- [EBrake Contract](https://testnet.bscscan.com/address/${EBRAKE})`, + forDescription: "Execute this proposal", + againstDescription: "Do not execute this proposal", + abstainDescription: "Indifferent to execution", + }; + + return makeProposal( + [ + // ======================================== + // 1. Upgrade DeviationSentinel proxy to new implementation + // ======================================== + { + target: PROXY_ADMIN, + signature: "upgrade(address,address)", + params: [DEVIATION_SENTINEL, NEW_DEVIATION_SENTINEL_IMPL], + }, + + // ======================================== + // 2. Grant EBrake permissions on comptrollers + // ======================================== + { + target: ACM, + signature: "giveCallPermission(address,string,address)", + params: [ethers.constants.AddressZero, "_setActionsPaused(address[],uint8[],bool)", EBRAKE], + }, + { + target: ACM, + signature: "giveCallPermission(address,string,address)", + params: [ethers.constants.AddressZero, "setCollateralFactor(uint96,address,uint256,uint256)", EBRAKE], + }, + { + target: ACM, + signature: "giveCallPermission(address,string,address)", + params: [ethers.constants.AddressZero, "_setMarketBorrowCaps(address[],uint256[])", EBRAKE], + }, + { + target: ACM, + signature: "giveCallPermission(address,string,address)", + params: [ethers.constants.AddressZero, "_setMarketSupplyCaps(address[],uint256[])", EBRAKE], + }, + + // ======================================== + // 3. Grant DeviationSentinel permissions on EBrake + // ======================================== + { + target: ACM, + signature: "giveCallPermission(address,string,address)", + params: [EBRAKE, "pauseBorrow(address)", DEVIATION_SENTINEL], + }, + { + target: ACM, + signature: "giveCallPermission(address,string,address)", + params: [EBRAKE, "pauseSupply(address)", DEVIATION_SENTINEL], + }, + { + target: ACM, + signature: "giveCallPermission(address,string,address)", + params: [EBRAKE, "setCFZero(address)", DEVIATION_SENTINEL], + }, + + // ======================================== + // 4. Revoke old direct comptroller permissions from DeviationSentinel + // ======================================== + { + target: ACM, + signature: "revokeCallPermission(address,string,address)", + params: [ethers.constants.AddressZero, "setActionsPaused(address[],uint8[],bool)", DEVIATION_SENTINEL], + }, + { + target: ACM, + signature: "revokeCallPermission(address,string,address)", + params: [ethers.constants.AddressZero, "_setActionsPaused(address[],uint8[],bool)", DEVIATION_SENTINEL], + }, + { + target: ACM, + signature: "revokeCallPermission(address,string,address)", + params: [ethers.constants.AddressZero, "setCollateralFactor(address,uint256,uint256)", DEVIATION_SENTINEL], + }, + { + target: ACM, + signature: "revokeCallPermission(address,string,address)", + params: [ + ethers.constants.AddressZero, + "setCollateralFactor(uint96,address,uint256,uint256)", + DEVIATION_SENTINEL, + ], + }, + ], + meta, + ProposalType.REGULAR, + ); +}; + +export default vip661Testnet; From 6b4cdaee3b917245930d8d55d6d6f0cf07a2de1f Mon Sep 17 00:00:00 2001 From: Debugger022 Date: Thu, 2 Apr 2026 17:56:21 +0530 Subject: [PATCH 02/11] fix: update VIP-661 contract addresses and fork block on BSC testnet --- simulations/vip-661/bsctestnet.ts | 2 +- vips/vip-661/bsctestnet.ts | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/simulations/vip-661/bsctestnet.ts b/simulations/vip-661/bsctestnet.ts index f8777a567..04ea133c9 100644 --- a/simulations/vip-661/bsctestnet.ts +++ b/simulations/vip-661/bsctestnet.ts @@ -16,7 +16,7 @@ import ACCESS_CONTROL_MANAGER_ABI from "./abi/AccessControlManager.json"; import DEVIATION_SENTINEL_ABI from "./abi/DeviationSentinel.json"; import PROXY_ADMIN_ABI from "./abi/ProxyAdmin.json"; -forking(99131750, async () => { +forking(99316719, async () => { let accessControlManager: Contract; let proxyAdmin: Contract; let deviationSentinel: Contract; diff --git a/vips/vip-661/bsctestnet.ts b/vips/vip-661/bsctestnet.ts index 0bf42e349..55d1d29c7 100644 --- a/vips/vip-661/bsctestnet.ts +++ b/vips/vip-661/bsctestnet.ts @@ -3,8 +3,8 @@ import { ProposalType } from "src/types"; import { makeProposal } from "src/utils"; // New deployments -export const EBRAKE = "0x73f0d19A34e466286f909346F2EF08A18D0228D2"; -export const NEW_DEVIATION_SENTINEL_IMPL = "0x10716E3Bde7770BD84C4A3d7EC06BB0885C0a891"; +export const EBRAKE = "0x957c09e3Ac3d9e689244DC74307c94111FBa8B42"; +export const NEW_DEVIATION_SENTINEL_IMPL = "0xBC7249c0ABb89a374CD2AA2bEEd0820E2E189e03"; // Existing testnet addresses export const DEVIATION_SENTINEL = "0x9245d72712548707809D66848e63B8E2B169F3c1"; From 24f2067a3307a400e0109a485e563885fdd07ee3 Mon Sep 17 00:00:00 2001 From: Debugger022 Date: Thu, 2 Apr 2026 18:35:41 +0530 Subject: [PATCH 03/11] feat: add EBrake and timelock permissions to VIP-661 on BSC testnet Grant additional EBrake comptroller permissions (setCollateralFactor, setMarketBorrowCaps, setMarketSupplyCaps, setFlashLoanPaused) and resetMarketState permission to all three timelocks. Update simulation with corresponding pre/post checks and event count --- simulations/vip-661/bsctestnet.ts | 69 ++++++++++++++++++++++++++++++- vips/vip-661/bsctestnet.ts | 44 +++++++++++++++++++- 2 files changed, 111 insertions(+), 2 deletions(-) diff --git a/simulations/vip-661/bsctestnet.ts b/simulations/vip-661/bsctestnet.ts index 04ea133c9..3ee43a310 100644 --- a/simulations/vip-661/bsctestnet.ts +++ b/simulations/vip-661/bsctestnet.ts @@ -4,6 +4,7 @@ import { ethers } from "hardhat"; import { expectEvents } from "src/utils"; import { forking, testVip } from "src/vip-framework"; +import { NETWORK_ADDRESSES } from "../../src/networkAddresses"; import { ACM, DEVIATION_SENTINEL, @@ -16,6 +17,8 @@ import ACCESS_CONTROL_MANAGER_ABI from "./abi/AccessControlManager.json"; import DEVIATION_SENTINEL_ABI from "./abi/DeviationSentinel.json"; import PROXY_ADMIN_ABI from "./abi/ProxyAdmin.json"; +const { NORMAL_TIMELOCK, FAST_TRACK_TIMELOCK, CRITICAL_TIMELOCK } = NETWORK_ADDRESSES.bsctestnet; + forking(99316719, async () => { let accessControlManager: Contract; let proxyAdmin: Contract; @@ -62,6 +65,38 @@ forking(99316719, async () => { "_setMarketSupplyCaps(address[],uint256[])", ), ).to.equal(false); + expect( + await accessControlManager.hasPermission( + EBRAKE, + ethers.constants.AddressZero, + "setCollateralFactor(address,uint256,uint256)", + ), + ).to.equal(false); + expect( + await accessControlManager.hasPermission( + EBRAKE, + ethers.constants.AddressZero, + "setMarketBorrowCaps(address[],uint256[])", + ), + ).to.equal(false); + expect( + await accessControlManager.hasPermission( + EBRAKE, + ethers.constants.AddressZero, + "setMarketSupplyCaps(address[],uint256[])", + ), + ).to.equal(false); + expect( + await accessControlManager.hasPermission(EBRAKE, ethers.constants.AddressZero, "setFlashLoanPaused(bool)"), + ).to.equal(false); + }); + + it("Timelocks should not have resetMarketState permission", async () => { + for (const timelock of [NORMAL_TIMELOCK, FAST_TRACK_TIMELOCK, CRITICAL_TIMELOCK]) { + expect( + await accessControlManager.hasPermission(timelock, ethers.constants.AddressZero, "resetMarketState(address)"), + ).to.equal(false); + } }); it("DeviationSentinel should not have permissions on EBrake", async () => { @@ -110,7 +145,7 @@ forking(99316719, async () => { testVip("VIP-661 Configure EBrake-integrated DeviationSentinel", await vip661Testnet(), { callbackAfterExecution: async txResponse => { - await expectEvents(txResponse, [ACCESS_CONTROL_MANAGER_ABI], ["PermissionGranted"], [7]); + await expectEvents(txResponse, [ACCESS_CONTROL_MANAGER_ABI], ["PermissionGranted"], [14]); await expectEvents(txResponse, [ACCESS_CONTROL_MANAGER_ABI], ["PermissionRevoked"], [4]); }, }); @@ -154,6 +189,38 @@ forking(99316719, async () => { "_setMarketSupplyCaps(address[],uint256[])", ), ).to.equal(true); + expect( + await accessControlManager.hasPermission( + EBRAKE, + ethers.constants.AddressZero, + "setCollateralFactor(address,uint256,uint256)", + ), + ).to.equal(true); + expect( + await accessControlManager.hasPermission( + EBRAKE, + ethers.constants.AddressZero, + "setMarketBorrowCaps(address[],uint256[])", + ), + ).to.equal(true); + expect( + await accessControlManager.hasPermission( + EBRAKE, + ethers.constants.AddressZero, + "setMarketSupplyCaps(address[],uint256[])", + ), + ).to.equal(true); + expect( + await accessControlManager.hasPermission(EBRAKE, ethers.constants.AddressZero, "setFlashLoanPaused(bool)"), + ).to.equal(true); + }); + + it("Timelocks should have resetMarketState permission", async () => { + for (const timelock of [NORMAL_TIMELOCK, FAST_TRACK_TIMELOCK, CRITICAL_TIMELOCK]) { + expect( + await accessControlManager.hasPermission(timelock, ethers.constants.AddressZero, "resetMarketState(address)"), + ).to.equal(true); + } }); it("DeviationSentinel should have permissions on EBrake", async () => { diff --git a/vips/vip-661/bsctestnet.ts b/vips/vip-661/bsctestnet.ts index 55d1d29c7..67f9a7808 100644 --- a/vips/vip-661/bsctestnet.ts +++ b/vips/vip-661/bsctestnet.ts @@ -1,7 +1,10 @@ import { ethers } from "hardhat"; +import { NETWORK_ADDRESSES } from "src/networkAddresses"; import { ProposalType } from "src/types"; import { makeProposal } from "src/utils"; +const { NORMAL_TIMELOCK, FAST_TRACK_TIMELOCK, CRITICAL_TIMELOCK } = NETWORK_ADDRESSES.bsctestnet; + // New deployments export const EBRAKE = "0x957c09e3Ac3d9e689244DC74307c94111FBa8B42"; export const NEW_DEVIATION_SENTINEL_IMPL = "0xBC7249c0ABb89a374CD2AA2bEEd0820E2E189e03"; @@ -14,7 +17,7 @@ export const ACM = "0x45f8a08F534f34A97187626E05d4b6648Eeaa9AA"; export const vip661Testnet = () => { const meta = { version: "v2", - title: "VIP-661 Configure EBrake-integrated DeviationSentinel on BSC Testnet", + title: "VIP-661 Configure EBrake-integrated DeviationSentinel", description: `#### Summary This VIP configures the EBrake-integrated DeviationSentinel system on BSC Testnet by: @@ -64,6 +67,11 @@ This architecture change adds an additional safety layer and standardizes emerge signature: "giveCallPermission(address,string,address)", params: [ethers.constants.AddressZero, "setCollateralFactor(uint96,address,uint256,uint256)", EBRAKE], }, + { + target: ACM, + signature: "giveCallPermission(address,string,address)", + params: [ethers.constants.AddressZero, "setCollateralFactor(address,uint256,uint256)", EBRAKE], + }, { target: ACM, signature: "giveCallPermission(address,string,address)", @@ -74,6 +82,40 @@ This architecture change adds an additional safety layer and standardizes emerge signature: "giveCallPermission(address,string,address)", params: [ethers.constants.AddressZero, "_setMarketSupplyCaps(address[],uint256[])", EBRAKE], }, + { + target: ACM, + signature: "giveCallPermission(address,string,address)", + params: [ethers.constants.AddressZero, "setMarketBorrowCaps(address[],uint256[])", EBRAKE], + }, + { + target: ACM, + signature: "giveCallPermission(address,string,address)", + params: [ethers.constants.AddressZero, "setMarketSupplyCaps(address[],uint256[])", EBRAKE], + }, + { + target: ACM, + signature: "giveCallPermission(address,string,address)", + params: [ethers.constants.AddressZero, "setFlashLoanPaused(bool)", EBRAKE], + }, + + // ======================================== + // 2b. Grant resetMarketState permission to timelocks + // ======================================== + { + target: ACM, + signature: "giveCallPermission(address,string,address)", + params: [ethers.constants.AddressZero, "resetMarketState(address)", NORMAL_TIMELOCK], + }, + { + target: ACM, + signature: "giveCallPermission(address,string,address)", + params: [ethers.constants.AddressZero, "resetMarketState(address)", FAST_TRACK_TIMELOCK], + }, + { + target: ACM, + signature: "giveCallPermission(address,string,address)", + params: [ethers.constants.AddressZero, "resetMarketState(address)", CRITICAL_TIMELOCK], + }, // ======================================== // 3. Grant DeviationSentinel permissions on EBrake From 81f7d78d86b0dccba59c8dec4fde6514f603c2b1 Mon Sep 17 00:00:00 2001 From: Debugger022 Date: Mon, 6 Apr 2026 20:00:01 +0530 Subject: [PATCH 04/11] feat: add VIP-661 addendum to recover vETH_Core after EBrake incident on testnet - Restore vETH_Core collateral factor and unpause mint after EBrake tightened the market during DeviationSentinel testing - Clear EBrake snapshot for vETH_Core via resetMarketState - Grant keeper permissions for _setActionsPaused, setCollateralFactor, and resetMarketState on testnet to enable repeatable E2E testing - Update EBrake ABI with new initialize, marketStates, getMarketCFSnapshot, resetMarketState, and MarketStateReset entries - Add ERC20, ResilientOracle, and Comptroller ABIs for simulation --- simulations/vip-661/abi/EBrake.json | 107 +- simulations/vip-661/abi/ERC20.json | 134 + simulations/vip-661/abi/ResilientOracle.json | 750 ++++ simulations/vip-661/abi/comptroller.json | 3979 ++++++++++++++++++ simulations/vip-661/bsctestnet-addendum.ts | 166 + vips/vip-661/bsctestnet-addendum.ts | 98 + 6 files changed, 5229 insertions(+), 5 deletions(-) create mode 100644 simulations/vip-661/abi/ERC20.json create mode 100644 simulations/vip-661/abi/ResilientOracle.json create mode 100644 simulations/vip-661/abi/comptroller.json create mode 100644 simulations/vip-661/bsctestnet-addendum.ts create mode 100644 vips/vip-661/bsctestnet-addendum.ts diff --git a/simulations/vip-661/abi/EBrake.json b/simulations/vip-661/abi/EBrake.json index 5d568cbd8..c7aedf09d 100644 --- a/simulations/vip-661/abi/EBrake.json +++ b/simulations/vip-661/abi/EBrake.json @@ -6,11 +6,6 @@ "name": "corePoolComptroller_", "type": "address" }, - { - "internalType": "address", - "name": "accessControlManager_", - "type": "address" - }, { "internalType": "bool", "name": "isIsolatedPool_", @@ -252,6 +247,19 @@ "name": "Initialized", "type": "event" }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "market", + "type": "address" + } + ], + "name": "MarketStateReset", + "type": "event" + }, { "anonymous": false, "inputs": [ @@ -380,6 +388,82 @@ "stateMutability": "view", "type": "function" }, + { + "inputs": [ + { + "internalType": "address", + "name": "market", + "type": "address" + }, + { + "internalType": "uint96", + "name": "poolId", + "type": "uint96" + } + ], + "name": "getMarketCFSnapshot", + "outputs": [ + { + "internalType": "uint256", + "name": "cf", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "lt", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "accessControlManager_", + "type": "address" + } + ], + "name": "initialize", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "name": "marketStates", + "outputs": [ + { + "internalType": "uint256", + "name": "borrowCap", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "supplyCap", + "type": "uint256" + }, + { + "internalType": "bool", + "name": "borrowCapSnapshotted", + "type": "bool" + }, + { + "internalType": "bool", + "name": "supplyCapSnapshotted", + "type": "bool" + } + ], + "stateMutability": "view", + "type": "function" + }, { "inputs": [], "name": "owner", @@ -490,6 +574,19 @@ "stateMutability": "nonpayable", "type": "function" }, + { + "inputs": [ + { + "internalType": "address", + "name": "market", + "type": "address" + } + ], + "name": "resetMarketState", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, { "inputs": [ { diff --git a/simulations/vip-661/abi/ERC20.json b/simulations/vip-661/abi/ERC20.json new file mode 100644 index 000000000..3a509c9c4 --- /dev/null +++ b/simulations/vip-661/abi/ERC20.json @@ -0,0 +1,134 @@ +[ + { + "inputs": [ + { "internalType": "string", "name": "name_", "type": "string" }, + { "internalType": "string", "name": "symbol_", "type": "string" }, + { "internalType": "uint8", "name": "decimals_", "type": "uint8" } + ], + "stateMutability": "nonpayable", + "type": "constructor" + }, + { + "anonymous": false, + "inputs": [ + { "indexed": true, "internalType": "address", "name": "owner", "type": "address" }, + { "indexed": true, "internalType": "address", "name": "spender", "type": "address" }, + { "indexed": false, "internalType": "uint256", "name": "value", "type": "uint256" } + ], + "name": "Approval", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { "indexed": true, "internalType": "address", "name": "from", "type": "address" }, + { "indexed": true, "internalType": "address", "name": "to", "type": "address" }, + { "indexed": false, "internalType": "uint256", "name": "value", "type": "uint256" } + ], + "name": "Transfer", + "type": "event" + }, + { + "inputs": [ + { "internalType": "address", "name": "owner", "type": "address" }, + { "internalType": "address", "name": "spender", "type": "address" } + ], + "name": "allowance", + "outputs": [{ "internalType": "uint256", "name": "", "type": "uint256" }], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { "internalType": "address", "name": "spender", "type": "address" }, + { "internalType": "uint256", "name": "amount", "type": "uint256" } + ], + "name": "approve", + "outputs": [{ "internalType": "bool", "name": "", "type": "bool" }], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [{ "internalType": "address", "name": "account", "type": "address" }], + "name": "balanceOf", + "outputs": [{ "internalType": "uint256", "name": "", "type": "uint256" }], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "decimals", + "outputs": [{ "internalType": "uint8", "name": "", "type": "uint8" }], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { "internalType": "address", "name": "spender", "type": "address" }, + { "internalType": "uint256", "name": "subtractedValue", "type": "uint256" } + ], + "name": "decreaseAllowance", + "outputs": [{ "internalType": "bool", "name": "", "type": "bool" }], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [{ "internalType": "uint256", "name": "amount", "type": "uint256" }], + "name": "faucet", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { "internalType": "address", "name": "spender", "type": "address" }, + { "internalType": "uint256", "name": "addedValue", "type": "uint256" } + ], + "name": "increaseAllowance", + "outputs": [{ "internalType": "bool", "name": "", "type": "bool" }], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "name", + "outputs": [{ "internalType": "string", "name": "", "type": "string" }], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "symbol", + "outputs": [{ "internalType": "string", "name": "", "type": "string" }], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "totalSupply", + "outputs": [{ "internalType": "uint256", "name": "", "type": "uint256" }], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { "internalType": "address", "name": "to", "type": "address" }, + { "internalType": "uint256", "name": "amount", "type": "uint256" } + ], + "name": "transfer", + "outputs": [{ "internalType": "bool", "name": "", "type": "bool" }], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { "internalType": "address", "name": "from", "type": "address" }, + { "internalType": "address", "name": "to", "type": "address" }, + { "internalType": "uint256", "name": "amount", "type": "uint256" } + ], + "name": "transferFrom", + "outputs": [{ "internalType": "bool", "name": "", "type": "bool" }], + "stateMutability": "nonpayable", + "type": "function" + } +] diff --git a/simulations/vip-661/abi/ResilientOracle.json b/simulations/vip-661/abi/ResilientOracle.json new file mode 100644 index 000000000..04fb2fe79 --- /dev/null +++ b/simulations/vip-661/abi/ResilientOracle.json @@ -0,0 +1,750 @@ +[ + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "address", + "name": "previousAdmin", + "type": "address" + }, + { + "indexed": false, + "internalType": "address", + "name": "newAdmin", + "type": "address" + } + ], + "name": "AdminChanged", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "beacon", + "type": "address" + } + ], + "name": "BeaconUpgraded", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "implementation", + "type": "address" + } + ], + "name": "Upgraded", + "type": "event" + }, + { + "stateMutability": "payable", + "type": "fallback" + }, + { + "inputs": [], + "name": "admin", + "outputs": [ + { + "internalType": "address", + "name": "admin_", + "type": "address" + } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "implementation", + "outputs": [ + { + "internalType": "address", + "name": "implementation_", + "type": "address" + } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "newImplementation", + "type": "address" + } + ], + "name": "upgradeTo", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "newImplementation", + "type": "address" + }, + { + "internalType": "bytes", + "name": "data", + "type": "bytes" + } + ], + "name": "upgradeToAndCall", + "outputs": [], + "stateMutability": "payable", + "type": "function" + }, + { + "stateMutability": "payable", + "type": "receive" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "sender", + "type": "address" + }, + { + "internalType": "address", + "name": "calledContract", + "type": "address" + }, + { + "internalType": "string", + "name": "methodSignature", + "type": "string" + } + ], + "name": "Unauthorized", + "type": "error" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "uint8", + "name": "version", + "type": "uint8" + } + ], + "name": "Initialized", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "address", + "name": "oldAccessControlManager", + "type": "address" + }, + { + "indexed": false, + "internalType": "address", + "name": "newAccessControlManager", + "type": "address" + } + ], + "name": "NewAccessControlManager", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "asset", + "type": "address" + }, + { + "indexed": true, + "internalType": "uint256", + "name": "role", + "type": "uint256" + }, + { + "indexed": true, + "internalType": "bool", + "name": "enable", + "type": "bool" + } + ], + "name": "OracleEnabled", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "asset", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "oracle", + "type": "address" + }, + { + "indexed": true, + "internalType": "uint256", + "name": "role", + "type": "uint256" + } + ], + "name": "OracleSet", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "previousOwner", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "newOwner", + "type": "address" + } + ], + "name": "OwnershipTransferStarted", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "previousOwner", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "newOwner", + "type": "address" + } + ], + "name": "OwnershipTransferred", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "address", + "name": "account", + "type": "address" + } + ], + "name": "Paused", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "asset", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "mainOracle", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "pivotOracle", + "type": "address" + }, + { + "indexed": false, + "internalType": "address", + "name": "fallbackOracle", + "type": "address" + } + ], + "name": "TokenConfigAdded", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "address", + "name": "account", + "type": "address" + } + ], + "name": "Unpaused", + "type": "event" + }, + { + "inputs": [], + "name": "BNB_ADDR", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "INVALID_PRICE", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "acceptOwnership", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "accessControlManager", + "outputs": [ + { + "internalType": "contract IAccessControlManagerV8", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "boundValidator", + "outputs": [ + { + "internalType": "contract BoundValidatorInterface", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "asset", + "type": "address" + }, + { + "internalType": "enum ResilientOracle.OracleRole", + "name": "role", + "type": "uint8" + }, + { + "internalType": "bool", + "name": "enable", + "type": "bool" + } + ], + "name": "enableOracle", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "asset", + "type": "address" + }, + { + "internalType": "enum ResilientOracle.OracleRole", + "name": "role", + "type": "uint8" + } + ], + "name": "getOracle", + "outputs": [ + { + "internalType": "address", + "name": "oracle", + "type": "address" + }, + { + "internalType": "bool", + "name": "enabled", + "type": "bool" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "asset", + "type": "address" + } + ], + "name": "getPrice", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "asset", + "type": "address" + } + ], + "name": "getTokenConfig", + "outputs": [ + { + "components": [ + { + "internalType": "address", + "name": "asset", + "type": "address" + }, + { + "internalType": "address[3]", + "name": "oracles", + "type": "address[3]" + }, + { + "internalType": "bool[3]", + "name": "enableFlagsForOracles", + "type": "bool[3]" + } + ], + "internalType": "struct ResilientOracle.TokenConfig", + "name": "", + "type": "tuple" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "vToken", + "type": "address" + } + ], + "name": "getUnderlyingPrice", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "accessControlManager_", + "type": "address" + } + ], + "name": "initialize", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "owner", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "pause", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "paused", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "pendingOwner", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "renounceOwnership", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "accessControlManager_", + "type": "address" + } + ], + "name": "setAccessControlManager", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "asset", + "type": "address" + }, + { + "internalType": "address", + "name": "oracle", + "type": "address" + }, + { + "internalType": "enum ResilientOracle.OracleRole", + "name": "role", + "type": "uint8" + } + ], + "name": "setOracle", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "components": [ + { + "internalType": "address", + "name": "asset", + "type": "address" + }, + { + "internalType": "address[3]", + "name": "oracles", + "type": "address[3]" + }, + { + "internalType": "bool[3]", + "name": "enableFlagsForOracles", + "type": "bool[3]" + } + ], + "internalType": "struct ResilientOracle.TokenConfig", + "name": "tokenConfig", + "type": "tuple" + } + ], + "name": "setTokenConfig", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "components": [ + { + "internalType": "address", + "name": "asset", + "type": "address" + }, + { + "internalType": "address[3]", + "name": "oracles", + "type": "address[3]" + }, + { + "internalType": "bool[3]", + "name": "enableFlagsForOracles", + "type": "bool[3]" + } + ], + "internalType": "struct ResilientOracle.TokenConfig[]", + "name": "tokenConfigs_", + "type": "tuple[]" + } + ], + "name": "setTokenConfigs", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "newOwner", + "type": "address" + } + ], + "name": "transferOwnership", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "unpause", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "asset", + "type": "address" + } + ], + "name": "updateAssetPrice", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "vToken", + "type": "address" + } + ], + "name": "updatePrice", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "vBnb", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "vai", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "_logic", + "type": "address" + }, + { + "internalType": "address", + "name": "admin_", + "type": "address" + }, + { + "internalType": "bytes", + "name": "_data", + "type": "bytes" + } + ], + "stateMutability": "payable", + "type": "constructor" + } +] diff --git a/simulations/vip-661/abi/comptroller.json b/simulations/vip-661/abi/comptroller.json new file mode 100644 index 000000000..f2cb3ea37 --- /dev/null +++ b/simulations/vip-661/abi/comptroller.json @@ -0,0 +1,3979 @@ +[ + { + "inputs": [], + "stateMutability": "nonpayable", + "type": "constructor" + }, + { + "inputs": [], + "name": "AlreadyInSelectedPool", + "type": "error" + }, + { + "inputs": [], + "name": "ArrayLengthMismatch", + "type": "error" + }, + { + "inputs": [], + "name": "BorrowNotAllowedInPool", + "type": "error" + }, + { + "inputs": [], + "name": "EmptyPoolLabel", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "uint96", + "name": "poolId", + "type": "uint96" + } + ], + "name": "InactivePool", + "type": "error" + }, + { + "inputs": [], + "name": "IncompatibleBorrowedAssets", + "type": "error" + }, + { + "inputs": [], + "name": "InvalidOperationForCorePool", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "enum WeightFunction", + "name": "strategy", + "type": "uint8" + } + ], + "name": "InvalidWeightingStrategy", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "errorCode", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "shortfall", + "type": "uint256" + } + ], + "name": "LiquidityCheckFailed", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "uint96", + "name": "poolId", + "type": "uint96" + }, + { + "internalType": "address", + "name": "vToken", + "type": "address" + } + ], + "name": "MarketAlreadyListed", + "type": "error" + }, + { + "inputs": [], + "name": "MarketConfigNotFound", + "type": "error" + }, + { + "inputs": [], + "name": "MarketNotListedInCorePool", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "uint96", + "name": "poolId", + "type": "uint96" + } + ], + "name": "PoolDoesNotExist", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "uint96", + "name": "poolId", + "type": "uint96" + }, + { + "internalType": "address", + "name": "vToken", + "type": "address" + } + ], + "name": "PoolMarketNotFound", + "type": "error" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "contract VToken", + "name": "vToken", + "type": "address" + }, + { + "indexed": true, + "internalType": "enum 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": "uint96", + "name": "poolId", + "type": "uint96" + }, + { + "indexed": true, + "internalType": "address", + "name": "market", + "type": "address" + }, + { + "indexed": false, + "internalType": "bool", + "name": "oldStatus", + "type": "bool" + }, + { + "indexed": false, + "internalType": "bool", + "name": "newStatus", + "type": "bool" + } + ], + "name": "BorrowAllowedUpdated", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "approver", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "delegate", + "type": "address" + }, + { + "indexed": false, + "internalType": "bool", + "name": "approved", + "type": "bool" + } + ], + "name": "DelegateUpdated", + "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": true, + "internalType": "address", + "name": "vToken", + "type": "address" + } + ], + "name": "MarketUnlisted", + "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": "uint96", + "name": "poolId", + "type": "uint96" + }, + { + "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": true, + "internalType": "uint96", + "name": "poolId", + "type": "uint96" + }, + { + "indexed": true, + "internalType": "address", + "name": "vToken", + "type": "address" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "oldLiquidationIncentiveMantissa", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "newLiquidationIncentiveMantissa", + "type": "uint256" + } + ], + "name": "NewLiquidationIncentive", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "uint96", + "name": "poolId", + "type": "uint96" + }, + { + "indexed": true, + "internalType": "contract VToken", + "name": "vToken", + "type": "address" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "oldLiquidationThresholdMantissa", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "newLiquidationThresholdMantissa", + "type": "uint256" + } + ], + "name": "NewLiquidationThreshold", + "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 ResilientOracleInterface", + "name": "oldPriceOracle", + "type": "address" + }, + { + "indexed": false, + "internalType": "contract ResilientOracleInterface", + "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": "address", + "name": "oldXVS", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "newXVS", + "type": "address" + } + ], + "name": "NewXVSToken", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "oldXVSVToken", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "newXVSVToken", + "type": "address" + } + ], + "name": "NewXVSVToken", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "uint96", + "name": "poolId", + "type": "uint96" + }, + { + "indexed": false, + "internalType": "bool", + "name": "oldStatus", + "type": "bool" + }, + { + "indexed": false, + "internalType": "bool", + "name": "newStatus", + "type": "bool" + } + ], + "name": "PoolActiveStatusUpdated", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "uint96", + "name": "poolId", + "type": "uint96" + }, + { + "indexed": false, + "internalType": "string", + "name": "label", + "type": "string" + } + ], + "name": "PoolCreated", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "uint96", + "name": "poolId", + "type": "uint96" + }, + { + "indexed": false, + "internalType": "bool", + "name": "oldStatus", + "type": "bool" + }, + { + "indexed": false, + "internalType": "bool", + "name": "newStatus", + "type": "bool" + } + ], + "name": "PoolFallbackStatusUpdated", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "uint96", + "name": "poolId", + "type": "uint96" + }, + { + "indexed": false, + "internalType": "string", + "name": "oldLabel", + "type": "string" + }, + { + "indexed": false, + "internalType": "string", + "name": "newLabel", + "type": "string" + } + ], + "name": "PoolLabelUpdated", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "uint96", + "name": "poolId", + "type": "uint96" + }, + { + "indexed": true, + "internalType": "address", + "name": "market", + "type": "address" + } + ], + "name": "PoolMarketInitialized", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "uint96", + "name": "poolId", + "type": "uint96" + }, + { + "indexed": true, + "internalType": "address", + "name": "vToken", + "type": "address" + } + ], + "name": "PoolMarketRemoved", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "account", + "type": "address" + }, + { + "indexed": false, + "internalType": "uint96", + "name": "previousPoolId", + "type": "uint96" + }, + { + "indexed": true, + "internalType": "uint96", + "name": "newPoolId", + "type": "uint96" + } + ], + "name": "PoolSelected", + "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": "address", + "name": "holder", + "type": "address" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "amount", + "type": "uint256" + } + ], + "name": "VenusSeized", + "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" + }, + { + "inputs": [ + { + "internalType": "contract Unitroller", + "name": "unitroller", + "type": "address" + } + ], + "name": "_become", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "recipient", + "type": "address" + }, + { + "internalType": "uint256", + "name": "amount", + "type": "uint256" + } + ], + "name": "_grantXVS", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "newAccessControlAddress", + "type": "address" + } + ], + "name": "_setAccessControl", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address[]", + "name": "markets_", + "type": "address[]" + }, + { + "internalType": "enum Action[]", + "name": "actions_", + "type": "uint8[]" + }, + { + "internalType": "bool", + "name": "paused_", + "type": "bool" + } + ], + "name": "_setActionsPaused", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "newCloseFactorMantissa", + "type": "uint256" + } + ], + "name": "_setCloseFactor", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "contract ComptrollerLensInterface", + "name": "comptrollerLens_", + "type": "address" + } + ], + "name": "_setComptrollerLens", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "vTokenBorrowed", + "type": "address" + }, + { + "internalType": "bool", + "name": "enable", + "type": "bool" + } + ], + "name": "_setForcedLiquidation", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "borrower", + "type": "address" + }, + { + "internalType": "address", + "name": "vTokenBorrowed", + "type": "address" + }, + { + "internalType": "bool", + "name": "enable", + "type": "bool" + } + ], + "name": "_setForcedLiquidationForUser", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "newLiquidatorContract_", + "type": "address" + } + ], + "name": "_setLiquidatorContract", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "contract VToken[]", + "name": "vTokens", + "type": "address[]" + }, + { + "internalType": "uint256[]", + "name": "newBorrowCaps", + "type": "uint256[]" + } + ], + "name": "_setMarketBorrowCaps", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "contract VToken[]", + "name": "vTokens", + "type": "address[]" + }, + { + "internalType": "uint256[]", + "name": "newSupplyCaps", + "type": "uint256[]" + } + ], + "name": "_setMarketSupplyCaps", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "newPauseGuardian", + "type": "address" + } + ], + "name": "_setPauseGuardian", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "contract ResilientOracleInterface", + "name": "newOracle", + "type": "address" + } + ], + "name": "_setPriceOracle", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "contract IPrime", + "name": "_prime", + "type": "address" + } + ], + "name": "_setPrimeToken", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "bool", + "name": "state", + "type": "bool" + } + ], + "name": "_setProtocolPaused", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "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" + } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "contract VAIControllerInterface", + "name": "vaiController_", + "type": "address" + } + ], + "name": "_setVAIController", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "newVAIMintRate", + "type": "uint256" + } + ], + "name": "_setVAIMintRate", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "vault_", + "type": "address" + }, + { + "internalType": "uint256", + "name": "releaseStartBlock_", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "minReleaseAmount_", + "type": "uint256" + } + ], + "name": "_setVAIVaultInfo", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "contract VToken[]", + "name": "vTokens", + "type": "address[]" + }, + { + "internalType": "uint256[]", + "name": "supplySpeeds", + "type": "uint256[]" + }, + { + "internalType": "uint256[]", + "name": "borrowSpeeds", + "type": "uint256[]" + } + ], + "name": "_setVenusSpeeds", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "venusVAIVaultRate_", + "type": "uint256" + } + ], + "name": "_setVenusVAIVaultRate", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "xvs_", + "type": "address" + } + ], + "name": "_setXVSToken", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "xvsVToken_", + "type": "address" + } + ], + "name": "_setXVSVToken", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "contract VToken", + "name": "vToken", + "type": "address" + } + ], + "name": "_supportMarket", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + }, + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "name": "accountAssets", + "outputs": [ + { + "internalType": "contract VToken", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "market", + "type": "address" + }, + { + "internalType": "enum Action", + "name": "action", + "type": "uint8" + } + ], + "name": "actionPaused", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint96[]", + "name": "poolIds", + "type": "uint96[]" + }, + { + "internalType": "address[]", + "name": "vTokens", + "type": "address[]" + } + ], + "name": "addPoolMarkets", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "admin", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "name": "allMarkets", + "outputs": [ + { + "internalType": "contract VToken", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + }, + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "name": "approvedDelegates", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "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" + } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "borrowCapGuardian", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "name": "borrowCaps", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "vToken", + "type": "address" + }, + { + "internalType": "address", + "name": "borrower", + "type": "address" + }, + { + "internalType": "uint256", + "name": "borrowAmount", + "type": "uint256" + } + ], + "name": "borrowVerify", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "account", + "type": "address" + }, + { + "internalType": "contract VToken", + "name": "vToken", + "type": "address" + } + ], + "name": "checkMembership", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "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": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "holder", + "type": "address" + }, + { + "internalType": "contract VToken[]", + "name": "vTokens", + "type": "address[]" + } + ], + "name": "claimVenus", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "holder", + "type": "address" + } + ], + "name": "claimVenus", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "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": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "holder", + "type": "address" + } + ], + "name": "claimVenusAsCollateral", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "closeFactorMantissa", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "comptrollerImplementation", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "comptrollerLens", + "outputs": [ + { + "internalType": "contract ComptrollerLensInterface", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "corePoolId", + "outputs": [ + { + "internalType": "uint96", + "name": "", + "type": "uint96" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "string", + "name": "label", + "type": "string" + } + ], + "name": "createPool", + "outputs": [ + { + "internalType": "uint96", + "name": "", + "type": "uint96" + } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address[]", + "name": "vTokens", + "type": "address[]" + } + ], + "name": "enterMarkets", + "outputs": [ + { + "internalType": "uint256[]", + "name": "", + "type": "uint256[]" + } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint96", + "name": "poolId", + "type": "uint96" + } + ], + "name": "enterPool", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "vTokenAddress", + "type": "address" + } + ], + "name": "exitMarket", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "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" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "getAllMarkets", + "outputs": [ + { + "internalType": "contract VToken[]", + "name": "", + "type": "address[]" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "account", + "type": "address" + } + ], + "name": "getAssetsIn", + "outputs": [ + { + "internalType": "contract VToken[]", + "name": "", + "type": "address[]" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "account", + "type": "address" + } + ], + "name": "getBorrowingPower", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "vToken", + "type": "address" + } + ], + "name": "getCollateralFactor", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "account", + "type": "address" + }, + { + "internalType": "address", + "name": "vToken", + "type": "address" + } + ], + "name": "getEffectiveLiquidationIncentive", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "account", + "type": "address" + }, + { + "internalType": "address", + "name": "vToken", + "type": "address" + }, + { + "internalType": "enum WeightFunction", + "name": "weightingStrategy", + "type": "uint8" + } + ], + "name": "getEffectiveLtvFactor", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "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" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "vToken", + "type": "address" + } + ], + "name": "getLiquidationIncentive", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "vToken", + "type": "address" + } + ], + "name": "getLiquidationThreshold", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint96", + "name": "poolId", + "type": "uint96" + }, + { + "internalType": "address", + "name": "vToken", + "type": "address" + } + ], + "name": "getPoolMarketIndex", + "outputs": [ + { + "internalType": "PoolMarketId", + "name": "", + "type": "bytes32" + } + ], + "stateMutability": "pure", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint96", + "name": "poolId", + "type": "uint96" + } + ], + "name": "getPoolVTokens", + "outputs": [ + { + "internalType": "address[]", + "name": "", + "type": "address[]" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "getXVSAddress", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "getXVSVTokenAddress", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "account", + "type": "address" + }, + { + "internalType": "uint96", + "name": "targetPoolId", + "type": "uint96" + } + ], + "name": "hasValidPoolBorrows", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "isComptroller", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "pure", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "name": "isForcedLiquidationEnabled", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "user", + "type": "address" + }, + { + "internalType": "address", + "name": "market", + "type": "address" + } + ], + "name": "isForcedLiquidationEnabledForUser", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "contract VToken", + "name": "vToken", + "type": "address" + } + ], + "name": "isMarketListed", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "lastPoolId", + "outputs": [ + { + "internalType": "uint96", + "name": "", + "type": "uint96" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "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" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "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": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "borrower", + "type": "address" + }, + { + "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" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "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" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "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" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "liquidatorContract", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "vToken", + "type": "address" + } + ], + "name": "markets", + "outputs": [ + { + "internalType": "bool", + "name": "isListed", + "type": "bool" + }, + { + "internalType": "uint256", + "name": "collateralFactorMantissa", + "type": "uint256" + }, + { + "internalType": "bool", + "name": "isVenus", + "type": "bool" + }, + { + "internalType": "uint256", + "name": "liquidationThresholdMantissa", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "liquidationIncentiveMantissa", + "type": "uint256" + }, + { + "internalType": "uint96", + "name": "marketPoolId", + "type": "uint96" + }, + { + "internalType": "bool", + "name": "isBorrowAllowed", + "type": "bool" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "maxAssets", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "minReleaseAmount", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "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" + } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "mintVAIGuardianPaused", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "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": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "name": "mintedVAIs", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "oracle", + "outputs": [ + { + "internalType": "contract ResilientOracleInterface", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "pauseGuardian", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "pendingAdmin", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "pendingComptrollerImplementation", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint96", + "name": "poolId", + "type": "uint96" + }, + { + "internalType": "address", + "name": "vToken", + "type": "address" + } + ], + "name": "poolMarkets", + "outputs": [ + { + "internalType": "bool", + "name": "isListed", + "type": "bool" + }, + { + "internalType": "uint256", + "name": "collateralFactorMantissa", + "type": "uint256" + }, + { + "internalType": "bool", + "name": "isVenus", + "type": "bool" + }, + { + "internalType": "uint256", + "name": "liquidationThresholdMantissa", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "liquidationIncentiveMantissa", + "type": "uint256" + }, + { + "internalType": "uint96", + "name": "marketPoolId", + "type": "uint96" + }, + { + "internalType": "bool", + "name": "isBorrowAllowed", + "type": "bool" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint96", + "name": "", + "type": "uint96" + } + ], + "name": "pools", + "outputs": [ + { + "internalType": "string", + "name": "label", + "type": "string" + }, + { + "internalType": "bool", + "name": "isActive", + "type": "bool" + }, + { + "internalType": "bool", + "name": "allowCorePoolFallback", + "type": "bool" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "prime", + "outputs": [ + { + "internalType": "contract IPrime", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "protocolPaused", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "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" + } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "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": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "releaseStartBlock", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint96", + "name": "poolId", + "type": "uint96" + }, + { + "internalType": "address", + "name": "vToken", + "type": "address" + } + ], + "name": "removePoolMarket", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "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" + } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "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": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "repayVAIGuardianPaused", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "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" + } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address[]", + "name": "holders", + "type": "address[]" + }, + { + "internalType": "address", + "name": "recipient", + "type": "address" + } + ], + "name": "seizeVenus", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "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": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address[]", + "name": "markets_", + "type": "address[]" + }, + { + "internalType": "enum Action[]", + "name": "actions_", + "type": "uint8[]" + }, + { + "internalType": "bool", + "name": "paused_", + "type": "bool" + } + ], + "name": "setActionsPaused", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint96", + "name": "poolId", + "type": "uint96" + }, + { + "internalType": "bool", + "name": "allowFallback", + "type": "bool" + } + ], + "name": "setAllowCorePoolFallback", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "newCloseFactorMantissa", + "type": "uint256" + } + ], + "name": "setCloseFactor", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "contract VToken", + "name": "vToken", + "type": "address" + }, + { + "internalType": "uint256", + "name": "newCollateralFactorMantissa", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "newLiquidationThresholdMantissa", + "type": "uint256" + } + ], + "name": "setCollateralFactor", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint96", + "name": "poolId", + "type": "uint96" + }, + { + "internalType": "contract VToken", + "name": "vToken", + "type": "address" + }, + { + "internalType": "uint256", + "name": "newCollateralFactorMantissa", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "newLiquidationThresholdMantissa", + "type": "uint256" + } + ], + "name": "setCollateralFactor", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "vTokenBorrowed", + "type": "address" + }, + { + "internalType": "bool", + "name": "enable", + "type": "bool" + } + ], + "name": "setForcedLiquidation", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint96", + "name": "poolId", + "type": "uint96" + }, + { + "internalType": "address", + "name": "vToken", + "type": "address" + }, + { + "internalType": "bool", + "name": "borrowAllowed", + "type": "bool" + } + ], + "name": "setIsBorrowAllowed", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint96", + "name": "poolId", + "type": "uint96" + }, + { + "internalType": "address", + "name": "vToken", + "type": "address" + }, + { + "internalType": "uint256", + "name": "newLiquidationIncentiveMantissa", + "type": "uint256" + } + ], + "name": "setLiquidationIncentive", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "vToken", + "type": "address" + }, + { + "internalType": "uint256", + "name": "newLiquidationIncentiveMantissa", + "type": "uint256" + } + ], + "name": "setLiquidationIncentive", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "contract VToken[]", + "name": "vTokens", + "type": "address[]" + }, + { + "internalType": "uint256[]", + "name": "newBorrowCaps", + "type": "uint256[]" + } + ], + "name": "setMarketBorrowCaps", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "contract VToken[]", + "name": "vTokens", + "type": "address[]" + }, + { + "internalType": "uint256[]", + "name": "newSupplyCaps", + "type": "uint256[]" + } + ], + "name": "setMarketSupplyCaps", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "owner", + "type": "address" + }, + { + "internalType": "uint256", + "name": "amount", + "type": "uint256" + } + ], + "name": "setMintedVAIOf", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint96", + "name": "poolId", + "type": "uint96" + }, + { + "internalType": "bool", + "name": "active", + "type": "bool" + } + ], + "name": "setPoolActive", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint96", + "name": "poolId", + "type": "uint96" + }, + { + "internalType": "string", + "name": "newLabel", + "type": "string" + } + ], + "name": "setPoolLabel", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "contract ResilientOracleInterface", + "name": "newOracle", + "type": "address" + } + ], + "name": "setPriceOracle", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "contract IPrime", + "name": "_prime", + "type": "address" + } + ], + "name": "setPrimeToken", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "name": "supplyCaps", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "contract VToken", + "name": "vToken", + "type": "address" + } + ], + "name": "supportMarket", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "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" + } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "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": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "treasuryAddress", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "treasuryGuardian", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "treasuryPercent", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "market", + "type": "address" + } + ], + "name": "unlistMarket", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "delegate", + "type": "address" + }, + { + "internalType": "bool", + "name": "approved", + "type": "bool" + } + ], + "name": "updateDelegate", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "name": "userPoolId", + "outputs": [ + { + "internalType": "uint96", + "name": "", + "type": "uint96" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "vaiController", + "outputs": [ + { + "internalType": "contract VAIControllerInterface", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "vaiMintRate", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "vaiVaultAddress", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "name": "venusAccrued", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "name": "venusBorrowSpeeds", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "name": "venusBorrowState", + "outputs": [ + { + "internalType": "uint224", + "name": "index", + "type": "uint224" + }, + { + "internalType": "uint32", + "name": "block", + "type": "uint32" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + }, + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "name": "venusBorrowerIndex", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "venusInitialIndex", + "outputs": [ + { + "internalType": "uint224", + "name": "", + "type": "uint224" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + }, + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "name": "venusSupplierIndex", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "name": "venusSupplySpeeds", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "name": "venusSupplyState", + "outputs": [ + { + "internalType": "uint224", + "name": "index", + "type": "uint224" + }, + { + "internalType": "uint32", + "name": "block", + "type": "uint32" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "venusVAIVaultRate", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + } +] diff --git a/simulations/vip-661/bsctestnet-addendum.ts b/simulations/vip-661/bsctestnet-addendum.ts new file mode 100644 index 000000000..f1597bfc7 --- /dev/null +++ b/simulations/vip-661/bsctestnet-addendum.ts @@ -0,0 +1,166 @@ +import { expect } from "chai"; +import { Contract } from "ethers"; +import { ethers } from "hardhat"; +import { NETWORK_ADDRESSES } from "src/networkAddresses"; +import { expectEvents, setMaxStalePeriod } from "src/utils"; +import { forking, testVip } from "src/vip-framework"; + +import { + ACM, + CF, + COMPTROLLER, + EBRAKE, + KEEPER_ADDRESS, + LT, + VETH_CORE, + vip661TestnetAddendum, +} from "../../vips/vip-661/bsctestnet-addendum"; +import ACCESS_CONTROL_MANAGER_ABI from "./abi/AccessControlManager.json"; +import EBRAKE_ABI from "./abi/EBrake.json"; +import ERC20_ABI from "./abi/ERC20.json"; +import RESILIENT_ORACLE_ABI from "./abi/ResilientOracle.json"; +import COMPTROLLER_ABI from "./abi/comptroller.json"; + +const Action = { + MINT: 0, +}; + +const ETH = "0x98f7A83361F7Ac8765CcEBAB1425da6b341958a7"; + +forking(100083240, async () => { + let comptroller: Contract; + let eBrake: Contract; + let accessControlManager: Contract; + let resilientOracle: Contract; + let eth: Contract; + + before(async () => { + comptroller = await ethers.getContractAt(COMPTROLLER_ABI, COMPTROLLER); + eBrake = await ethers.getContractAt(EBRAKE_ABI, EBRAKE); + accessControlManager = await ethers.getContractAt(ACCESS_CONTROL_MANAGER_ABI, ACM); + resilientOracle = await ethers.getContractAt( + RESILIENT_ORACLE_ABI, + NETWORK_ADDRESSES["bsctestnet"].RESILIENT_ORACLE, + ); + eth = await ethers.getContractAt(ERC20_ABI, ETH); + + await setMaxStalePeriod(resilientOracle, eth, 7 * 24 * 60 * 60); + }); + + describe("Pre-VIP behavior", () => { + it("vETH_Core should have collateral factor of 0", async () => { + const market = await comptroller.markets(VETH_CORE); + expect(market.collateralFactorMantissa).to.equal(0); + }); + + it("vETH_Core mint should be paused", async () => { + const paused = await comptroller.actionPaused(VETH_CORE, Action.MINT); + expect(paused).to.equal(true); + }); + + it("EBrake should have non-zero snapshot for vETH_Core", async () => { + const snapshot = await eBrake.getMarketCFSnapshot(VETH_CORE, 0); + expect(snapshot.cf).to.equal(CF); + expect(snapshot.lt).to.equal(LT); + }); + + it("Normal Timelock should not have resetMarketState permission on EBrake", async () => { + expect( + await accessControlManager.hasPermission( + NETWORK_ADDRESSES.bsctestnet.NORMAL_TIMELOCK, + EBRAKE, + "resetMarketState(address)", + ), + ).to.equal(false); + }); + + it("Keeper should not have _setActionsPaused permission on comptrollers", async () => { + expect( + await accessControlManager.hasPermission( + KEEPER_ADDRESS, + ethers.constants.AddressZero, + "_setActionsPaused(address[],uint8[],bool)", + ), + ).to.equal(false); + }); + + it("Keeper should not have setCollateralFactor permission on comptrollers", async () => { + expect( + await accessControlManager.hasPermission( + KEEPER_ADDRESS, + ethers.constants.AddressZero, + "setCollateralFactor(address,uint256,uint256)", + ), + ).to.equal(false); + }); + + it("Keeper should not have resetMarketState permission on EBrake", async () => { + expect(await accessControlManager.hasPermission(KEEPER_ADDRESS, EBRAKE, "resetMarketState(address)")).to.equal( + false, + ); + }); + }); + + testVip("VIP-661 Addendum: Recover vETH_Core after EBrake Incident", await vip661TestnetAddendum(), { + callbackAfterExecution: async txResponse => { + await expectEvents(txResponse, [COMPTROLLER_ABI], ["ActionPausedMarket", "NewCollateralFactor"], [1, 1]); + await expectEvents(txResponse, [EBRAKE_ABI], ["MarketStateReset"], [1]); + await expectEvents(txResponse, [ACCESS_CONTROL_MANAGER_ABI], ["PermissionGranted"], [4]); + }, + }); + + describe("Post-VIP behavior", () => { + it("vETH_Core should have restored collateral factor", async () => { + const market = await comptroller.markets(VETH_CORE); + expect(market.collateralFactorMantissa).to.equal(CF); + expect(market.liquidationThresholdMantissa).to.equal(LT); + }); + + it("vETH_Core mint should be unpaused", async () => { + const paused = await comptroller.actionPaused(VETH_CORE, Action.MINT); + expect(paused).to.equal(false); + }); + + it("EBrake snapshot for vETH_Core should be cleared", async () => { + const snapshot = await eBrake.getMarketCFSnapshot(VETH_CORE, 0); + expect(snapshot.cf).to.equal(0); + expect(snapshot.lt).to.equal(0); + }); + + it("Normal Timelock should have resetMarketState permission on EBrake", async () => { + expect( + await accessControlManager.hasPermission( + NETWORK_ADDRESSES.bsctestnet.NORMAL_TIMELOCK, + EBRAKE, + "resetMarketState(address)", + ), + ).to.equal(true); + }); + + it("Keeper should have _setActionsPaused permission on comptrollers", async () => { + expect( + await accessControlManager.hasPermission( + KEEPER_ADDRESS, + ethers.constants.AddressZero, + "_setActionsPaused(address[],uint8[],bool)", + ), + ).to.equal(true); + }); + + it("Keeper should have setCollateralFactor permission on comptrollers", async () => { + expect( + await accessControlManager.hasPermission( + KEEPER_ADDRESS, + ethers.constants.AddressZero, + "setCollateralFactor(address,uint256,uint256)", + ), + ).to.equal(true); + }); + + it("Keeper should have resetMarketState permission on EBrake", async () => { + expect(await accessControlManager.hasPermission(KEEPER_ADDRESS, EBRAKE, "resetMarketState(address)")).to.equal( + true, + ); + }); + }); +}); diff --git a/vips/vip-661/bsctestnet-addendum.ts b/vips/vip-661/bsctestnet-addendum.ts new file mode 100644 index 000000000..9574824ed --- /dev/null +++ b/vips/vip-661/bsctestnet-addendum.ts @@ -0,0 +1,98 @@ +import { ethers } from "hardhat"; +import { NETWORK_ADDRESSES } from "src/networkAddresses"; +import { ProposalType } from "src/types"; +import { makeProposal } from "src/utils"; + +export const EBRAKE = "0x957c09e3Ac3d9e689244DC74307c94111FBa8B42"; +export const COMPTROLLER = "0x94d1820b2D1c7c7452A163983Dc888CEC546b77D"; +export const VETH_CORE = "0x162D005F0Fff510E54958Cfc5CF32A3180A84aab"; +export const KEEPER_ADDRESS = "0x2Ce1d0ffD7E869D9DF33e28552b12DdDed326706"; +export const ACM = "0x45f8a08F534f34A97187626E05d4b6648Eeaa9AA"; + +export const CF = "800000000000000000"; +export const LT = "800000000000000000"; + +const Action = { + MINT: 0, +}; + +export const vip661TestnetAddendum = () => { + const meta = { + version: "v2", + title: "VIP-661 Addendum: Recover vETH_Core after EBrake Incident on BSC Testnet", + description: `#### Summary + +Recover the vETH_Core market on BSC Testnet after EBrake tightened it during DeviationSentinel testing. The price deviation has resolved (5% < 10% threshold), so this VIP restores the market to normal operating state and clears EBrake's stored snapshot. + +This VIP also grants the keeper address permissions on the comptroller for direct pause/CF management on testnet, so future testing iterations don't require a governance VIP. + +#### Actions + +1. Grant Normal Timelock permission to call \`resetMarketState\` on EBrake (required for step 4) +2. Restore vETH_Core collateral factor to 0.8e18 / 0.8e18 (snapshot values from EBrake) +3. Unpause MINT (supply) on vETH_Core +4. Clear EBrake's stored snapshot for vETH_Core via \`resetMarketState\` +5. Grant keeper address permission to call \`_setActionsPaused\` on comptrollers (testnet convenience) +6. Grant keeper address permission to call \`setCollateralFactor\` on comptrollers (testnet convenience)`, + forDescription: "Execute this proposal", + againstDescription: "Do not execute this proposal", + abstainDescription: "Indifferent to execution", + }; + + return makeProposal( + [ + // 1. Grant Normal Timelock permission to call resetMarketState on EBrake + { + target: ACM, + signature: "giveCallPermission(address,string,address)", + params: [EBRAKE, "resetMarketState(address)", NETWORK_ADDRESSES.bsctestnet.NORMAL_TIMELOCK], + }, + + // 2. Restore vETH_Core collateral factor on the comptroller + { + target: COMPTROLLER, + signature: "setCollateralFactor(address,uint256,uint256)", + params: [VETH_CORE, CF, LT], + }, + + // 3. Unpause MINT (supply) on vETH_Core + { + target: COMPTROLLER, + signature: "_setActionsPaused(address[],uint8[],bool)", + params: [[VETH_CORE], [Action.MINT], false], + }, + + // 4. Clear EBrake's stored snapshot for vETH_Core + { + target: EBRAKE, + signature: "resetMarketState(address)", + params: [VETH_CORE], + }, + + // 5. Grant keeper permission to pause/unpause actions on comptrollers (testnet only) + { + target: ACM, + signature: "giveCallPermission(address,string,address)", + params: [ethers.constants.AddressZero, "_setActionsPaused(address[],uint8[],bool)", KEEPER_ADDRESS], + }, + + // 6. Grant keeper permission to set collateral factor on comptrollers (testnet only) + { + target: ACM, + signature: "giveCallPermission(address,string,address)", + params: [ethers.constants.AddressZero, "setCollateralFactor(address,uint256,uint256)", KEEPER_ADDRESS], + }, + + // 7. Grant keeper permission to reset EBrake market state (testnet convenience — enables repeatable E2E testing) + { + target: ACM, + signature: "giveCallPermission(address,string,address)", + params: [EBRAKE, "resetMarketState(address)", KEEPER_ADDRESS], + }, + ], + meta, + ProposalType.REGULAR, + ); +}; + +export default vip661TestnetAddendum; From 4542f0fb6539a5b939562a64114f16e7782a13fc Mon Sep 17 00:00:00 2001 From: Debugger022 Date: Tue, 7 Apr 2026 13:27:19 +0530 Subject: [PATCH 05/11] fix: add comment to KEEPER_ADDRESS for clarity on bsctestnet Guardian role --- vips/vip-661/bsctestnet-addendum.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/vips/vip-661/bsctestnet-addendum.ts b/vips/vip-661/bsctestnet-addendum.ts index 9574824ed..c074385ee 100644 --- a/vips/vip-661/bsctestnet-addendum.ts +++ b/vips/vip-661/bsctestnet-addendum.ts @@ -6,7 +6,7 @@ import { makeProposal } from "src/utils"; export const EBRAKE = "0x957c09e3Ac3d9e689244DC74307c94111FBa8B42"; export const COMPTROLLER = "0x94d1820b2D1c7c7452A163983Dc888CEC546b77D"; export const VETH_CORE = "0x162D005F0Fff510E54958Cfc5CF32A3180A84aab"; -export const KEEPER_ADDRESS = "0x2Ce1d0ffD7E869D9DF33e28552b12DdDed326706"; +export const KEEPER_ADDRESS = "0x2Ce1d0ffD7E869D9DF33e28552b12DdDed326706"; // bsctestnet Guardian address export const ACM = "0x45f8a08F534f34A97187626E05d4b6648Eeaa9AA"; export const CF = "800000000000000000"; From 8d5c4af28a725e189e2110fac136f9f56613efe7 Mon Sep 17 00:00:00 2001 From: Debugger022 Date: Tue, 7 Apr 2026 13:32:41 +0530 Subject: [PATCH 06/11] feat: add VIP-661 addendum 2 to recover vETH_Core on BSC testnet MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - adds minimal 3-action VIP: restore CF/LT, unpause MINT, clear EBrake snapshot - no permission grants needed — established by bsctestnet.ts (Guardian/EBrake perms) and addendum 1 (Normal Timelock → resetMarketState) - simulation forks from block 100233050 (post-addendum-1, second incident active via Tenderly web action) - serves as reference for minimal governance footprint to recover any future EBrake-tightened market --- simulations/vip-661/bsctestnet-addendum-2.ts | 93 ++++++++++++++++++++ vips/vip-661/bsctestnet-addendum-2.ts | 64 ++++++++++++++ 2 files changed, 157 insertions(+) create mode 100644 simulations/vip-661/bsctestnet-addendum-2.ts create mode 100644 vips/vip-661/bsctestnet-addendum-2.ts diff --git a/simulations/vip-661/bsctestnet-addendum-2.ts b/simulations/vip-661/bsctestnet-addendum-2.ts new file mode 100644 index 000000000..9c0589895 --- /dev/null +++ b/simulations/vip-661/bsctestnet-addendum-2.ts @@ -0,0 +1,93 @@ +import { expect } from "chai"; +import { Contract } from "ethers"; +import { ethers } from "hardhat"; +import { NETWORK_ADDRESSES } from "src/networkAddresses"; +import { expectEvents, setMaxStalePeriod } from "src/utils"; +import { forking, testVip } from "src/vip-framework"; + +import { + CF, + COMPTROLLER, + EBRAKE, + LT, + VETH_CORE, + vip661TestnetAddendum2, +} from "../../vips/vip-661/bsctestnet-addendum-2"; +import EBRAKE_ABI from "./abi/EBrake.json"; +import ERC20_ABI from "./abi/ERC20.json"; +import RESILIENT_ORACLE_ABI from "./abi/ResilientOracle.json"; +import COMPTROLLER_ABI from "./abi/comptroller.json"; + +const Action = { + MINT: 0, +}; + +const ETH = "0x98f7A83361F7Ac8765CcEBAB1425da6b341958a7"; + +// Fork from block 100233050 — after VIP-661 Addendum 1 was executed on BSC Testnet and all +// guardian(keeper)/timelock permissions were established. A Tenderly web action subsequently re-triggered +// the DeviationSentinel, causing EBrake to zero vETH_Core's CF and pause MINT again. This +// simulation validates that addendum-2 (the three minimal recovery actions) fully restores +// the market from that second incident. +forking(100233050, async () => { + let comptroller: Contract; + let eBrake: Contract; + let resilientOracle: Contract; + let eth: Contract; + + before(async () => { + comptroller = await ethers.getContractAt(COMPTROLLER_ABI, COMPTROLLER); + eBrake = await ethers.getContractAt(EBRAKE_ABI, EBRAKE); + resilientOracle = await ethers.getContractAt( + RESILIENT_ORACLE_ABI, + NETWORK_ADDRESSES["bsctestnet"].RESILIENT_ORACLE, + ); + eth = await ethers.getContractAt(ERC20_ABI, ETH); + + await setMaxStalePeriod(resilientOracle, eth, 7 * 24 * 60 * 60); + }); + + describe("Pre-VIP behavior", () => { + it("vETH_Core should have collateral factor of 0 (tightened by EBrake)", async () => { + const market = await comptroller.markets(VETH_CORE); + expect(market.collateralFactorMantissa).to.equal(0); + }); + + it("vETH_Core mint should be paused (tightened by EBrake)", async () => { + const paused = await comptroller.actionPaused(VETH_CORE, Action.MINT); + expect(paused).to.equal(true); + }); + + it("EBrake should have non-zero snapshot for vETH_Core", async () => { + const snapshot = await eBrake.getMarketCFSnapshot(VETH_CORE, 0); + expect(snapshot.cf).to.equal(CF); + expect(snapshot.lt).to.equal(LT); + }); + }); + + testVip("VIP-661 Addendum 2: Minimal Recovery of vETH_Core", await vip661TestnetAddendum2(), { + callbackAfterExecution: async txResponse => { + await expectEvents(txResponse, [COMPTROLLER_ABI], ["ActionPausedMarket", "NewCollateralFactor"], [1, 1]); + await expectEvents(txResponse, [EBRAKE_ABI], ["MarketStateReset"], [1]); + }, + }); + + describe("Post-VIP behavior", () => { + it("vETH_Core should have restored collateral factor", async () => { + const market = await comptroller.markets(VETH_CORE); + expect(market.collateralFactorMantissa).to.equal(CF); + expect(market.liquidationThresholdMantissa).to.equal(LT); + }); + + it("vETH_Core mint should be unpaused", async () => { + const paused = await comptroller.actionPaused(VETH_CORE, Action.MINT); + expect(paused).to.equal(false); + }); + + it("EBrake snapshot for vETH_Core should be cleared", async () => { + const snapshot = await eBrake.getMarketCFSnapshot(VETH_CORE, 0); + expect(snapshot.cf).to.equal(0); + expect(snapshot.lt).to.equal(0); + }); + }); +}); diff --git a/vips/vip-661/bsctestnet-addendum-2.ts b/vips/vip-661/bsctestnet-addendum-2.ts new file mode 100644 index 000000000..62fac7bf8 --- /dev/null +++ b/vips/vip-661/bsctestnet-addendum-2.ts @@ -0,0 +1,64 @@ +import { ProposalType } from "src/types"; +import { makeProposal } from "src/utils"; + +export const EBRAKE = "0x957c09e3Ac3d9e689244DC74307c94111FBa8B42"; +export const COMPTROLLER = "0x94d1820b2D1c7c7452A163983Dc888CEC546b77D"; +export const VETH_CORE = "0x162D005F0Fff510E54958Cfc5CF32A3180A84aab"; + +export const CF = "800000000000000000"; +export const LT = "800000000000000000"; + +const Action = { + MINT: 0, +}; + +export const vip661TestnetAddendum2 = () => { + const meta = { + version: "v2", + title: "VIP-661 Addendum 2: Minimal Recovery of vETH_Core after EBrake Incident on BSC Testnet", + description: `#### Summary + +Recover the vETH_Core market on BSC Testnet after EBrake tightened it a second time during DeviationSentinel testing. +VIP-661 Addendum 1 already established all required permissions (Normal Timelock → \`resetMarketState\`, Guardian +convenience grants), so this VIP contains only the three actions that are strictly necessary to restore the market. +This serves as a reference for the minimal governance footprint needed to recover from any future EBrake incident. + +#### Actions + +1. Restore vETH_Core collateral factor to 0.8e18 / 0.8e18 (values stored in EBrake snapshot) +2. Unpause MINT (supply) on vETH_Core +3. Clear EBrake's stored snapshot for vETH_Core via \`resetMarketState\``, + forDescription: "Execute this proposal", + againstDescription: "Do not execute this proposal", + abstainDescription: "Indifferent to execution", + }; + + return makeProposal( + [ + // 1. Restore vETH_Core collateral factor on the comptroller + { + target: COMPTROLLER, + signature: "setCollateralFactor(address,uint256,uint256)", + params: [VETH_CORE, CF, LT], + }, + + // 2. Unpause MINT (supply) on vETH_Core + { + target: COMPTROLLER, + signature: "_setActionsPaused(address[],uint8[],bool)", + params: [[VETH_CORE], [Action.MINT], false], + }, + + // 3. Clear EBrake's stored snapshot for vETH_Core + { + target: EBRAKE, + signature: "resetMarketState(address)", + params: [VETH_CORE], + }, + ], + meta, + ProposalType.REGULAR, + ); +}; + +export default vip661TestnetAddendum2; From 094f90b791a027e74fd19d56f30d9a93b8f912e2 Mon Sep 17 00:00:00 2001 From: Debugger022 Date: Tue, 7 Apr 2026 15:42:41 +0530 Subject: [PATCH 07/11] feat: add bscmainnet VIP and simulation for EBrake-integrated DeviationSentinel - upgrades DeviationSentinel proxy to EBrake-integrated implementation - grants EBrake permissions on Core Pool Comptroller (_setActionsPaused, setCollateralFactor, _setMarketBorrowCaps, _setMarketSupplyCaps, setFlashLoanPaused) - grants Guardian and all three timelocks resetMarketState permission on EBrake - grants DeviationSentinel pauseBorrow, pauseSupply, and setCFZero permissions on EBrake - revokes DeviationSentinel's old direct comptroller permissions from VIP-590 - simulation uses isAllowedToCall pattern (BSC mainnet ACM) with impersonated host contracts - EBRAKE and NEW_DEVIATION_SENTINEL_IMPL marked as TODO pending deployment --- simulations/vip-661/bscmainnet.ts | 158 ++++++++++++++++++++++++++ vips/vip-661/bscmainnet.ts | 181 ++++++++++++++++++++++++++++++ 2 files changed, 339 insertions(+) create mode 100644 simulations/vip-661/bscmainnet.ts create mode 100644 vips/vip-661/bscmainnet.ts diff --git a/simulations/vip-661/bscmainnet.ts b/simulations/vip-661/bscmainnet.ts new file mode 100644 index 000000000..fb3230583 --- /dev/null +++ b/simulations/vip-661/bscmainnet.ts @@ -0,0 +1,158 @@ +import { SignerWithAddress } from "@nomiclabs/hardhat-ethers/signers"; +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 vip661, { + ACM, + CORE_POOL_COMPTROLLER, + DEVIATION_SENTINEL, + EBRAKE, + NEW_DEVIATION_SENTINEL_IMPL, + PROXY_ADMIN, +} from "../../vips/vip-661/bscmainnet"; +import ACCESS_CONTROL_MANAGER_ABI from "./abi/AccessControlManager.json"; +import DEVIATION_SENTINEL_ABI from "./abi/DeviationSentinel.json"; +import EBRAKE_ABI from "./abi/EBrake.json"; +import PROXY_ADMIN_ABI from "./abi/ProxyAdmin.json"; + +const { NORMAL_TIMELOCK, FAST_TRACK_TIMELOCK, CRITICAL_TIMELOCK, GUARDIAN } = NETWORK_ADDRESSES.bscmainnet; +const TIMELOCKS = [NORMAL_TIMELOCK, FAST_TRACK_TIMELOCK, CRITICAL_TIMELOCK]; + +// Permissions EBrake needs on the Core Pool Comptroller (host = comptroller) +const EBRAKE_COMPTROLLER_PERMS = [ + "_setActionsPaused(address[],uint8[],bool)", + "setCollateralFactor(uint96,address,uint256,uint256)", + "_setMarketBorrowCaps(address[],uint256[])", + "_setMarketSupplyCaps(address[],uint256[])", + "setFlashLoanPaused(bool)", +]; + +// Permissions DeviationSentinel will have on EBrake (host = EBrake) +const SENTINEL_EBRAKE_PERMS = ["pauseBorrow(address)", "pauseSupply(address)", "setCFZero(address)"]; + +// Permissions DeviationSentinel had directly on the comptroller in VIP-590 and that this VIP revokes +const SENTINEL_COMPTROLLER_PERMS_TO_REVOKE = [ + "setActionsPaused(address[],uint8[],bool)", + "_setActionsPaused(address[],uint8[],bool)", + "setCollateralFactor(uint96,address,uint256,uint256)", +]; + +forking(91124514, async () => { + let accessControlManager: Contract; + let deviationSentinel: Contract; + let proxyAdmin: Contract; + + // Impersonated host contracts — BSC mainnet ACM exposes isAllowedToCall(account, sig) + // where the host contract MUST be msg.sender. So we impersonate the host and call from it. + let impersonatedComptroller: SignerWithAddress; + let impersonatedEBrake: SignerWithAddress; + + before(async () => { + accessControlManager = await ethers.getContractAt(ACCESS_CONTROL_MANAGER_ABI, ACM); + deviationSentinel = await ethers.getContractAt(DEVIATION_SENTINEL_ABI, DEVIATION_SENTINEL); + proxyAdmin = await ethers.getContractAt(PROXY_ADMIN_ABI, PROXY_ADMIN); + + impersonatedComptroller = await initMainnetUser(CORE_POOL_COMPTROLLER, ethers.utils.parseEther("1")); + impersonatedEBrake = await initMainnetUser(EBRAKE, ethers.utils.parseEther("1")); + }); + + describe("Pre-VIP behavior", () => { + it("DeviationSentinel proxy implementation should not yet be the new EBrake-integrated one", async () => { + expect(await proxyAdmin.getProxyImplementation(DEVIATION_SENTINEL)).to.not.equal(NEW_DEVIATION_SENTINEL_IMPL); + }); + + it("EBrake should not yet have any permissions on the Core Pool Comptroller", async () => { + const acm = accessControlManager.connect(impersonatedComptroller); + for (const sig of EBRAKE_COMPTROLLER_PERMS) { + expect(await acm.isAllowedToCall(EBRAKE, sig)).to.equal(false); + } + }); + + it("Guardian and governance timelocks should not yet have resetMarketState permission on EBrake", async () => { + const acm = accessControlManager.connect(impersonatedEBrake); + for (const account of [GUARDIAN, ...TIMELOCKS]) { + expect(await acm.isAllowedToCall(account, "resetMarketState(address)")).to.equal(false); + } + }); + + it("DeviationSentinel should not yet have any permissions on EBrake", async () => { + const acm = accessControlManager.connect(impersonatedEBrake); + for (const sig of SENTINEL_EBRAKE_PERMS) { + expect(await acm.isAllowedToCall(DEVIATION_SENTINEL, sig)).to.equal(false); + } + }); + + it("DeviationSentinel should still have direct comptroller permissions from VIP-590", async () => { + const acm = accessControlManager.connect(impersonatedComptroller); + for (const sig of SENTINEL_COMPTROLLER_PERMS_TO_REVOKE) { + expect(await acm.isAllowedToCall(DEVIATION_SENTINEL, sig)).to.equal(true); + } + }); + }); + + testVip("VIP-661 [BNB Chain] Configure EBrake-integrated DeviationSentinel", await vip661(), { + callbackAfterExecution: async txResponse => { + // 12 giveCallPermission calls: + // - 5 EBrake on Core Pool Comptroller + // - 4 resetMarketState (Guardian + 3 timelocks) + // - 3 DeviationSentinel on EBrake + // 3 revokeCallPermission calls (DeviationSentinel direct comptroller perms from VIP-590) + // BSC mainnet ACM emits RoleGranted/RoleRevoked from the underlying AccessControl base. + await expectEvents(txResponse, [ACCESS_CONTROL_MANAGER_ABI], ["RoleGranted"], [12]); + await expectEvents(txResponse, [ACCESS_CONTROL_MANAGER_ABI], ["RoleRevoked"], [3]); + }, + }); + + describe("Post-VIP behavior", () => { + it("DeviationSentinel proxy should point to the new EBrake-integrated implementation", async () => { + expect(await proxyAdmin.getProxyImplementation(DEVIATION_SENTINEL)).to.equal(NEW_DEVIATION_SENTINEL_IMPL); + }); + + it("EBrake should have all required permissions on the Core Pool Comptroller", async () => { + const acm = accessControlManager.connect(impersonatedComptroller); + for (const sig of EBRAKE_COMPTROLLER_PERMS) { + expect(await acm.isAllowedToCall(EBRAKE, sig)).to.equal(true); + } + }); + + it("Guardian and governance timelocks should have resetMarketState permission on EBrake", async () => { + const acm = accessControlManager.connect(impersonatedEBrake); + for (const account of [GUARDIAN, ...TIMELOCKS]) { + expect(await acm.isAllowedToCall(account, "resetMarketState(address)")).to.equal(true); + } + }); + + it("DeviationSentinel should have pauseBorrow / pauseSupply / setCFZero permissions on EBrake", async () => { + const acm = accessControlManager.connect(impersonatedEBrake); + for (const sig of SENTINEL_EBRAKE_PERMS) { + expect(await acm.isAllowedToCall(DEVIATION_SENTINEL, sig)).to.equal(true); + } + }); + + it("DeviationSentinel should no longer have direct comptroller permissions (revoked)", async () => { + const acm = accessControlManager.connect(impersonatedComptroller); + for (const sig of SENTINEL_COMPTROLLER_PERMS_TO_REVOKE) { + expect(await acm.isAllowedToCall(DEVIATION_SENTINEL, sig)).to.equal(false); + } + }); + + // Smoke tests on the wired-up contracts + it("EBrake.COMPTROLLER should equal the Core Pool Comptroller", async () => { + const eBrake = await ethers.getContractAt(EBRAKE_ABI, EBRAKE); + expect(await eBrake.COMPTROLLER()).to.equal(CORE_POOL_COMPTROLLER); + }); + + it("EBrake.IS_ISOLATED_POOL should be false (Core Pool Diamond)", async () => { + const eBrake = await ethers.getContractAt(EBRAKE_ABI, EBRAKE); + expect(await eBrake.IS_ISOLATED_POOL()).to.equal(false); + }); + + it("DeviationSentinel.EBRAKE should equal the deployed EBrake address", async () => { + expect(await deviationSentinel.EBRAKE()).to.equal(EBRAKE); + }); + }); +}); diff --git a/vips/vip-661/bscmainnet.ts b/vips/vip-661/bscmainnet.ts new file mode 100644 index 000000000..e59e4f48b --- /dev/null +++ b/vips/vip-661/bscmainnet.ts @@ -0,0 +1,181 @@ +import { NETWORK_ADDRESSES } from "src/networkAddresses"; +import { ProposalType } from "src/types"; +import { makeProposal } from "src/utils"; + +const { NORMAL_TIMELOCK, FAST_TRACK_TIMELOCK, CRITICAL_TIMELOCK, GUARDIAN } = NETWORK_ADDRESSES.bscmainnet; + +// ============================================================ +// New deployments — TODO: fill in once contracts are deployed +// ============================================================ +// EBrake (TransparentUpgradeableProxy) — emergency action router +export const EBRAKE = "0x0000000000000000000000000000000000000000"; // TODO: set after deployment +// New DeviationSentinel implementation that routes through EBrake +export const NEW_DEVIATION_SENTINEL_IMPL = "0x0000000000000000000000000000000000000000"; // TODO: set after deployment + +// ============================================================ +// Existing BSC Mainnet addresses +// ============================================================ +// DeviationSentinel proxy (deployed in VIP-590) +export const DEVIATION_SENTINEL = "0x6599C15cc8407046CD91E5c0F8B7f765fF914870"; +// Default Proxy Admin that owns the DeviationSentinel proxy (admin slot read on-chain; +// owner is Normal Timelock 0x939bD8d64c0A9583A7Dcea9933f7b21697ab6396) +export const PROXY_ADMIN = "0x6beb6d2695b67feb73ad4f172e8e2975497187e4"; +// Venus Core Pool Comptroller (Diamond proxy) +export const CORE_POOL_COMPTROLLER = NETWORK_ADDRESSES.bscmainnet.UNITROLLER; +// Access Control Manager +export const ACM = NETWORK_ADDRESSES.bscmainnet.ACCESS_CONTROL_MANAGER; + +const TIMELOCKS = [NORMAL_TIMELOCK, FAST_TRACK_TIMELOCK, CRITICAL_TIMELOCK]; + +export const vip661 = () => { + const meta = { + version: "v2", + title: "VIP-661 [BNB Chain] Configure EBrake-integrated DeviationSentinel", + description: `#### Summary + +This VIP wires up the **EBrake** contract on BNB Chain Mainnet and migrates **DeviationSentinel** to route all +emergency actions through it. EBrake is a thin emergency-action router that exposes Comptroller pause / +collateral-factor / cap functions behind ACM permissions, snapshots pre-incident state for safe recovery, and is +called by DeviationSentinel when a price deviation is detected. + +If approved, this VIP will: + +1. Upgrade the DeviationSentinel proxy to the new implementation that routes actions through EBrake +2. Grant EBrake permissions on the Core Pool Comptroller (so EBrake can pause actions, zero collateral factors, + decrease borrow/supply caps, and pause flash loans) +3. Grant the Normal, FastTrack and Critical Timelocks (and Guardian) permission to call \`resetMarketState\` on + EBrake (the only path to clear EBrake's snapshots after a recovery VIP) +4. Grant DeviationSentinel permission to call \`pauseBorrow\`, \`pauseSupply\` and \`setCFZero\` on EBrake (the + three functions \`handleDeviation\` invokes) +5. Revoke DeviationSentinel's existing direct comptroller permissions (granted in VIP-590), since the sentinel + now goes through EBrake + +#### Description + +**EBrake** (\`EmergencyBrake/EBrake.sol\`) is the new emergency action router. It holds no detection logic — it +only exposes Comptroller emergency functions behind ACM checks and snapshots the pre-incident state of every +market it touches (collateral factors per pool, borrow/supply caps) so a recovery VIP can restore exact prior +values. Snapshots are first-write-wins and cleared via \`resetMarketState\`. + +**DeviationSentinel** has been refactored so that \`handleDeviation\` calls EBrake (\`pauseBorrow\`, +\`pauseSupply\`, \`setCFZero\`) instead of the comptroller directly. This adds an additional safety layer +(snapshot + idempotency live in EBrake) and standardizes emergency action routing across the protocol. + +#### References + +- [VIP Pull Request](https://github.com/VenusProtocol/vips/pull/694) +- [DeviationSentinel Proxy](https://bscscan.com/address/${DEVIATION_SENTINEL}) +- [New DeviationSentinel Implementation](https://bscscan.com/address/${NEW_DEVIATION_SENTINEL_IMPL}) +- [EBrake Contract](https://bscscan.com/address/${EBRAKE})`, + forDescription: "Execute this proposal", + againstDescription: "Do not execute this proposal", + abstainDescription: "Indifferent to execution", + }; + + return makeProposal( + [ + // ======================================== + // 1. Upgrade DeviationSentinel proxy to new (EBrake-integrated) implementation + // ======================================== + { + target: PROXY_ADMIN, + signature: "upgrade(address,address)", + params: [DEVIATION_SENTINEL, NEW_DEVIATION_SENTINEL_IMPL], + }, + + // ======================================== + // 2. Grant EBrake permissions on the Core Pool Comptroller + // (the functions EBrake invokes when tightening a market) + // ======================================== + + // pauseActions / pauseSupply / pauseRedeem / pauseBorrow / pauseTransfer → comptroller.setActionsPaused + { + target: ACM, + signature: "giveCallPermission(address,string,address)", + params: [CORE_POOL_COMPTROLLER, "_setActionsPaused(address[],uint8[],bool)", EBRAKE], + }, + + // setCFZero(address) / setCFZero(address,uint96) → Diamond comptroller (with poolId) + { + target: ACM, + signature: "giveCallPermission(address,string,address)", + params: [CORE_POOL_COMPTROLLER, "setCollateralFactor(uint96,address,uint256,uint256)", EBRAKE], + }, + + // setMarketBorrowCaps → comptroller.setMarketBorrowCaps + { + target: ACM, + signature: "giveCallPermission(address,string,address)", + params: [CORE_POOL_COMPTROLLER, "_setMarketBorrowCaps(address[],uint256[])", EBRAKE], + }, + + // setMarketSupplyCaps → comptroller.setMarketSupplyCaps + { + target: ACM, + signature: "giveCallPermission(address,string,address)", + params: [CORE_POOL_COMPTROLLER, "_setMarketSupplyCaps(address[],uint256[])", EBRAKE], + }, + + // pauseFlashLoan → comptroller.setFlashLoanPaused + { + target: ACM, + signature: "giveCallPermission(address,string,address)", + params: [CORE_POOL_COMPTROLLER, "setFlashLoanPaused(bool)", EBRAKE], + }, + + // ======================================== + // 3. Grant resetMarketState permission to governance timelocks and Guardian + // (the only path to clear EBrake's stored snapshots during recovery) + // ======================================== + ...[GUARDIAN, ...TIMELOCKS].map((account: string) => ({ + target: ACM, + signature: "giveCallPermission(address,string,address)", + params: [EBRAKE, "resetMarketState(address)", account], + })), + + // ======================================== + // 4. Grant DeviationSentinel permissions on EBrake + // (the three functions handleDeviation invokes) + // ======================================== + { + target: ACM, + signature: "giveCallPermission(address,string,address)", + params: [EBRAKE, "pauseBorrow(address)", DEVIATION_SENTINEL], + }, + { + target: ACM, + signature: "giveCallPermission(address,string,address)", + params: [EBRAKE, "pauseSupply(address)", DEVIATION_SENTINEL], + }, + { + target: ACM, + signature: "giveCallPermission(address,string,address)", + params: [EBRAKE, "setCFZero(address)", DEVIATION_SENTINEL], + }, + + // ======================================== + // 5. Revoke DeviationSentinel's old direct comptroller permissions (granted in VIP-590) + // Sentinel now routes everything through EBrake. + // ======================================== + { + target: ACM, + signature: "revokeCallPermission(address,string,address)", + params: [CORE_POOL_COMPTROLLER, "setActionsPaused(address[],uint8[],bool)", DEVIATION_SENTINEL], + }, + { + target: ACM, + signature: "revokeCallPermission(address,string,address)", + params: [CORE_POOL_COMPTROLLER, "_setActionsPaused(address[],uint8[],bool)", DEVIATION_SENTINEL], + }, + { + target: ACM, + signature: "revokeCallPermission(address,string,address)", + params: [CORE_POOL_COMPTROLLER, "setCollateralFactor(uint96,address,uint256,uint256)", DEVIATION_SENTINEL], + }, + ], + meta, + ProposalType.REGULAR, + ); +}; + +export default vip661; From 15b965e9f57883b404c521a8097bcd4252f3bcd7 Mon Sep 17 00:00:00 2001 From: Debugger022 Date: Thu, 9 Apr 2026 16:53:06 +0530 Subject: [PATCH 08/11] feat: update VIP-661 EBrake permissions and refactor bscmainnet VIP Replace setCFZero with decreaseCF, split resetMarketState into three granular snapshot-reset functions, and grant governance timelocks permission on all EBrake action functions so Critical VIPs can route through EBrake and benefit from its snapshot mechanism. Refactor VIP body to use signature arrays with giveCallPermission/ revokeCallPermission helpers, reducing makeProposal from ~100 to 13 lines. --- simulations/vip-661/bscmainnet.ts | 100 ++++++++++++---- vips/vip-661/bscmainnet.ts | 184 ++++++++++++++---------------- 2 files changed, 159 insertions(+), 125 deletions(-) diff --git a/simulations/vip-661/bscmainnet.ts b/simulations/vip-661/bscmainnet.ts index fb3230583..ff359a517 100644 --- a/simulations/vip-661/bscmainnet.ts +++ b/simulations/vip-661/bscmainnet.ts @@ -22,19 +22,42 @@ import PROXY_ADMIN_ABI from "./abi/ProxyAdmin.json"; const { NORMAL_TIMELOCK, FAST_TRACK_TIMELOCK, CRITICAL_TIMELOCK, GUARDIAN } = NETWORK_ADDRESSES.bscmainnet; const TIMELOCKS = [NORMAL_TIMELOCK, FAST_TRACK_TIMELOCK, CRITICAL_TIMELOCK]; -// Permissions EBrake needs on the Core Pool Comptroller (host = comptroller) +// Permissions EBrake needs on the Core Pool Comptroller const EBRAKE_COMPTROLLER_PERMS = [ "_setActionsPaused(address[],uint8[],bool)", + "setCollateralFactor(address,uint256,uint256)", "setCollateralFactor(uint96,address,uint256,uint256)", "_setMarketBorrowCaps(address[],uint256[])", "_setMarketSupplyCaps(address[],uint256[])", "setFlashLoanPaused(bool)", + "setIsBorrowAllowed(uint96,address,bool)", + "setWhiteListFlashLoanAccount(address,bool)", ]; -// Permissions DeviationSentinel will have on EBrake (host = EBrake) -const SENTINEL_EBRAKE_PERMS = ["pauseBorrow(address)", "pauseSupply(address)", "setCFZero(address)"]; +// Permissions DeviationSentinel will have on EBrake +const SENTINEL_EBRAKE_PERMS = ["pauseBorrow(address)", "pauseSupply(address)", "decreaseCF(address,uint256)"]; + +// Granular snapshot-reset functions governance/guardian can call on EBrake +const RESET_PERMS = ["resetCFSnapshot(address)", "resetBorrowCapSnapshot(address)", "resetSupplyCapSnapshot(address)"]; + +// All EBrake action functions governance/guardian can call directly +// (so a Critical VIP can route through EBrake and get snapshot coverage) +const GOVERNANCE_EBRAKE_PERMS = [ + "pauseBorrow(address)", + "pauseSupply(address)", + "pauseRedeem(address)", + "pauseTransfer(address)", + "pauseFlashLoan()", + "pauseActions(address[],uint8[])", + "setMarketBorrowCaps(address[],uint256[])", + "setMarketSupplyCaps(address[],uint256[])", + "disablePoolBorrow(uint96,address)", + "revokeFlashLoanAccess(address)", + "decreaseCF(address,uint256)", + "decreaseCF(address,uint96,uint256)", +]; -// Permissions DeviationSentinel had directly on the comptroller in VIP-590 and that this VIP revokes +// Permissions DeviationSentinel had directly on the comptroller in VIP-590 — this VIP revokes them const SENTINEL_COMPTROLLER_PERMS_TO_REVOKE = [ "setActionsPaused(address[],uint8[],bool)", "_setActionsPaused(address[],uint8[],bool)", @@ -46,8 +69,8 @@ forking(91124514, async () => { let deviationSentinel: Contract; let proxyAdmin: Contract; - // Impersonated host contracts — BSC mainnet ACM exposes isAllowedToCall(account, sig) - // where the host contract MUST be msg.sender. So we impersonate the host and call from it. + // BSC mainnet ACM checks msg.sender (the host contract) against the stored contract address. + // We impersonate the host so isAllowedToCall returns the correct result. let impersonatedComptroller: SignerWithAddress; let impersonatedEBrake: SignerWithAddress; @@ -68,41 +91,58 @@ forking(91124514, async () => { it("EBrake should not yet have any permissions on the Core Pool Comptroller", async () => { const acm = accessControlManager.connect(impersonatedComptroller); for (const sig of EBRAKE_COMPTROLLER_PERMS) { - expect(await acm.isAllowedToCall(EBRAKE, sig)).to.equal(false); + expect(await acm.isAllowedToCall(EBRAKE, sig)).to.equal(false, `unexpected permission: ${sig}`); + } + }); + + it("Guardian and governance timelocks should not yet have granular reset permissions on EBrake", async () => { + const acm = accessControlManager.connect(impersonatedEBrake); + for (const account of [GUARDIAN, ...TIMELOCKS]) { + for (const sig of RESET_PERMS) { + expect(await acm.isAllowedToCall(account, sig)).to.equal( + false, + `unexpected permission: ${sig} for ${account}`, + ); + } } }); - it("Guardian and governance timelocks should not yet have resetMarketState permission on EBrake", async () => { + it("Guardian and governance timelocks should not yet have new EBrake function permissions", async () => { const acm = accessControlManager.connect(impersonatedEBrake); for (const account of [GUARDIAN, ...TIMELOCKS]) { - expect(await acm.isAllowedToCall(account, "resetMarketState(address)")).to.equal(false); + for (const sig of GOVERNANCE_EBRAKE_PERMS) { + expect(await acm.isAllowedToCall(account, sig)).to.equal( + false, + `unexpected permission: ${sig} for ${account}`, + ); + } } }); it("DeviationSentinel should not yet have any permissions on EBrake", async () => { const acm = accessControlManager.connect(impersonatedEBrake); for (const sig of SENTINEL_EBRAKE_PERMS) { - expect(await acm.isAllowedToCall(DEVIATION_SENTINEL, sig)).to.equal(false); + expect(await acm.isAllowedToCall(DEVIATION_SENTINEL, sig)).to.equal(false, `unexpected permission: ${sig}`); } }); it("DeviationSentinel should still have direct comptroller permissions from VIP-590", async () => { const acm = accessControlManager.connect(impersonatedComptroller); for (const sig of SENTINEL_COMPTROLLER_PERMS_TO_REVOKE) { - expect(await acm.isAllowedToCall(DEVIATION_SENTINEL, sig)).to.equal(true); + expect(await acm.isAllowedToCall(DEVIATION_SENTINEL, sig)).to.equal(true, `expected permission: ${sig}`); } }); }); testVip("VIP-661 [BNB Chain] Configure EBrake-integrated DeviationSentinel", await vip661(), { callbackAfterExecution: async txResponse => { - // 12 giveCallPermission calls: - // - 5 EBrake on Core Pool Comptroller - // - 4 resetMarketState (Guardian + 3 timelocks) - // - 3 DeviationSentinel on EBrake - // 3 revokeCallPermission calls (DeviationSentinel direct comptroller perms from VIP-590) - // BSC mainnet ACM emits RoleGranted/RoleRevoked from the underlying AccessControl base. - await expectEvents(txResponse, [ACCESS_CONTROL_MANAGER_ABI], ["RoleGranted"], [12]); + // RoleGranted breakdown: + // - 8 EBrake on Core Pool Comptroller (6 original + setIsBorrowAllowed + setWhiteListFlashLoanAccount) + // - 12 granular reset functions (3 × 4 accounts: Guardian + 3 timelocks) + // - 3 DeviationSentinel on EBrake (pauseBorrow, pauseSupply, decreaseCF) + // - 48 governance EBrake action functions (12 functions × 4 accounts) + // RoleRevoked: 3 (DeviationSentinel direct comptroller perms from VIP-590) + await expectEvents(txResponse, [ACCESS_CONTROL_MANAGER_ABI], ["RoleGranted"], [71]); await expectEvents(txResponse, [ACCESS_CONTROL_MANAGER_ABI], ["RoleRevoked"], [3]); }, }); @@ -115,32 +155,42 @@ forking(91124514, async () => { it("EBrake should have all required permissions on the Core Pool Comptroller", async () => { const acm = accessControlManager.connect(impersonatedComptroller); for (const sig of EBRAKE_COMPTROLLER_PERMS) { - expect(await acm.isAllowedToCall(EBRAKE, sig)).to.equal(true); + expect(await acm.isAllowedToCall(EBRAKE, sig)).to.equal(true, `missing permission: ${sig}`); + } + }); + + it("Guardian and governance timelocks should have granular reset permissions on EBrake", async () => { + const acm = accessControlManager.connect(impersonatedEBrake); + for (const account of [GUARDIAN, ...TIMELOCKS]) { + for (const sig of RESET_PERMS) { + expect(await acm.isAllowedToCall(account, sig)).to.equal(true, `missing permission: ${sig} for ${account}`); + } } }); - it("Guardian and governance timelocks should have resetMarketState permission on EBrake", async () => { + it("Guardian and governance timelocks should have new EBrake function permissions", async () => { const acm = accessControlManager.connect(impersonatedEBrake); for (const account of [GUARDIAN, ...TIMELOCKS]) { - expect(await acm.isAllowedToCall(account, "resetMarketState(address)")).to.equal(true); + for (const sig of GOVERNANCE_EBRAKE_PERMS) { + expect(await acm.isAllowedToCall(account, sig)).to.equal(true, `missing permission: ${sig} for ${account}`); + } } }); - it("DeviationSentinel should have pauseBorrow / pauseSupply / setCFZero permissions on EBrake", async () => { + it("DeviationSentinel should have pauseBorrow / pauseSupply / decreaseCF permissions on EBrake", async () => { const acm = accessControlManager.connect(impersonatedEBrake); for (const sig of SENTINEL_EBRAKE_PERMS) { - expect(await acm.isAllowedToCall(DEVIATION_SENTINEL, sig)).to.equal(true); + expect(await acm.isAllowedToCall(DEVIATION_SENTINEL, sig)).to.equal(true, `missing permission: ${sig}`); } }); it("DeviationSentinel should no longer have direct comptroller permissions (revoked)", async () => { const acm = accessControlManager.connect(impersonatedComptroller); for (const sig of SENTINEL_COMPTROLLER_PERMS_TO_REVOKE) { - expect(await acm.isAllowedToCall(DEVIATION_SENTINEL, sig)).to.equal(false); + expect(await acm.isAllowedToCall(DEVIATION_SENTINEL, sig)).to.equal(false, `unexpected permission: ${sig}`); } }); - // Smoke tests on the wired-up contracts it("EBrake.COMPTROLLER should equal the Core Pool Comptroller", async () => { const eBrake = await ethers.getContractAt(EBRAKE_ABI, EBRAKE); expect(await eBrake.COMPTROLLER()).to.equal(CORE_POOL_COMPTROLLER); diff --git a/vips/vip-661/bscmainnet.ts b/vips/vip-661/bscmainnet.ts index e59e4f48b..f457a74e2 100644 --- a/vips/vip-661/bscmainnet.ts +++ b/vips/vip-661/bscmainnet.ts @@ -4,17 +4,11 @@ import { makeProposal } from "src/utils"; const { NORMAL_TIMELOCK, FAST_TRACK_TIMELOCK, CRITICAL_TIMELOCK, GUARDIAN } = NETWORK_ADDRESSES.bscmainnet; -// ============================================================ -// New deployments — TODO: fill in once contracts are deployed -// ============================================================ // EBrake (TransparentUpgradeableProxy) — emergency action router export const EBRAKE = "0x0000000000000000000000000000000000000000"; // TODO: set after deployment // New DeviationSentinel implementation that routes through EBrake export const NEW_DEVIATION_SENTINEL_IMPL = "0x0000000000000000000000000000000000000000"; // TODO: set after deployment -// ============================================================ -// Existing BSC Mainnet addresses -// ============================================================ // DeviationSentinel proxy (deployed in VIP-590) export const DEVIATION_SENTINEL = "0x6599C15cc8407046CD91E5c0F8B7f765fF914870"; // Default Proxy Admin that owns the DeviationSentinel proxy (admin slot read on-chain; @@ -27,6 +21,63 @@ export const ACM = NETWORK_ADDRESSES.bscmainnet.ACCESS_CONTROL_MANAGER; const TIMELOCKS = [NORMAL_TIMELOCK, FAST_TRACK_TIMELOCK, CRITICAL_TIMELOCK]; +// Comptroller functions EBrake is allowed to call +const EBRAKE_COMPTROLLER_PERMS = [ + "_setActionsPaused(address[],uint8[],bool)", + "setCollateralFactor(uint96,address,uint256,uint256)", + "setCollateralFactor(address,uint256,uint256)", + "_setMarketBorrowCaps(address[],uint256[])", + "_setMarketSupplyCaps(address[],uint256[])", + "setFlashLoanPaused(bool)", + "setIsBorrowAllowed(uint96,address,bool)", // disablePoolBorrow + "setWhiteListFlashLoanAccount(address,bool)", // revokeFlashLoanAccess +]; + +// Granular snapshot-reset functions governance timelocks and Guardian can call on EBrake +const RESET_PERMS = ["resetCFSnapshot(address)", "resetBorrowCapSnapshot(address)", "resetSupplyCapSnapshot(address)"]; + +// EBrake functions DeviationSentinel calls via handleDeviation +const SENTINEL_EBRAKE_PERMS = ["pauseBorrow(address)", "pauseSupply(address)", "decreaseCF(address,uint256)"]; + +// All EBrake action functions governance timelocks and Guardian can call directly. +// Lets a Critical VIP (~1h delay) route through EBrake and get snapshot coverage, +// making recovery via resetXxxSnapshot trivial. Without this, governance would call +// the comptroller directly (no snapshot stored). +const GOVERNANCE_EBRAKE_PERMS = [ + "pauseBorrow(address)", + "pauseSupply(address)", + "pauseRedeem(address)", + "pauseTransfer(address)", + "pauseFlashLoan()", + "pauseActions(address[],uint8[])", + "setMarketBorrowCaps(address[],uint256[])", + "setMarketSupplyCaps(address[],uint256[])", + "disablePoolBorrow(uint96,address)", + "revokeFlashLoanAccess(address)", + "decreaseCF(address,uint256)", + "decreaseCF(address,uint96,uint256)", +]; + +// DeviationSentinel direct comptroller permissions granted in VIP-590 — revoked here +// since the sentinel now routes all emergency actions through EBrake +const SENTINEL_COMPTROLLER_PERMS_TO_REVOKE = [ + "setActionsPaused(address[],uint8[],bool)", + "_setActionsPaused(address[],uint8[],bool)", + "setCollateralFactor(uint96,address,uint256,uint256)", +]; + +const giveCallPermission = (contract: string, sig: string, account: string) => ({ + target: ACM, + signature: "giveCallPermission(address,string,address)", + params: [contract, sig, account], +}); + +const revokeCallPermission = (contract: string, sig: string, account: string) => ({ + target: ACM, + signature: "revokeCallPermission(address,string,address)", + params: [contract, sig, account], +}); + export const vip661 = () => { const meta = { version: "v2", @@ -38,16 +89,22 @@ emergency actions through it. EBrake is a thin emergency-action router that expo collateral-factor / cap functions behind ACM permissions, snapshots pre-incident state for safe recovery, and is called by DeviationSentinel when a price deviation is detected. +This VIP incorporates Phase-0 Hashdit audit fixes (venus-periphery#62): \`setCFZero\` replaced by +\`decreaseCF(address,uint256)\`, \`resetMarketState\` split into three granular snapshot-reset functions, and two +new surgical controls (\`disablePoolBorrow\`, \`revokeFlashLoanAccess\`) added to EBrake. + If approved, this VIP will: 1. Upgrade the DeviationSentinel proxy to the new implementation that routes actions through EBrake -2. Grant EBrake permissions on the Core Pool Comptroller (so EBrake can pause actions, zero collateral factors, - decrease borrow/supply caps, and pause flash loans) -3. Grant the Normal, FastTrack and Critical Timelocks (and Guardian) permission to call \`resetMarketState\` on - EBrake (the only path to clear EBrake's snapshots after a recovery VIP) -4. Grant DeviationSentinel permission to call \`pauseBorrow\`, \`pauseSupply\` and \`setCFZero\` on EBrake (the +2. Grant EBrake permissions on the Core Pool Comptroller (so EBrake can pause actions, decrease collateral + factors, decrease borrow/supply caps, pause flash loans, disable per-pool borrows, and revoke flash loan access) +3. Grant the Normal, FastTrack and Critical Timelocks (and Guardian) permission to call the three granular + snapshot-reset functions on EBrake (\`resetCFSnapshot\`, \`resetBorrowCapSnapshot\`, \`resetSupplyCapSnapshot\`) +4. Grant DeviationSentinel permission to call \`pauseBorrow\`, \`pauseSupply\` and \`decreaseCF\` on EBrake (the three functions \`handleDeviation\` invokes) -5. Revoke DeviationSentinel's existing direct comptroller permissions (granted in VIP-590), since the sentinel +5. Grant the Normal, FastTrack and Critical Timelocks (and Guardian) permission to call all EBrake action + functions directly, so a Critical VIP can route through EBrake and benefit from its snapshot mechanism +6. Revoke DeviationSentinel's existing direct comptroller permissions (granted in VIP-590), since the sentinel now goes through EBrake #### Description @@ -55,10 +112,10 @@ If approved, this VIP will: **EBrake** (\`EmergencyBrake/EBrake.sol\`) is the new emergency action router. It holds no detection logic — it only exposes Comptroller emergency functions behind ACM checks and snapshots the pre-incident state of every market it touches (collateral factors per pool, borrow/supply caps) so a recovery VIP can restore exact prior -values. Snapshots are first-write-wins and cleared via \`resetMarketState\`. +values. Snapshots are first-write-wins and cleared via the three granular reset functions. **DeviationSentinel** has been refactored so that \`handleDeviation\` calls EBrake (\`pauseBorrow\`, -\`pauseSupply\`, \`setCFZero\`) instead of the comptroller directly. This adds an additional safety layer +\`pauseSupply\`, \`decreaseCF\`) instead of the comptroller directly. This adds an additional safety layer (snapshot + idempotency live in EBrake) and standardizes emergency action routing across the protocol. #### References @@ -74,104 +131,31 @@ values. Snapshots are first-write-wins and cleared via \`resetMarketState\`. return makeProposal( [ - // ======================================== // 1. Upgrade DeviationSentinel proxy to new (EBrake-integrated) implementation - // ======================================== { target: PROXY_ADMIN, signature: "upgrade(address,address)", params: [DEVIATION_SENTINEL, NEW_DEVIATION_SENTINEL_IMPL], }, - // ======================================== // 2. Grant EBrake permissions on the Core Pool Comptroller - // (the functions EBrake invokes when tightening a market) - // ======================================== - - // pauseActions / pauseSupply / pauseRedeem / pauseBorrow / pauseTransfer → comptroller.setActionsPaused - { - target: ACM, - signature: "giveCallPermission(address,string,address)", - params: [CORE_POOL_COMPTROLLER, "_setActionsPaused(address[],uint8[],bool)", EBRAKE], - }, - - // setCFZero(address) / setCFZero(address,uint96) → Diamond comptroller (with poolId) - { - target: ACM, - signature: "giveCallPermission(address,string,address)", - params: [CORE_POOL_COMPTROLLER, "setCollateralFactor(uint96,address,uint256,uint256)", EBRAKE], - }, + ...EBRAKE_COMPTROLLER_PERMS.map(sig => giveCallPermission(CORE_POOL_COMPTROLLER, sig, EBRAKE)), - // setMarketBorrowCaps → comptroller.setMarketBorrowCaps - { - target: ACM, - signature: "giveCallPermission(address,string,address)", - params: [CORE_POOL_COMPTROLLER, "_setMarketBorrowCaps(address[],uint256[])", EBRAKE], - }, + // 3. Grant granular snapshot-reset permissions to governance timelocks and Guardian + ...[GUARDIAN, ...TIMELOCKS].flatMap(account => RESET_PERMS.map(sig => giveCallPermission(EBRAKE, sig, account))), - // setMarketSupplyCaps → comptroller.setMarketSupplyCaps - { - target: ACM, - signature: "giveCallPermission(address,string,address)", - params: [CORE_POOL_COMPTROLLER, "_setMarketSupplyCaps(address[],uint256[])", EBRAKE], - }, + // 4. Grant DeviationSentinel permissions on EBrake (the three functions handleDeviation invokes) + ...SENTINEL_EBRAKE_PERMS.map(sig => giveCallPermission(EBRAKE, sig, DEVIATION_SENTINEL)), - // pauseFlashLoan → comptroller.setFlashLoanPaused - { - target: ACM, - signature: "giveCallPermission(address,string,address)", - params: [CORE_POOL_COMPTROLLER, "setFlashLoanPaused(bool)", EBRAKE], - }, + // 5. Grant governance timelocks and Guardian permissions on all EBrake action functions + ...[GUARDIAN, ...TIMELOCKS].flatMap(account => + GOVERNANCE_EBRAKE_PERMS.map(sig => giveCallPermission(EBRAKE, sig, account)), + ), - // ======================================== - // 3. Grant resetMarketState permission to governance timelocks and Guardian - // (the only path to clear EBrake's stored snapshots during recovery) - // ======================================== - ...[GUARDIAN, ...TIMELOCKS].map((account: string) => ({ - target: ACM, - signature: "giveCallPermission(address,string,address)", - params: [EBRAKE, "resetMarketState(address)", account], - })), - - // ======================================== - // 4. Grant DeviationSentinel permissions on EBrake - // (the three functions handleDeviation invokes) - // ======================================== - { - target: ACM, - signature: "giveCallPermission(address,string,address)", - params: [EBRAKE, "pauseBorrow(address)", DEVIATION_SENTINEL], - }, - { - target: ACM, - signature: "giveCallPermission(address,string,address)", - params: [EBRAKE, "pauseSupply(address)", DEVIATION_SENTINEL], - }, - { - target: ACM, - signature: "giveCallPermission(address,string,address)", - params: [EBRAKE, "setCFZero(address)", DEVIATION_SENTINEL], - }, - - // ======================================== - // 5. Revoke DeviationSentinel's old direct comptroller permissions (granted in VIP-590) - // Sentinel now routes everything through EBrake. - // ======================================== - { - target: ACM, - signature: "revokeCallPermission(address,string,address)", - params: [CORE_POOL_COMPTROLLER, "setActionsPaused(address[],uint8[],bool)", DEVIATION_SENTINEL], - }, - { - target: ACM, - signature: "revokeCallPermission(address,string,address)", - params: [CORE_POOL_COMPTROLLER, "_setActionsPaused(address[],uint8[],bool)", DEVIATION_SENTINEL], - }, - { - target: ACM, - signature: "revokeCallPermission(address,string,address)", - params: [CORE_POOL_COMPTROLLER, "setCollateralFactor(uint96,address,uint256,uint256)", DEVIATION_SENTINEL], - }, + // 6. Revoke DeviationSentinel's old direct comptroller permissions (granted in VIP-590) + ...SENTINEL_COMPTROLLER_PERMS_TO_REVOKE.map(sig => + revokeCallPermission(CORE_POOL_COMPTROLLER, sig, DEVIATION_SENTINEL), + ), ], meta, ProposalType.REGULAR, From 450070ce265d5aeaa5dd361dc741125ed1fa680f Mon Sep 17 00:00:00 2001 From: Debugger022 Date: Thu, 9 Apr 2026 17:12:04 +0530 Subject: [PATCH 09/11] feat: add VIP-661 addendum 3 to update EBrake permissions on BSC testnet Upgrades EBrake and DeviationSentinel to the new implementations, swaps setCFZero for decreaseCF on DeviationSentinel, replaces resetMarketState with three granular snapshot-reset functions, and grants timelocks and keeper permission on all EBrake action functions so Critical VIPs can route through EBrake and benefit from its snapshot mechanism. --- simulations/vip-661/bsctestnet-addendum-3.ts | 219 +++++++++++++++++++ vips/vip-661/bsctestnet-addendum-3.ts | 136 ++++++++++++ 2 files changed, 355 insertions(+) create mode 100644 simulations/vip-661/bsctestnet-addendum-3.ts create mode 100644 vips/vip-661/bsctestnet-addendum-3.ts diff --git a/simulations/vip-661/bsctestnet-addendum-3.ts b/simulations/vip-661/bsctestnet-addendum-3.ts new file mode 100644 index 000000000..bea9ba662 --- /dev/null +++ b/simulations/vip-661/bsctestnet-addendum-3.ts @@ -0,0 +1,219 @@ +import { expect } from "chai"; +import { Contract } from "ethers"; +import { ethers } from "hardhat"; +import { NETWORK_ADDRESSES } from "src/networkAddresses"; +import { expectEvents } from "src/utils"; +import { forking, testVip } from "src/vip-framework"; + +import { + ACM, + DEVIATION_SENTINEL, + EBRAKE, + KEEPER_ADDRESS, + NEW_DEVIATION_SENTINEL_IMPL, + NEW_EBRAKE_IMPL, + PROXY_ADMIN, + vip661TestnetAddendum3, +} from "../../vips/vip-661/bsctestnet-addendum-3"; +import ACCESS_CONTROL_MANAGER_ABI from "./abi/AccessControlManager.json"; +import DEVIATION_SENTINEL_ABI from "./abi/DeviationSentinel.json"; +import EBRAKE_ABI from "./abi/EBrake.json"; +import PROXY_ADMIN_ABI from "./abi/ProxyAdmin.json"; + +const { NORMAL_TIMELOCK, FAST_TRACK_TIMELOCK, CRITICAL_TIMELOCK } = NETWORK_ADDRESSES.bsctestnet; +const TIMELOCKS = [NORMAL_TIMELOCK, FAST_TRACK_TIMELOCK, CRITICAL_TIMELOCK]; +// All accounts that had resetMarketState and will receive the new granular reset permissions +const RESET_ACCOUNTS = [...TIMELOCKS, KEEPER_ADDRESS]; + +// Granular snapshot-reset functions (replace resetMarketState) +const RESET_PERMS = ["resetCFSnapshot(address)", "resetBorrowCapSnapshot(address)", "resetSupplyCapSnapshot(address)"]; + +// All EBrake action functions governance/timelocks/keeper can call directly +const GOVERNANCE_EBRAKE_PERMS = [ + "pauseBorrow(address)", + "pauseSupply(address)", + "pauseRedeem(address)", + "pauseTransfer(address)", + "pauseFlashLoan()", + "pauseActions(address[],uint8[])", + "setMarketBorrowCaps(address[],uint256[])", + "setMarketSupplyCaps(address[],uint256[])", + "disablePoolBorrow(uint96,address)", + "revokeFlashLoanAccess(address)", + "decreaseCF(address,uint256)", + "decreaseCF(address,uint96,uint256)", +]; + +// New Comptroller permissions EBrake needs for the new surgical controls +const NEW_EBRAKE_COMPTROLLER_PERMS = [ + "setIsBorrowAllowed(uint96,address,bool)", + "setWhiteListFlashLoanAccount(address,bool)", +]; + +// TODO: update this block number to one after VIP-661 Addendum 2 executed and before Addendum 3 +forking(0, async () => { + let accessControlManager: Contract; + let proxyAdmin: Contract; + let deviationSentinel: Contract; + + before(async () => { + accessControlManager = await ethers.getContractAt(ACCESS_CONTROL_MANAGER_ABI, ACM); + proxyAdmin = new ethers.Contract(PROXY_ADMIN, PROXY_ADMIN_ABI, ethers.provider); + deviationSentinel = await ethers.getContractAt(DEVIATION_SENTINEL_ABI, DEVIATION_SENTINEL); + }); + + describe("Pre-VIP behavior", () => { + it("EBrake proxy should not yet point to the audit-fixed implementation", async () => { + expect(await proxyAdmin.getProxyImplementation(EBRAKE)).to.not.equal(NEW_EBRAKE_IMPL); + }); + + it("DeviationSentinel proxy should not yet point to the new implementation", async () => { + expect(await proxyAdmin.getProxyImplementation(DEVIATION_SENTINEL)).to.not.equal(NEW_DEVIATION_SENTINEL_IMPL); + }); + + it("DeviationSentinel should still have old setCFZero permission on EBrake", async () => { + expect(await accessControlManager.hasPermission(DEVIATION_SENTINEL, EBRAKE, "setCFZero(address)")).to.equal(true); + }); + + it("DeviationSentinel should not yet have decreaseCF permission on EBrake", async () => { + expect( + await accessControlManager.hasPermission(DEVIATION_SENTINEL, EBRAKE, "decreaseCF(address,uint256)"), + ).to.equal(false); + }); + + it("Timelocks and keeper should still have resetMarketState permission", async () => { + // Timelocks were granted at address(0) in bsctestnet.ts + for (const account of TIMELOCKS) { + expect( + await accessControlManager.hasPermission(account, ethers.constants.AddressZero, "resetMarketState(address)"), + ).to.equal(true, `expected resetMarketState for ${account}`); + } + // Keeper was granted at EBRAKE specifically in Addendum 1 + expect(await accessControlManager.hasPermission(KEEPER_ADDRESS, EBRAKE, "resetMarketState(address)")).to.equal( + true, + ); + }); + + it("Timelocks and keeper should not yet have granular reset permissions", async () => { + for (const account of RESET_ACCOUNTS) { + for (const sig of RESET_PERMS) { + expect(await accessControlManager.hasPermission(account, ethers.constants.AddressZero, sig)).to.equal( + false, + `unexpected permission: ${sig} for ${account}`, + ); + } + } + }); + + it("EBrake should not yet have new Comptroller permissions", async () => { + for (const sig of NEW_EBRAKE_COMPTROLLER_PERMS) { + expect(await accessControlManager.hasPermission(EBRAKE, ethers.constants.AddressZero, sig)).to.equal( + false, + `unexpected permission: ${sig}`, + ); + } + }); + + it("Timelocks and keeper should not yet have new EBrake function permissions", async () => { + for (const account of RESET_ACCOUNTS) { + for (const sig of GOVERNANCE_EBRAKE_PERMS) { + expect(await accessControlManager.hasPermission(account, ethers.constants.AddressZero, sig)).to.equal( + false, + `unexpected permission: ${sig} for ${account}`, + ); + } + } + }); + }); + + testVip( + "VIP-661 Addendum 3: Apply Phase-0 Hashdit Audit Fixes (EBrake) on BSC Testnet", + await vip661TestnetAddendum3(), + { + callbackAfterExecution: async txResponse => { + // PermissionRevoked breakdown: + // - 1 setCFZero from DeviationSentinel on EBrake + // - 3 resetMarketState from [3 timelocks] at address(0) (bsctestnet.ts grants) + // - 1 resetMarketState from NormalTimelock at EBRAKE (Addendum 1 grant) + // - 1 resetMarketState from Keeper at EBRAKE (Addendum 1 grant) + // PermissionGranted breakdown: + // - 1 decreaseCF(address,uint256) for DeviationSentinel on EBrake + // - 12 granular reset functions (3 × 4 accounts) + // - 2 new Comptroller permissions for EBrake (setIsBorrowAllowed, setWhiteListFlashLoanAccount) + // - 48 governance EBrake action functions (12 functions × 4 accounts) + await expectEvents(txResponse, [ACCESS_CONTROL_MANAGER_ABI], ["PermissionGranted"], [63]); + await expectEvents(txResponse, [ACCESS_CONTROL_MANAGER_ABI], ["PermissionRevoked"], [6]); + }, + }, + ); + + describe("Post-VIP behavior", () => { + it("EBrake proxy should point to the audit-fixed implementation", async () => { + expect(await proxyAdmin.getProxyImplementation(EBRAKE)).to.equal(NEW_EBRAKE_IMPL); + }); + + it("DeviationSentinel proxy should point to the new implementation", async () => { + expect(await proxyAdmin.getProxyImplementation(DEVIATION_SENTINEL)).to.equal(NEW_DEVIATION_SENTINEL_IMPL); + }); + + it("DeviationSentinel should no longer have setCFZero permission on EBrake", async () => { + expect(await accessControlManager.hasPermission(DEVIATION_SENTINEL, EBRAKE, "setCFZero(address)")).to.equal( + false, + ); + }); + + it("DeviationSentinel should have decreaseCF permission on EBrake", async () => { + expect( + await accessControlManager.hasPermission(DEVIATION_SENTINEL, EBRAKE, "decreaseCF(address,uint256)"), + ).to.equal(true); + }); + + it("Timelocks and keeper should no longer have resetMarketState permission", async () => { + for (const account of RESET_ACCOUNTS) { + expect( + await accessControlManager.hasPermission(account, ethers.constants.AddressZero, "resetMarketState(address)"), + ).to.equal(false, `unexpected resetMarketState for ${account}`); + } + }); + + it("Timelocks and keeper should have all three granular reset permissions", async () => { + for (const account of RESET_ACCOUNTS) { + for (const sig of RESET_PERMS) { + expect(await accessControlManager.hasPermission(account, ethers.constants.AddressZero, sig)).to.equal( + true, + `missing permission: ${sig} for ${account}`, + ); + } + } + }); + + it("EBrake should have new Comptroller permissions for surgical controls", async () => { + for (const sig of NEW_EBRAKE_COMPTROLLER_PERMS) { + expect(await accessControlManager.hasPermission(EBRAKE, ethers.constants.AddressZero, sig)).to.equal( + true, + `missing permission: ${sig}`, + ); + } + }); + + it("Timelocks and keeper should have all new EBrake function permissions", async () => { + for (const account of RESET_ACCOUNTS) { + for (const sig of GOVERNANCE_EBRAKE_PERMS) { + expect(await accessControlManager.hasPermission(account, ethers.constants.AddressZero, sig)).to.equal( + true, + `missing permission: ${sig} for ${account}`, + ); + } + } + }); + + it("DeviationSentinel.EBRAKE should still equal the deployed EBrake address", async () => { + expect(await deviationSentinel.EBRAKE()).to.equal(EBRAKE); + }); + + it("EBrake.IS_ISOLATED_POOL should be false (Core Pool Diamond)", async () => { + const eBrake = await ethers.getContractAt(EBRAKE_ABI, EBRAKE); + expect(await eBrake.IS_ISOLATED_POOL()).to.equal(false); + }); + }); +}); diff --git a/vips/vip-661/bsctestnet-addendum-3.ts b/vips/vip-661/bsctestnet-addendum-3.ts new file mode 100644 index 000000000..d5afae0ae --- /dev/null +++ b/vips/vip-661/bsctestnet-addendum-3.ts @@ -0,0 +1,136 @@ +import { ethers } from "hardhat"; +import { NETWORK_ADDRESSES } from "src/networkAddresses"; +import { ProposalType } from "src/types"; +import { makeProposal } from "src/utils"; + +const { NORMAL_TIMELOCK, FAST_TRACK_TIMELOCK, CRITICAL_TIMELOCK } = NETWORK_ADDRESSES.bsctestnet; + +// New EBrake implementation — TODO: set after PR #62 deployment +export const NEW_EBRAKE_IMPL = "0x0000000000000000000000000000000000000000"; +// New DeviationSentinel implementation (calls decreaseCF instead of setCFZero) — TODO: set after deployment +export const NEW_DEVIATION_SENTINEL_IMPL = "0x0000000000000000000000000000000000000000"; + +export const EBRAKE = "0x957c09e3Ac3d9e689244DC74307c94111FBa8B42"; +export const DEVIATION_SENTINEL = "0x9245d72712548707809D66848e63B8E2B169F3c1"; +export const PROXY_ADMIN = "0x7877ffd62649b6a1557b55d4c20fcbab17344c91"; +export const ACM = "0x45f8a08F534f34A97187626E05d4b6648Eeaa9AA"; +// Keeper/Guardian address granted extra testnet convenience permissions in Addendum 1 +export const KEEPER_ADDRESS = "0x2Ce1d0ffD7E869D9DF33e28552b12DdDed326706"; + +const TIMELOCKS = [NORMAL_TIMELOCK, FAST_TRACK_TIMELOCK, CRITICAL_TIMELOCK]; +// All accounts that received resetMarketState permission (bsctestnet.ts + Addendum 1) +const RESET_ACCOUNTS = [...TIMELOCKS, KEEPER_ADDRESS]; + +// Testnet uses address(0) so grants apply across all comptrollers +const ADDR_ZERO = ethers.constants.AddressZero; + +// Granular snapshot-reset functions (replace resetMarketState) +const RESET_PERMS = ["resetCFSnapshot(address)", "resetBorrowCapSnapshot(address)", "resetSupplyCapSnapshot(address)"]; + +// New Comptroller permissions EBrake needs for the new surgical controls +const NEW_EBRAKE_COMPTROLLER_PERMS = [ + "setIsBorrowAllowed(uint96,address,bool)", + "setWhiteListFlashLoanAccount(address,bool)", +]; + +// All EBrake action functions timelocks and keeper can call directly. +// Lets a Critical VIP (~1h delay) route through EBrake and get snapshot coverage, +// making recovery via resetXxxSnapshot trivial. Without this, governance would call +// the comptroller directly (no snapshot stored). +const GOVERNANCE_EBRAKE_PERMS = [ + "pauseBorrow(address)", + "pauseSupply(address)", + "pauseRedeem(address)", + "pauseTransfer(address)", + "pauseFlashLoan()", + "pauseActions(address[],uint8[])", + "setMarketBorrowCaps(address[],uint256[])", + "setMarketSupplyCaps(address[],uint256[])", + "disablePoolBorrow(uint96,address)", + "revokeFlashLoanAccess(address)", + "decreaseCF(address,uint256)", + "decreaseCF(address,uint96,uint256)", +]; + +const giveCallPermission = (contract: string, sig: string, account: string) => ({ + target: ACM, + signature: "giveCallPermission(address,string,address)", + params: [contract, sig, account], +}); + +const revokeCallPermission = (contract: string, sig: string, account: string) => ({ + target: ACM, + signature: "revokeCallPermission(address,string,address)", + params: [contract, sig, account], +}); + +export const vip661TestnetAddendum3 = () => { + const meta = { + version: "v2", + title: "VIP-661 Addendum 3: Update EBrake permissions on BSC Testnet", + description: `#### Summary + +This VIP updates the EBrake system on BSC Testnet with changes from +[venus-periphery#62](https://github.com/VenusProtocol/venus-periphery/pull/62). + +If approved, this VIP will: + +1. Upgrade EBrake proxy to the new implementation +2. Upgrade DeviationSentinel proxy to the new implementation (calls \`decreaseCF\` instead of \`setCFZero\`) +3. Revoke the old \`setCFZero(address)\` grant from DeviationSentinel on EBrake; grant \`decreaseCF(address,uint256)\` instead +4. Revoke the old \`resetMarketState(address)\` grants from all accounts (timelocks + keeper); replace with three + granular snapshot-reset functions: \`resetCFSnapshot\`, \`resetBorrowCapSnapshot\`, \`resetSupplyCapSnapshot\` +5. Grant EBrake two new Comptroller permissions needed by the new surgical controls: + \`setIsBorrowAllowed(uint96,address,bool)\` and \`setWhiteListFlashLoanAccount(address,bool)\` +6. Grant timelocks and keeper permission to call all EBrake action functions directly, so a Critical VIP + can route through EBrake and benefit from its snapshot mechanism + +#### References + +- [EBrake Contract](https://testnet.bscscan.com/address/${EBRAKE}) +- [DeviationSentinel Proxy](https://testnet.bscscan.com/address/${DEVIATION_SENTINEL}) +- [New EBrake Implementation](https://testnet.bscscan.com/address/${NEW_EBRAKE_IMPL}) +- [New DeviationSentinel Implementation](https://testnet.bscscan.com/address/${NEW_DEVIATION_SENTINEL_IMPL})`, + forDescription: "Execute this proposal", + againstDescription: "Do not execute this proposal", + abstainDescription: "Indifferent to execution", + }; + + return makeProposal( + [ + // 1. Upgrade EBrake proxy to new implementation + { target: PROXY_ADMIN, signature: "upgrade(address,address)", params: [EBRAKE, NEW_EBRAKE_IMPL] }, + + // 2. Upgrade DeviationSentinel proxy to new implementation + { + target: PROXY_ADMIN, + signature: "upgrade(address,address)", + params: [DEVIATION_SENTINEL, NEW_DEVIATION_SENTINEL_IMPL], + }, + + // 3. Swap setCFZero → decreaseCF for DeviationSentinel on EBrake + revokeCallPermission(EBRAKE, "setCFZero(address)", DEVIATION_SENTINEL), + giveCallPermission(EBRAKE, "decreaseCF(address,uint256)", DEVIATION_SENTINEL), + + // 4. Revoke resetMarketState; grant three granular reset functions in its place + // Timelocks were granted at address(0) in bsctestnet.ts. + // NormalTimelock + Keeper were additionally granted at EBRAKE specifically in Addendum 1. + ...TIMELOCKS.map(account => revokeCallPermission(ADDR_ZERO, "resetMarketState(address)", account)), + revokeCallPermission(EBRAKE, "resetMarketState(address)", NORMAL_TIMELOCK), + revokeCallPermission(EBRAKE, "resetMarketState(address)", KEEPER_ADDRESS), + ...RESET_ACCOUNTS.flatMap(account => RESET_PERMS.map(sig => giveCallPermission(ADDR_ZERO, sig, account))), + + // 5. Grant EBrake new Comptroller permissions for the new surgical controls + ...NEW_EBRAKE_COMPTROLLER_PERMS.map(sig => giveCallPermission(ADDR_ZERO, sig, EBRAKE)), + + // 6. Grant timelocks and keeper permission on all EBrake action functions + ...RESET_ACCOUNTS.flatMap(account => + GOVERNANCE_EBRAKE_PERMS.map(sig => giveCallPermission(ADDR_ZERO, sig, account)), + ), + ], + meta, + ProposalType.REGULAR, + ); +}; + +export default vip661TestnetAddendum3; From 8db24cff31e48dc41c61a6a8c1841858277972c5 Mon Sep 17 00:00:00 2001 From: Debugger022 Date: Fri, 10 Apr 2026 12:33:11 +0530 Subject: [PATCH 10/11] feat: grant Venus team multisig EBrake action permissions on BSC mainnet Add MULTISIG (0xCCa5a587) to VIP-661 so the Venus team can call all EBrake action functions directly for emergency pausing (Phase 0). Updates simulation with pre/post permission checks and bumps RoleGranted count from 71 to 83. --- simulations/vip-661/bscmainnet.ts | 18 +++++++++++++++++- vips/vip-661/bscmainnet.ts | 10 +++++++++- 2 files changed, 26 insertions(+), 2 deletions(-) diff --git a/simulations/vip-661/bscmainnet.ts b/simulations/vip-661/bscmainnet.ts index ff359a517..29a2f940f 100644 --- a/simulations/vip-661/bscmainnet.ts +++ b/simulations/vip-661/bscmainnet.ts @@ -11,6 +11,7 @@ import vip661, { CORE_POOL_COMPTROLLER, DEVIATION_SENTINEL, EBRAKE, + MULTISIG, NEW_DEVIATION_SENTINEL_IMPL, PROXY_ADMIN, } from "../../vips/vip-661/bscmainnet"; @@ -119,6 +120,13 @@ forking(91124514, async () => { } }); + it("Multisig should not yet have EBrake action permissions", async () => { + const acm = accessControlManager.connect(impersonatedEBrake); + for (const sig of GOVERNANCE_EBRAKE_PERMS) { + expect(await acm.isAllowedToCall(MULTISIG, sig)).to.equal(false, `unexpected permission: ${sig} for multisig`); + } + }); + it("DeviationSentinel should not yet have any permissions on EBrake", async () => { const acm = accessControlManager.connect(impersonatedEBrake); for (const sig of SENTINEL_EBRAKE_PERMS) { @@ -141,8 +149,9 @@ forking(91124514, async () => { // - 12 granular reset functions (3 × 4 accounts: Guardian + 3 timelocks) // - 3 DeviationSentinel on EBrake (pauseBorrow, pauseSupply, decreaseCF) // - 48 governance EBrake action functions (12 functions × 4 accounts) + // - 12 EBrake action functions for Venus team multisig (emergency pausing, Phase 0) // RoleRevoked: 3 (DeviationSentinel direct comptroller perms from VIP-590) - await expectEvents(txResponse, [ACCESS_CONTROL_MANAGER_ABI], ["RoleGranted"], [71]); + await expectEvents(txResponse, [ACCESS_CONTROL_MANAGER_ABI], ["RoleGranted"], [83]); await expectEvents(txResponse, [ACCESS_CONTROL_MANAGER_ABI], ["RoleRevoked"], [3]); }, }); @@ -177,6 +186,13 @@ forking(91124514, async () => { } }); + it("Multisig should have all EBrake action permissions for emergency pausing", async () => { + const acm = accessControlManager.connect(impersonatedEBrake); + for (const sig of GOVERNANCE_EBRAKE_PERMS) { + expect(await acm.isAllowedToCall(MULTISIG, sig)).to.equal(true, `missing permission: ${sig} for multisig`); + } + }); + it("DeviationSentinel should have pauseBorrow / pauseSupply / decreaseCF permissions on EBrake", async () => { const acm = accessControlManager.connect(impersonatedEBrake); for (const sig of SENTINEL_EBRAKE_PERMS) { diff --git a/vips/vip-661/bscmainnet.ts b/vips/vip-661/bscmainnet.ts index f457a74e2..e30bd232b 100644 --- a/vips/vip-661/bscmainnet.ts +++ b/vips/vip-661/bscmainnet.ts @@ -18,6 +18,8 @@ export const PROXY_ADMIN = "0x6beb6d2695b67feb73ad4f172e8e2975497187e4"; export const CORE_POOL_COMPTROLLER = NETWORK_ADDRESSES.bscmainnet.UNITROLLER; // Access Control Manager export const ACM = NETWORK_ADDRESSES.bscmainnet.ACCESS_CONTROL_MANAGER; +// Venus team multisig — can call EBrake action functions directly for emergency pausing (Phase 0) +export const MULTISIG = "0xCCa5a587eBDBe80f23c8610F2e53B03158e62948"; const TIMELOCKS = [NORMAL_TIMELOCK, FAST_TRACK_TIMELOCK, CRITICAL_TIMELOCK]; @@ -106,6 +108,8 @@ If approved, this VIP will: functions directly, so a Critical VIP can route through EBrake and benefit from its snapshot mechanism 6. Revoke DeviationSentinel's existing direct comptroller permissions (granted in VIP-590), since the sentinel now goes through EBrake +7. Grant the Venus team multisig (\`${MULTISIG}\`) permission to call all EBrake action functions directly, + enabling the team to pause the protocol in emergency conditions (Phase 0) #### Description @@ -123,7 +127,8 @@ values. Snapshots are first-write-wins and cleared via the three granular reset - [VIP Pull Request](https://github.com/VenusProtocol/vips/pull/694) - [DeviationSentinel Proxy](https://bscscan.com/address/${DEVIATION_SENTINEL}) - [New DeviationSentinel Implementation](https://bscscan.com/address/${NEW_DEVIATION_SENTINEL_IMPL}) -- [EBrake Contract](https://bscscan.com/address/${EBRAKE})`, +- [EBrake Contract](https://bscscan.com/address/${EBRAKE}) +- [Venus Team Multisig](https://bscscan.com/address/${MULTISIG})`, forDescription: "Execute this proposal", againstDescription: "Do not execute this proposal", abstainDescription: "Indifferent to execution", @@ -156,6 +161,9 @@ values. Snapshots are first-write-wins and cleared via the three granular reset ...SENTINEL_COMPTROLLER_PERMS_TO_REVOKE.map(sig => revokeCallPermission(CORE_POOL_COMPTROLLER, sig, DEVIATION_SENTINEL), ), + + // 7. Grant Venus team multisig permission on all EBrake action functions (emergency pausing, Phase 0) + ...GOVERNANCE_EBRAKE_PERMS.map(sig => giveCallPermission(EBRAKE, sig, MULTISIG)), ], meta, ProposalType.REGULAR, From 3de597fe0a1f76a5b0e3ea6641077fc302928a7c Mon Sep 17 00:00:00 2001 From: Debugger022 Date: Fri, 10 Apr 2026 18:25:02 +0530 Subject: [PATCH 11/11] fix: set deployment addresses and fix pre-VIP permission checks for VIP-661 Fill in deployed contract addresses for EBrake and DeviationSentinel implementations on both mainnet and testnet, and set fork block numbers. Exclude setMarketBorrowCaps/setMarketSupplyCaps from pre-VIP assertions since governance already holds those grants at the comptroller level. --- simulations/vip-661/bscmainnet.ts | 9 +++- simulations/vip-661/bsctestnet-addendum-3.ts | 46 ++++++++++---------- vips/vip-661/bscmainnet.ts | 6 +-- vips/vip-661/bsctestnet-addendum-3.ts | 6 +-- 4 files changed, 34 insertions(+), 33 deletions(-) diff --git a/simulations/vip-661/bscmainnet.ts b/simulations/vip-661/bscmainnet.ts index 29a2f940f..9a94e4c7a 100644 --- a/simulations/vip-661/bscmainnet.ts +++ b/simulations/vip-661/bscmainnet.ts @@ -65,7 +65,7 @@ const SENTINEL_COMPTROLLER_PERMS_TO_REVOKE = [ "setCollateralFactor(uint96,address,uint256,uint256)", ]; -forking(91124514, async () => { +forking(91721610, async () => { let accessControlManager: Contract; let deviationSentinel: Contract; let proxyAdmin: Contract; @@ -110,8 +110,13 @@ forking(91124514, async () => { it("Guardian and governance timelocks should not yet have new EBrake function permissions", async () => { const acm = accessControlManager.connect(impersonatedEBrake); + // setMarketBorrowCaps/setMarketSupplyCaps share names with comptroller functions that governance + // already has granted at the comptroller address — skip those here to avoid false failures. + const ebrakeUnique = GOVERNANCE_EBRAKE_PERMS.filter( + sig => sig !== "setMarketBorrowCaps(address[],uint256[])" && sig !== "setMarketSupplyCaps(address[],uint256[])", + ); for (const account of [GUARDIAN, ...TIMELOCKS]) { - for (const sig of GOVERNANCE_EBRAKE_PERMS) { + for (const sig of ebrakeUnique) { expect(await acm.isAllowedToCall(account, sig)).to.equal( false, `unexpected permission: ${sig} for ${account}`, diff --git a/simulations/vip-661/bsctestnet-addendum-3.ts b/simulations/vip-661/bsctestnet-addendum-3.ts index bea9ba662..e02198433 100644 --- a/simulations/vip-661/bsctestnet-addendum-3.ts +++ b/simulations/vip-661/bsctestnet-addendum-3.ts @@ -50,8 +50,7 @@ const NEW_EBRAKE_COMPTROLLER_PERMS = [ "setWhiteListFlashLoanAccount(address,bool)", ]; -// TODO: update this block number to one after VIP-661 Addendum 2 executed and before Addendum 3 -forking(0, async () => { +forking(100849419, async () => { let accessControlManager: Contract; let proxyAdmin: Contract; let deviationSentinel: Contract; @@ -114,9 +113,14 @@ forking(0, async () => { } }); - it("Timelocks and keeper should not yet have new EBrake function permissions", async () => { + it("Timelocks and keeper should not yet have new EBrake-unique function permissions", async () => { + // setMarketBorrowCaps/setMarketSupplyCaps share names with comptroller functions that timelocks + // already have granted at address(0) from prior governance VIPs — skip those here. + const ebrakeUnique = GOVERNANCE_EBRAKE_PERMS.filter( + sig => sig !== "setMarketBorrowCaps(address[],uint256[])" && sig !== "setMarketSupplyCaps(address[],uint256[])", + ); for (const account of RESET_ACCOUNTS) { - for (const sig of GOVERNANCE_EBRAKE_PERMS) { + for (const sig of ebrakeUnique) { expect(await accessControlManager.hasPermission(account, ethers.constants.AddressZero, sig)).to.equal( false, `unexpected permission: ${sig} for ${account}`, @@ -126,26 +130,22 @@ forking(0, async () => { }); }); - testVip( - "VIP-661 Addendum 3: Apply Phase-0 Hashdit Audit Fixes (EBrake) on BSC Testnet", - await vip661TestnetAddendum3(), - { - callbackAfterExecution: async txResponse => { - // PermissionRevoked breakdown: - // - 1 setCFZero from DeviationSentinel on EBrake - // - 3 resetMarketState from [3 timelocks] at address(0) (bsctestnet.ts grants) - // - 1 resetMarketState from NormalTimelock at EBRAKE (Addendum 1 grant) - // - 1 resetMarketState from Keeper at EBRAKE (Addendum 1 grant) - // PermissionGranted breakdown: - // - 1 decreaseCF(address,uint256) for DeviationSentinel on EBrake - // - 12 granular reset functions (3 × 4 accounts) - // - 2 new Comptroller permissions for EBrake (setIsBorrowAllowed, setWhiteListFlashLoanAccount) - // - 48 governance EBrake action functions (12 functions × 4 accounts) - await expectEvents(txResponse, [ACCESS_CONTROL_MANAGER_ABI], ["PermissionGranted"], [63]); - await expectEvents(txResponse, [ACCESS_CONTROL_MANAGER_ABI], ["PermissionRevoked"], [6]); - }, + testVip("VIP-661 Addendum 3: Apply Phase-0 Audit Fixes (EBrake) on BSC Testnet", await vip661TestnetAddendum3(), { + callbackAfterExecution: async txResponse => { + // PermissionRevoked breakdown: + // - 1 setCFZero from DeviationSentinel on EBrake + // - 3 resetMarketState from [3 timelocks] at address(0) (bsctestnet.ts grants) + // - 1 resetMarketState from NormalTimelock at EBRAKE (Addendum 1 grant) + // - 1 resetMarketState from Keeper at EBRAKE (Addendum 1 grant) + // PermissionGranted breakdown: + // - 1 decreaseCF(address,uint256) for DeviationSentinel on EBrake + // - 12 granular reset functions (3 × 4 accounts) + // - 2 new Comptroller permissions for EBrake (setIsBorrowAllowed, setWhiteListFlashLoanAccount) + // - 48 governance EBrake action functions (12 functions × 4 accounts) + await expectEvents(txResponse, [ACCESS_CONTROL_MANAGER_ABI], ["PermissionGranted"], [63]); + await expectEvents(txResponse, [ACCESS_CONTROL_MANAGER_ABI], ["PermissionRevoked"], [6]); }, - ); + }); describe("Post-VIP behavior", () => { it("EBrake proxy should point to the audit-fixed implementation", async () => { diff --git a/vips/vip-661/bscmainnet.ts b/vips/vip-661/bscmainnet.ts index e30bd232b..51835795a 100644 --- a/vips/vip-661/bscmainnet.ts +++ b/vips/vip-661/bscmainnet.ts @@ -4,10 +4,8 @@ import { makeProposal } from "src/utils"; const { NORMAL_TIMELOCK, FAST_TRACK_TIMELOCK, CRITICAL_TIMELOCK, GUARDIAN } = NETWORK_ADDRESSES.bscmainnet; -// EBrake (TransparentUpgradeableProxy) — emergency action router -export const EBRAKE = "0x0000000000000000000000000000000000000000"; // TODO: set after deployment -// New DeviationSentinel implementation that routes through EBrake -export const NEW_DEVIATION_SENTINEL_IMPL = "0x0000000000000000000000000000000000000000"; // TODO: set after deployment +export const EBRAKE = "0x35eBaBB99c7Fb7ba0C90bCc26e5d55Cdf89C23Ec"; +export const NEW_DEVIATION_SENTINEL_IMPL = "0xc86153Ae39fc9B60Ff59E99cA75aAD5Ab9d28a87"; // DeviationSentinel proxy (deployed in VIP-590) export const DEVIATION_SENTINEL = "0x6599C15cc8407046CD91E5c0F8B7f765fF914870"; diff --git a/vips/vip-661/bsctestnet-addendum-3.ts b/vips/vip-661/bsctestnet-addendum-3.ts index d5afae0ae..3ed220da1 100644 --- a/vips/vip-661/bsctestnet-addendum-3.ts +++ b/vips/vip-661/bsctestnet-addendum-3.ts @@ -5,10 +5,8 @@ import { makeProposal } from "src/utils"; const { NORMAL_TIMELOCK, FAST_TRACK_TIMELOCK, CRITICAL_TIMELOCK } = NETWORK_ADDRESSES.bsctestnet; -// New EBrake implementation — TODO: set after PR #62 deployment -export const NEW_EBRAKE_IMPL = "0x0000000000000000000000000000000000000000"; -// New DeviationSentinel implementation (calls decreaseCF instead of setCFZero) — TODO: set after deployment -export const NEW_DEVIATION_SENTINEL_IMPL = "0x0000000000000000000000000000000000000000"; +export const NEW_EBRAKE_IMPL = "0x9cA0f0C412d2E8a2c4323c04214D811375c17B24"; +export const NEW_DEVIATION_SENTINEL_IMPL = "0x455C09D6BA46e7C9f80f29506DcC8E5F8378832a"; export const EBRAKE = "0x957c09e3Ac3d9e689244DC74307c94111FBa8B42"; export const DEVIATION_SENTINEL = "0x9245d72712548707809D66848e63B8E2B169F3c1";