From c8c6507eb0120f10844c60c0b0dddd756531ba3e Mon Sep 17 00:00:00 2001 From: Debugger022 Date: Wed, 22 Apr 2026 17:07:08 +0530 Subject: [PATCH 01/11] feat: add VIP-701 to configure tighten-only Executor on BSC Wires the Phase -1 Executor contract between off-chain signal monitors and EBrake. Grants monitor perms on Executor action handlers, Executor perms on EBrake, and Normal Timelock perm on setMarketConfig. Mainnet and testnet scaffolds with shared perm arrays imported from the VIP for single source of truth. --- .../vip-701/abi/AccessControlManager.json | 360 ++++++++++++++++++ simulations/vip-701/bscmainnet.ts | 91 +++++ simulations/vip-701/bsctestnet.ts | 90 +++++ vips/vip-701/bscmainnet.ts | 97 +++++ vips/vip-701/bsctestnet.ts | 81 ++++ 5 files changed, 719 insertions(+) create mode 100644 simulations/vip-701/abi/AccessControlManager.json create mode 100644 simulations/vip-701/bscmainnet.ts create mode 100644 simulations/vip-701/bsctestnet.ts create mode 100644 vips/vip-701/bscmainnet.ts create mode 100644 vips/vip-701/bsctestnet.ts diff --git a/simulations/vip-701/abi/AccessControlManager.json b/simulations/vip-701/abi/AccessControlManager.json new file mode 100644 index 000000000..4a118fcc4 --- /dev/null +++ b/simulations/vip-701/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-701/bscmainnet.ts b/simulations/vip-701/bscmainnet.ts new file mode 100644 index 000000000..d2419b466 --- /dev/null +++ b/simulations/vip-701/bscmainnet.ts @@ -0,0 +1,91 @@ +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 vip701, { + ACM, + EBRAKE, + EBRAKE_EXECUTOR_PERMS, + EXECUTOR, + EXECUTOR_GOVERNANCE_PERMS, + EXECUTOR_MONITOR_PERMS, + SIGNAL_MONITOR, +} from "../../vips/vip-701/bscmainnet"; +import ACCESS_CONTROL_MANAGER_ABI from "./abi/AccessControlManager.json"; + +const { NORMAL_TIMELOCK } = NETWORK_ADDRESSES.bscmainnet; + +// TODO: set to a block after Executor is deployed on BSC mainnet +const BLOCK_NUMBER = 0; + +forking(BLOCK_NUMBER, async () => { + let accessControlManager: Contract; + + // ACM checks msg.sender against the stored contract address — impersonate the host. + let impersonatedExecutor: SignerWithAddress; + let impersonatedEBrake: SignerWithAddress; + + before(async () => { + accessControlManager = await ethers.getContractAt(ACCESS_CONTROL_MANAGER_ABI, ACM); + + impersonatedExecutor = await initMainnetUser(EXECUTOR, ethers.utils.parseEther("1")); + impersonatedEBrake = await initMainnetUser(EBRAKE, ethers.utils.parseEther("1")); + }); + + describe("Pre-VIP behavior", () => { + it("Signal monitor should not yet have Executor action permissions", async () => { + const acm = accessControlManager.connect(impersonatedExecutor); + for (const sig of EXECUTOR_MONITOR_PERMS) { + expect(await acm.isAllowedToCall(SIGNAL_MONITOR, sig)).to.equal(false, `unexpected permission: ${sig}`); + } + }); + + it("Executor should not yet have EBrake permissions", async () => { + const acm = accessControlManager.connect(impersonatedEBrake); + for (const sig of EBRAKE_EXECUTOR_PERMS) { + expect(await acm.isAllowedToCall(EXECUTOR, sig)).to.equal(false, `unexpected permission: ${sig}`); + } + }); + + it("Normal Timelock should not yet have setMarketConfig permission on Executor", async () => { + const acm = accessControlManager.connect(impersonatedExecutor); + for (const sig of EXECUTOR_GOVERNANCE_PERMS) { + expect(await acm.isAllowedToCall(NORMAL_TIMELOCK, sig)).to.equal(false, `unexpected permission: ${sig}`); + } + }); + }); + + testVip("VIP-701 [BNB Chain] Configure tighten-only Executor", await vip701(), { + callbackAfterExecution: async txResponse => { + // RoleGranted: 4 (monitor on Executor) + 5 (Executor on EBrake) + 1 (timelock setMarketConfig) = 10 + await expectEvents(txResponse, [ACCESS_CONTROL_MANAGER_ABI], ["RoleGranted"], [10]); + }, + }); + + describe("Post-VIP behavior", () => { + it("Signal monitor should have all Executor action permissions", async () => { + const acm = accessControlManager.connect(impersonatedExecutor); + for (const sig of EXECUTOR_MONITOR_PERMS) { + expect(await acm.isAllowedToCall(SIGNAL_MONITOR, sig)).to.equal(true, `missing permission: ${sig}`); + } + }); + + it("Executor should have all EBrake permissions", async () => { + const acm = accessControlManager.connect(impersonatedEBrake); + for (const sig of EBRAKE_EXECUTOR_PERMS) { + expect(await acm.isAllowedToCall(EXECUTOR, sig)).to.equal(true, `missing permission: ${sig}`); + } + }); + + it("Normal Timelock should have setMarketConfig permission on Executor", async () => { + const acm = accessControlManager.connect(impersonatedExecutor); + for (const sig of EXECUTOR_GOVERNANCE_PERMS) { + expect(await acm.isAllowedToCall(NORMAL_TIMELOCK, sig)).to.equal(true, `missing permission: ${sig}`); + } + }); + }); +}); diff --git a/simulations/vip-701/bsctestnet.ts b/simulations/vip-701/bsctestnet.ts new file mode 100644 index 000000000..2f3e5bc8a --- /dev/null +++ b/simulations/vip-701/bsctestnet.ts @@ -0,0 +1,90 @@ +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 vip701Testnet, { + ACM, + EBRAKE, + EBRAKE_EXECUTOR_PERMS, + EXECUTOR, + EXECUTOR_GOVERNANCE_PERMS, + EXECUTOR_MONITOR_PERMS, + SIGNAL_MONITOR, +} from "../../vips/vip-701/bsctestnet"; +import ACCESS_CONTROL_MANAGER_ABI from "./abi/AccessControlManager.json"; + +const { NORMAL_TIMELOCK } = NETWORK_ADDRESSES.bsctestnet; + +// TODO: set to a block after Executor is deployed on BSC testnet +const BLOCK_NUMBER = 0; + +forking(BLOCK_NUMBER, async () => { + let accessControlManager: Contract; + + let impersonatedExecutor: SignerWithAddress; + let impersonatedEBrake: SignerWithAddress; + + before(async () => { + accessControlManager = await ethers.getContractAt(ACCESS_CONTROL_MANAGER_ABI, ACM); + + impersonatedExecutor = await initMainnetUser(EXECUTOR, ethers.utils.parseEther("1")); + impersonatedEBrake = await initMainnetUser(EBRAKE, ethers.utils.parseEther("1")); + }); + + describe("Pre-VIP behavior", () => { + it("Signal monitor should not yet have Executor action permissions", async () => { + const acm = accessControlManager.connect(impersonatedExecutor); + for (const sig of EXECUTOR_MONITOR_PERMS) { + expect(await acm.isAllowedToCall(SIGNAL_MONITOR, sig)).to.equal(false, `unexpected permission: ${sig}`); + } + }); + + it("Executor should not yet have EBrake permissions", async () => { + const acm = accessControlManager.connect(impersonatedEBrake); + for (const sig of EBRAKE_EXECUTOR_PERMS) { + expect(await acm.isAllowedToCall(EXECUTOR, sig)).to.equal(false, `unexpected permission: ${sig}`); + } + }); + + it("Normal Timelock should not yet have setMarketConfig permission on Executor", async () => { + const acm = accessControlManager.connect(impersonatedExecutor); + for (const sig of EXECUTOR_GOVERNANCE_PERMS) { + expect(await acm.isAllowedToCall(NORMAL_TIMELOCK, sig)).to.equal(false, `unexpected permission: ${sig}`); + } + }); + }); + + testVip("VIP-701 [BNB Testnet] Configure tighten-only Executor", await vip701Testnet(), { + callbackAfterExecution: async txResponse => { + // RoleGranted: 4 (monitor on Executor) + 5 (Executor on EBrake) + 1 (timelock setMarketConfig) = 10 + await expectEvents(txResponse, [ACCESS_CONTROL_MANAGER_ABI], ["RoleGranted"], [10]); + }, + }); + + describe("Post-VIP behavior", () => { + it("Signal monitor should have all Executor action permissions", async () => { + const acm = accessControlManager.connect(impersonatedExecutor); + for (const sig of EXECUTOR_MONITOR_PERMS) { + expect(await acm.isAllowedToCall(SIGNAL_MONITOR, sig)).to.equal(true, `missing permission: ${sig}`); + } + }); + + it("Executor should have all EBrake permissions", async () => { + const acm = accessControlManager.connect(impersonatedEBrake); + for (const sig of EBRAKE_EXECUTOR_PERMS) { + expect(await acm.isAllowedToCall(EXECUTOR, sig)).to.equal(true, `missing permission: ${sig}`); + } + }); + + it("Normal Timelock should have setMarketConfig permission on Executor", async () => { + const acm = accessControlManager.connect(impersonatedExecutor); + for (const sig of EXECUTOR_GOVERNANCE_PERMS) { + expect(await acm.isAllowedToCall(NORMAL_TIMELOCK, sig)).to.equal(true, `missing permission: ${sig}`); + } + }); + }); +}); diff --git a/vips/vip-701/bscmainnet.ts b/vips/vip-701/bscmainnet.ts new file mode 100644 index 000000000..cb904efbe --- /dev/null +++ b/vips/vip-701/bscmainnet.ts @@ -0,0 +1,97 @@ +import { NETWORK_ADDRESSES } from "src/networkAddresses"; +import { ProposalType } from "src/types"; +import { makeProposal } from "src/utils"; + +const { NORMAL_TIMELOCK } = NETWORK_ADDRESSES.bscmainnet; + +// Access Control Manager +export const ACM = NETWORK_ADDRESSES.bscmainnet.ACCESS_CONTROL_MANAGER; + +// EBrake (configured in VIP-610) +export const EBRAKE = "0x35eBaBB99c7Fb7ba0C90bCc26e5d55Cdf89C23Ec"; + +// Executor — tighten-only validation layer between off-chain signal monitors and EBrake +// TODO: replace with deployed Executor address once deployment VIP merges +export const EXECUTOR = "0x0000000000000000000000000000000000000000"; + +// Off-chain signal monitor authorized to call Executor action handlers +// TODO: replace with final monitor EOA/contract address +export const SIGNAL_MONITOR = "0x0000000000000000000000000000000000000000"; + +// Executor action functions the signal monitor invokes +export const EXECUTOR_MONITOR_PERMS = [ + "handleLTVAdjust(address,uint256)", + "handleCapAdjust(address,uint8,uint256)", + "handleSupplyHalt(address)", + "handleBorrowHalt(address)", +]; + +// Executor governance function — sets per-market bounds (originalLTV, minBorrowCap, +// minSupplyCap, originalBorrowCap, originalSupplyCap, enabled) +export const EXECUTOR_GOVERNANCE_PERMS = ["setMarketConfig(address,(uint256,uint256,uint256,uint256,uint256,bool))"]; + +// EBrake functions the Executor calls +export const EBRAKE_EXECUTOR_PERMS = [ + "pauseBorrow(address)", + "pauseSupply(address)", + "decreaseCF(address,uint256)", + "setMarketBorrowCaps(address[],uint256[])", + "setMarketSupplyCaps(address[],uint256[])", +]; + +const giveCallPermission = (contract: string, sig: string, account: string) => ({ + target: ACM, + signature: "giveCallPermission(address,string,address)", + params: [contract, sig, account], +}); + +export const vip701 = () => { + const meta = { + version: "v2", + title: "VIP-701 [BNB Chain] Configure tighten-only Executor for signal-driven risk parameter control", + description: `#### Description + +This VIP configures the **Executor** contract on BNB Chain mainnet — the validation layer between off-chain signal monitors and EBrake. It validates bounds on-chain and routes tightening actions to EBrake; it cannot loosen parameters. Recovery is exclusively through governance VIPs. + +Depends on: VIP-610 (EBrake configuration), VPD-984 (EBrake Phase-0). + +#### Proposed Changes + +**1. Grant Signal Monitor permissions on Executor action handlers** + +- Authorize the off-chain monitor to call \`handleLTVAdjust\`, \`handleCapAdjust\`, \`handleSupplyHalt\`, \`handleBorrowHalt\` + +**2. Grant Executor permissions on EBrake** + +- Authorize the Executor to call \`pauseBorrow\`, \`pauseSupply\`, \`decreaseCF\`, \`setMarketBorrowCaps\`, \`setMarketSupplyCaps\` on EBrake + +**3. Grant Normal Timelock permission to call \`setMarketConfig\` on Executor** + +- Lets governance set per-market bounds (\`originalLTV\`, \`minBorrowCap\`, \`minSupplyCap\`, \`originalBorrowCap\`, \`originalSupplyCap\`, \`enabled\`) + +#### References + +- [GitHub PR: VenusProtocol/venus-periphery#61](https://github.com/VenusProtocol/venus-periphery/pull/61) +- VPD-925 — Phase -1 Executor`, + forDescription: "I agree that Venus Protocol should proceed with this proposal", + againstDescription: "I do not think that Venus Protocol should proceed with this proposal", + abstainDescription: "I am indifferent to whether Venus Protocol proceeds or not", + }; + + return makeProposal( + [ + // 1. Signal monitor → Executor action handlers + ...EXECUTOR_MONITOR_PERMS.map(sig => giveCallPermission(EXECUTOR, sig, SIGNAL_MONITOR)), + + // 2. Executor → EBrake + ...EBRAKE_EXECUTOR_PERMS.map(sig => giveCallPermission(EBRAKE, sig, EXECUTOR)), + + // 3. Normal Timelock → Executor governance function + ...EXECUTOR_GOVERNANCE_PERMS.map(sig => giveCallPermission(EXECUTOR, sig, NORMAL_TIMELOCK)), + ], + meta, + ProposalType.REGULAR, + ); +}; + +export default vip701; diff --git a/vips/vip-701/bsctestnet.ts b/vips/vip-701/bsctestnet.ts new file mode 100644 index 000000000..82c175485 --- /dev/null +++ b/vips/vip-701/bsctestnet.ts @@ -0,0 +1,81 @@ +import { NETWORK_ADDRESSES } from "src/networkAddresses"; +import { ProposalType } from "src/types"; +import { makeProposal } from "src/utils"; + +const { NORMAL_TIMELOCK } = NETWORK_ADDRESSES.bsctestnet; + +// Access Control Manager (BSC testnet) +export const ACM = "0x45f8a08F534f34A97187626E05d4b6648Eeaa9AA"; + +// EBrake testnet (deployed in VIP-661 Testnet addendum) +export const EBRAKE = "0x957c09e3Ac3d9e689244DC74307c94111FBa8B42"; + +// Executor — tighten-only validation layer between off-chain signal monitors and EBrake +// TODO: replace with deployed Executor testnet address +export const EXECUTOR = "0x0000000000000000000000000000000000000000"; + +// Off-chain signal monitor authorized to call Executor action handlers +// TODO: replace with final monitor EOA/contract address on testnet +export const SIGNAL_MONITOR = "0x0000000000000000000000000000000000000000"; + +export const EXECUTOR_MONITOR_PERMS = [ + "handleLTVAdjust(address,uint256)", + "handleCapAdjust(address,uint8,uint256)", + "handleSupplyHalt(address)", + "handleBorrowHalt(address)", +]; + +export const EXECUTOR_GOVERNANCE_PERMS = ["setMarketConfig(address,(uint256,uint256,uint256,uint256,uint256,bool))"]; + +export const EBRAKE_EXECUTOR_PERMS = [ + "pauseBorrow(address)", + "pauseSupply(address)", + "decreaseCF(address,uint256)", + "setMarketBorrowCaps(address[],uint256[])", + "setMarketSupplyCaps(address[],uint256[])", +]; + +const giveCallPermission = (contract: string, sig: string, account: string) => ({ + target: ACM, + signature: "giveCallPermission(address,string,address)", + params: [contract, sig, account], +}); + +export const vip701Testnet = () => { + const meta = { + version: "v2", + title: "VIP-701 [BNB Testnet] Configure tighten-only Executor for signal-driven risk parameter control", + description: `#### Summary + +Configures the **Executor** contract on BSC testnet — tighten-only validation layer between off-chain signal monitors and EBrake. + +1. Grant signal monitor permissions on Executor action handlers (handleLTVAdjust, handleCapAdjust, handleSupplyHalt, handleBorrowHalt) +2. Grant Executor permissions on EBrake (pauseBorrow, pauseSupply, decreaseCF, setMarketBorrowCaps, setMarketSupplyCaps) +3. Grant Normal Timelock permission to call setMarketConfig on Executor + +#### References + +- [GitHub PR: VenusProtocol/venus-periphery#61](https://github.com/VenusProtocol/venus-periphery/pull/61) +- VPD-925 — Phase -1 Executor`, + forDescription: "Execute this proposal", + againstDescription: "Do not execute this proposal", + abstainDescription: "Indifferent to execution", + }; + + return makeProposal( + [ + // 1. Signal monitor → Executor action handlers + ...EXECUTOR_MONITOR_PERMS.map(sig => giveCallPermission(EXECUTOR, sig, SIGNAL_MONITOR)), + + // 2. Executor → EBrake + ...EBRAKE_EXECUTOR_PERMS.map(sig => giveCallPermission(EBRAKE, sig, EXECUTOR)), + + // 3. Normal Timelock → Executor governance function + ...EXECUTOR_GOVERNANCE_PERMS.map(sig => giveCallPermission(EXECUTOR, sig, NORMAL_TIMELOCK)), + ], + meta, + ProposalType.REGULAR, + ); +}; + +export default vip701Testnet; From 355eea4d7b17a2b92c6753261a20f17e69c34a9a Mon Sep 17 00:00:00 2001 From: Debugger022 Date: Wed, 22 Apr 2026 21:42:17 +0530 Subject: [PATCH 02/11] fix(vip-701): correct ACM strings and broaden setMarketConfig grants MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - `handleSupplyHalt` → `handleSupplyCapExceeding` (orphaned role: no such function on Executor) - `handleBorrowHalt` → `handleBorrowCapExceeding` (same) - `setMarketConfig` tuple: 6-field phantom → 3-field `(minBorrowCap, minSupplyCap, enabled)` matching IExecutor.MarketConfig - Broaden `setMarketConfig` grant from Normal-only to Guardian + all three timelocks --- simulations/vip-701/bscmainnet.ts | 27 +++++++++++++++++---------- simulations/vip-701/bsctestnet.ts | 26 +++++++++++++++++--------- vips/vip-701/bscmainnet.ts | 23 ++++++++++++----------- vips/vip-701/bsctestnet.ts | 18 ++++++++++-------- 4 files changed, 56 insertions(+), 38 deletions(-) diff --git a/simulations/vip-701/bscmainnet.ts b/simulations/vip-701/bscmainnet.ts index d2419b466..5bbe4eace 100644 --- a/simulations/vip-701/bscmainnet.ts +++ b/simulations/vip-701/bscmainnet.ts @@ -17,7 +17,8 @@ import vip701, { } from "../../vips/vip-701/bscmainnet"; import ACCESS_CONTROL_MANAGER_ABI from "./abi/AccessControlManager.json"; -const { NORMAL_TIMELOCK } = NETWORK_ADDRESSES.bscmainnet; +const { NORMAL_TIMELOCK, FAST_TRACK_TIMELOCK, CRITICAL_TIMELOCK, GUARDIAN } = NETWORK_ADDRESSES.bscmainnet; +const EXECUTOR_GOVERNANCE_ACCOUNTS = [GUARDIAN, NORMAL_TIMELOCK, FAST_TRACK_TIMELOCK, CRITICAL_TIMELOCK]; // TODO: set to a block after Executor is deployed on BSC mainnet const BLOCK_NUMBER = 0; @@ -25,7 +26,6 @@ const BLOCK_NUMBER = 0; forking(BLOCK_NUMBER, async () => { let accessControlManager: Contract; - // ACM checks msg.sender against the stored contract address — impersonate the host. let impersonatedExecutor: SignerWithAddress; let impersonatedEBrake: SignerWithAddress; @@ -51,18 +51,23 @@ forking(BLOCK_NUMBER, async () => { } }); - it("Normal Timelock should not yet have setMarketConfig permission on Executor", async () => { + it("Guardian and Timelocks should not yet have setMarketConfig permission on Executor", async () => { const acm = accessControlManager.connect(impersonatedExecutor); - for (const sig of EXECUTOR_GOVERNANCE_PERMS) { - expect(await acm.isAllowedToCall(NORMAL_TIMELOCK, sig)).to.equal(false, `unexpected permission: ${sig}`); + for (const account of EXECUTOR_GOVERNANCE_ACCOUNTS) { + for (const sig of EXECUTOR_GOVERNANCE_PERMS) { + expect(await acm.isAllowedToCall(account, sig)).to.equal( + false, + `unexpected permission ${sig} for ${account}`, + ); + } } }); }); testVip("VIP-701 [BNB Chain] Configure tighten-only Executor", await vip701(), { callbackAfterExecution: async txResponse => { - // RoleGranted: 4 (monitor on Executor) + 5 (Executor on EBrake) + 1 (timelock setMarketConfig) = 10 - await expectEvents(txResponse, [ACCESS_CONTROL_MANAGER_ABI], ["RoleGranted"], [10]); + // RoleGranted: 4 (monitor on Executor) + 5 (Executor on EBrake) + 4 (Guardian + 3 timelocks setMarketConfig) = 13 + await expectEvents(txResponse, [ACCESS_CONTROL_MANAGER_ABI], ["RoleGranted"], [13]); }, }); @@ -81,10 +86,12 @@ forking(BLOCK_NUMBER, async () => { } }); - it("Normal Timelock should have setMarketConfig permission on Executor", async () => { + it("Guardian and Timelocks should have setMarketConfig permission on Executor", async () => { const acm = accessControlManager.connect(impersonatedExecutor); - for (const sig of EXECUTOR_GOVERNANCE_PERMS) { - expect(await acm.isAllowedToCall(NORMAL_TIMELOCK, sig)).to.equal(true, `missing permission: ${sig}`); + for (const account of EXECUTOR_GOVERNANCE_ACCOUNTS) { + for (const sig of EXECUTOR_GOVERNANCE_PERMS) { + expect(await acm.isAllowedToCall(account, sig)).to.equal(true, `missing permission ${sig} for ${account}`); + } } }); }); diff --git a/simulations/vip-701/bsctestnet.ts b/simulations/vip-701/bsctestnet.ts index 2f3e5bc8a..02e51792f 100644 --- a/simulations/vip-701/bsctestnet.ts +++ b/simulations/vip-701/bsctestnet.ts @@ -17,7 +17,8 @@ import vip701Testnet, { } from "../../vips/vip-701/bsctestnet"; import ACCESS_CONTROL_MANAGER_ABI from "./abi/AccessControlManager.json"; -const { NORMAL_TIMELOCK } = NETWORK_ADDRESSES.bsctestnet; +const { NORMAL_TIMELOCK, FAST_TRACK_TIMELOCK, CRITICAL_TIMELOCK, GUARDIAN } = NETWORK_ADDRESSES.bsctestnet; +const EXECUTOR_GOVERNANCE_ACCOUNTS = [GUARDIAN, NORMAL_TIMELOCK, FAST_TRACK_TIMELOCK, CRITICAL_TIMELOCK]; // TODO: set to a block after Executor is deployed on BSC testnet const BLOCK_NUMBER = 0; @@ -50,18 +51,23 @@ forking(BLOCK_NUMBER, async () => { } }); - it("Normal Timelock should not yet have setMarketConfig permission on Executor", async () => { + it("Guardian and Timelocks should not yet have setMarketConfig permission on Executor", async () => { const acm = accessControlManager.connect(impersonatedExecutor); - for (const sig of EXECUTOR_GOVERNANCE_PERMS) { - expect(await acm.isAllowedToCall(NORMAL_TIMELOCK, sig)).to.equal(false, `unexpected permission: ${sig}`); + for (const account of EXECUTOR_GOVERNANCE_ACCOUNTS) { + for (const sig of EXECUTOR_GOVERNANCE_PERMS) { + expect(await acm.isAllowedToCall(account, sig)).to.equal( + false, + `unexpected permission ${sig} for ${account}`, + ); + } } }); }); testVip("VIP-701 [BNB Testnet] Configure tighten-only Executor", await vip701Testnet(), { callbackAfterExecution: async txResponse => { - // RoleGranted: 4 (monitor on Executor) + 5 (Executor on EBrake) + 1 (timelock setMarketConfig) = 10 - await expectEvents(txResponse, [ACCESS_CONTROL_MANAGER_ABI], ["RoleGranted"], [10]); + // RoleGranted: 4 (monitor on Executor) + 5 (Executor on EBrake) + 4 (Guardian + 3 timelocks setMarketConfig) = 13 + await expectEvents(txResponse, [ACCESS_CONTROL_MANAGER_ABI], ["RoleGranted"], [13]); }, }); @@ -80,10 +86,12 @@ forking(BLOCK_NUMBER, async () => { } }); - it("Normal Timelock should have setMarketConfig permission on Executor", async () => { + it("Guardian and Timelocks should have setMarketConfig permission on Executor", async () => { const acm = accessControlManager.connect(impersonatedExecutor); - for (const sig of EXECUTOR_GOVERNANCE_PERMS) { - expect(await acm.isAllowedToCall(NORMAL_TIMELOCK, sig)).to.equal(true, `missing permission: ${sig}`); + for (const account of EXECUTOR_GOVERNANCE_ACCOUNTS) { + for (const sig of EXECUTOR_GOVERNANCE_PERMS) { + expect(await acm.isAllowedToCall(account, sig)).to.equal(true, `missing permission ${sig} for ${account}`); + } } }); }); diff --git a/vips/vip-701/bscmainnet.ts b/vips/vip-701/bscmainnet.ts index cb904efbe..97885c647 100644 --- a/vips/vip-701/bscmainnet.ts +++ b/vips/vip-701/bscmainnet.ts @@ -2,7 +2,7 @@ import { NETWORK_ADDRESSES } from "src/networkAddresses"; import { ProposalType } from "src/types"; import { makeProposal } from "src/utils"; -const { NORMAL_TIMELOCK } = NETWORK_ADDRESSES.bscmainnet; +const { NORMAL_TIMELOCK, FAST_TRACK_TIMELOCK, CRITICAL_TIMELOCK, GUARDIAN } = NETWORK_ADDRESSES.bscmainnet; // Access Control Manager export const ACM = NETWORK_ADDRESSES.bscmainnet.ACCESS_CONTROL_MANAGER; @@ -22,13 +22,12 @@ export const SIGNAL_MONITOR = "0x0000000000000000000000000000000000000000"; export const EXECUTOR_MONITOR_PERMS = [ "handleLTVAdjust(address,uint256)", "handleCapAdjust(address,uint8,uint256)", - "handleSupplyHalt(address)", - "handleBorrowHalt(address)", + "handleSupplyCapExceeding(address)", + "handleBorrowCapExceeding(address)", ]; -// Executor governance function — sets per-market bounds (originalLTV, minBorrowCap, -// minSupplyCap, originalBorrowCap, originalSupplyCap, enabled) -export const EXECUTOR_GOVERNANCE_PERMS = ["setMarketConfig(address,(uint256,uint256,uint256,uint256,uint256,bool))"]; +// Executor governance function — sets per-market bounds (minBorrowCap, minSupplyCap, enabled) +export const EXECUTOR_GOVERNANCE_PERMS = ["setMarketConfig(address,(uint256,uint256,bool))"]; // EBrake functions the Executor calls export const EBRAKE_EXECUTOR_PERMS = [ @@ -59,15 +58,15 @@ Depends on: VIP-610 (EBrake configuration), VPD-984 (EBrake Phase-0). **1. Grant Signal Monitor permissions on Executor action handlers** -- Authorize the off-chain monitor to call \`handleLTVAdjust\`, \`handleCapAdjust\`, \`handleSupplyHalt\`, \`handleBorrowHalt\` +- Authorize the off-chain monitor to call \`handleLTVAdjust\`, \`handleCapAdjust\`, \`handleSupplyCapExceeding\`, \`handleBorrowCapExceeding\` **2. Grant Executor permissions on EBrake** - Authorize the Executor to call \`pauseBorrow\`, \`pauseSupply\`, \`decreaseCF\`, \`setMarketBorrowCaps\`, \`setMarketSupplyCaps\` on EBrake -**3. Grant Normal Timelock permission to call \`setMarketConfig\` on Executor** +**3. Grant Guardian and all three Timelocks (Normal, Fast Track, Critical) permission to call \`setMarketConfig\` on Executor** -- Lets governance set per-market bounds (\`originalLTV\`, \`minBorrowCap\`, \`minSupplyCap\`, \`originalBorrowCap\`, \`originalSupplyCap\`, \`enabled\`) +- Lets governance set per-market bounds (\`minBorrowCap\`, \`minSupplyCap\`, \`enabled\`). Granting to all three timelocks + Guardian mirrors VIP-610 and lets Critical (~1h) disable a compromised market's automation instead of waiting 48h on Normal. #### References @@ -86,8 +85,10 @@ Depends on: VIP-610 (EBrake configuration), VPD-984 (EBrake Phase-0). // 2. Executor → EBrake ...EBRAKE_EXECUTOR_PERMS.map(sig => giveCallPermission(EBRAKE, sig, EXECUTOR)), - // 3. Normal Timelock → Executor governance function - ...EXECUTOR_GOVERNANCE_PERMS.map(sig => giveCallPermission(EXECUTOR, sig, NORMAL_TIMELOCK)), + // 3. Guardian + all timelocks → Executor governance function + ...[GUARDIAN, NORMAL_TIMELOCK, FAST_TRACK_TIMELOCK, CRITICAL_TIMELOCK].flatMap(account => + EXECUTOR_GOVERNANCE_PERMS.map(sig => giveCallPermission(EXECUTOR, sig, account)), + ), ], meta, ProposalType.REGULAR, diff --git a/vips/vip-701/bsctestnet.ts b/vips/vip-701/bsctestnet.ts index 82c175485..34b4f9d7f 100644 --- a/vips/vip-701/bsctestnet.ts +++ b/vips/vip-701/bsctestnet.ts @@ -2,7 +2,7 @@ import { NETWORK_ADDRESSES } from "src/networkAddresses"; import { ProposalType } from "src/types"; import { makeProposal } from "src/utils"; -const { NORMAL_TIMELOCK } = NETWORK_ADDRESSES.bsctestnet; +const { NORMAL_TIMELOCK, FAST_TRACK_TIMELOCK, CRITICAL_TIMELOCK, GUARDIAN } = NETWORK_ADDRESSES.bsctestnet; // Access Control Manager (BSC testnet) export const ACM = "0x45f8a08F534f34A97187626E05d4b6648Eeaa9AA"; @@ -21,11 +21,11 @@ export const SIGNAL_MONITOR = "0x0000000000000000000000000000000000000000"; export const EXECUTOR_MONITOR_PERMS = [ "handleLTVAdjust(address,uint256)", "handleCapAdjust(address,uint8,uint256)", - "handleSupplyHalt(address)", - "handleBorrowHalt(address)", + "handleSupplyCapExceeding(address)", + "handleBorrowCapExceeding(address)", ]; -export const EXECUTOR_GOVERNANCE_PERMS = ["setMarketConfig(address,(uint256,uint256,uint256,uint256,uint256,bool))"]; +export const EXECUTOR_GOVERNANCE_PERMS = ["setMarketConfig(address,(uint256,uint256,bool))"]; export const EBRAKE_EXECUTOR_PERMS = [ "pauseBorrow(address)", @@ -49,9 +49,9 @@ export const vip701Testnet = () => { Configures the **Executor** contract on BSC testnet — tighten-only validation layer between off-chain signal monitors and EBrake. -1. Grant signal monitor permissions on Executor action handlers (handleLTVAdjust, handleCapAdjust, handleSupplyHalt, handleBorrowHalt) +1. Grant signal monitor permissions on Executor action handlers (handleLTVAdjust, handleCapAdjust, handleSupplyCapExceeding, handleBorrowCapExceeding) 2. Grant Executor permissions on EBrake (pauseBorrow, pauseSupply, decreaseCF, setMarketBorrowCaps, setMarketSupplyCaps) -3. Grant Normal Timelock permission to call setMarketConfig on Executor +3. Grant Guardian and all three Timelocks (Normal, Fast Track, Critical) permission to call setMarketConfig on Executor #### References @@ -70,8 +70,10 @@ Configures the **Executor** contract on BSC testnet — tighten-only validation // 2. Executor → EBrake ...EBRAKE_EXECUTOR_PERMS.map(sig => giveCallPermission(EBRAKE, sig, EXECUTOR)), - // 3. Normal Timelock → Executor governance function - ...EXECUTOR_GOVERNANCE_PERMS.map(sig => giveCallPermission(EXECUTOR, sig, NORMAL_TIMELOCK)), + // 3. Guardian + all timelocks → Executor governance function + ...[GUARDIAN, NORMAL_TIMELOCK, FAST_TRACK_TIMELOCK, CRITICAL_TIMELOCK].flatMap(account => + EXECUTOR_GOVERNANCE_PERMS.map(sig => giveCallPermission(EXECUTOR, sig, account)), + ), ], meta, ProposalType.REGULAR, From 8bae3b027b20e27be4fb1583b9fe84e8a50030dc Mon Sep 17 00:00:00 2001 From: Debugger022 Date: Thu, 14 May 2026 19:19:07 +0530 Subject: [PATCH 03/11] feat(vip-701): add Executor and Signal Monitor addresses in BSC mainnet configuration --- simulations/vip-701/bscmainnet.ts | 4 ++-- vips/vip-701/bscmainnet.ts | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/simulations/vip-701/bscmainnet.ts b/simulations/vip-701/bscmainnet.ts index 5bbe4eace..32d40e0ac 100644 --- a/simulations/vip-701/bscmainnet.ts +++ b/simulations/vip-701/bscmainnet.ts @@ -20,8 +20,8 @@ import ACCESS_CONTROL_MANAGER_ABI from "./abi/AccessControlManager.json"; const { NORMAL_TIMELOCK, FAST_TRACK_TIMELOCK, CRITICAL_TIMELOCK, GUARDIAN } = NETWORK_ADDRESSES.bscmainnet; const EXECUTOR_GOVERNANCE_ACCOUNTS = [GUARDIAN, NORMAL_TIMELOCK, FAST_TRACK_TIMELOCK, CRITICAL_TIMELOCK]; -// TODO: set to a block after Executor is deployed on BSC mainnet -const BLOCK_NUMBER = 0; +// block after Executor is deployed on BSC mainnet +const BLOCK_NUMBER = 98248415; forking(BLOCK_NUMBER, async () => { let accessControlManager: Contract; diff --git a/vips/vip-701/bscmainnet.ts b/vips/vip-701/bscmainnet.ts index 97885c647..082314951 100644 --- a/vips/vip-701/bscmainnet.ts +++ b/vips/vip-701/bscmainnet.ts @@ -12,11 +12,11 @@ export const EBRAKE = "0x35eBaBB99c7Fb7ba0C90bCc26e5d55Cdf89C23Ec"; // Executor — tighten-only validation layer between off-chain signal monitors and EBrake // TODO: replace with deployed Executor address once deployment VIP merges -export const EXECUTOR = "0x0000000000000000000000000000000000000000"; +export const EXECUTOR = "0xDd541A1b065F9587b01815a390a4d4559D7b630F"; // Off-chain signal monitor authorized to call Executor action handlers // TODO: replace with final monitor EOA/contract address -export const SIGNAL_MONITOR = "0x0000000000000000000000000000000000000000"; +export const SIGNAL_MONITOR = "0x61859C84E0C6aB7B5A9801A962C660477f31a2D3"; // Executor action functions the signal monitor invokes export const EXECUTOR_MONITOR_PERMS = [ From 74f9c50cccd44c62d9c7945081ad92820a9b4cb0 Mon Sep 17 00:00:00 2001 From: Debugger022 Date: Thu, 14 May 2026 19:38:19 +0530 Subject: [PATCH 04/11] chore: remove TODO comments --- vips/vip-701/bscmainnet.ts | 2 -- 1 file changed, 2 deletions(-) diff --git a/vips/vip-701/bscmainnet.ts b/vips/vip-701/bscmainnet.ts index 082314951..3031ef8e8 100644 --- a/vips/vip-701/bscmainnet.ts +++ b/vips/vip-701/bscmainnet.ts @@ -11,11 +11,9 @@ export const ACM = NETWORK_ADDRESSES.bscmainnet.ACCESS_CONTROL_MANAGER; export const EBRAKE = "0x35eBaBB99c7Fb7ba0C90bCc26e5d55Cdf89C23Ec"; // Executor — tighten-only validation layer between off-chain signal monitors and EBrake -// TODO: replace with deployed Executor address once deployment VIP merges export const EXECUTOR = "0xDd541A1b065F9587b01815a390a4d4559D7b630F"; // Off-chain signal monitor authorized to call Executor action handlers -// TODO: replace with final monitor EOA/contract address export const SIGNAL_MONITOR = "0x61859C84E0C6aB7B5A9801A962C660477f31a2D3"; // Executor action functions the signal monitor invokes From 477fafed4ea34dcc7363f75bbc3eb1efcbbaa153 Mon Sep 17 00:00:00 2001 From: Debugger022 Date: Thu, 14 May 2026 19:40:37 +0530 Subject: [PATCH 05/11] feat: add Executor and Comptroller ABIs, enhance bscmainnet tests for market configuration and access control --- simulations/vip-701/abi/Comptroller.json | 31 +++ simulations/vip-701/abi/Executor.json | 267 +++++++++++++++++++++++ simulations/vip-701/bscmainnet.ts | 197 ++++++++++++++++- 3 files changed, 491 insertions(+), 4 deletions(-) create mode 100644 simulations/vip-701/abi/Comptroller.json create mode 100644 simulations/vip-701/abi/Executor.json diff --git a/simulations/vip-701/abi/Comptroller.json b/simulations/vip-701/abi/Comptroller.json new file mode 100644 index 000000000..f003a4837 --- /dev/null +++ b/simulations/vip-701/abi/Comptroller.json @@ -0,0 +1,31 @@ +[ + { + "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": [{ "internalType": "address", "name": "", "type": "address" }], + "name": "borrowCaps", + "outputs": [{ "internalType": "uint256", "name": "", "type": "uint256" }], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [{ "internalType": "address", "name": "", "type": "address" }], + "name": "supplyCaps", + "outputs": [{ "internalType": "uint256", "name": "", "type": "uint256" }], + "stateMutability": "view", + "type": "function" + } +] diff --git a/simulations/vip-701/abi/Executor.json b/simulations/vip-701/abi/Executor.json new file mode 100644 index 000000000..4a4cf23f0 --- /dev/null +++ b/simulations/vip-701/abi/Executor.json @@ -0,0 +1,267 @@ +[ + { + "inputs": [ + { "internalType": "contract IEBrake", "name": "eBrake_", "type": "address" }, + { "internalType": "contract ICorePoolComptroller", "name": "comptroller_", "type": "address" }, + { "internalType": "bool", "name": "isCorePool_", "type": "bool" } + ], + "stateMutability": "nonpayable", + "type": "constructor" + }, + { + "inputs": [ + { "internalType": "uint256", "name": "adjustedCap", "type": "uint256" }, + { "internalType": "uint256", "name": "minCap", "type": "uint256" } + ], + "name": "CapBelowMinimum", + "type": "error" + }, + { "inputs": [], "name": "CapNotBreached", "type": "error" }, + { + "inputs": [{ "internalType": "address", "name": "market", "type": "address" }], + "name": "MarketDisabled", + "type": "error" + }, + { + "inputs": [{ "internalType": "address", "name": "market", "type": "address" }], + "name": "MarketNotConfigured", + "type": "error" + }, + { + "inputs": [{ "internalType": "address", "name": "market", "type": "address" }], + "name": "MarketNotListed", + "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" } + ], + "name": "BorrowCapExceeding", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { "indexed": true, "internalType": "address", "name": "caller", "type": "address" }, + { "indexed": true, "internalType": "address", "name": "market", "type": "address" }, + { "indexed": false, "internalType": "enum IExecutor.CapType", "name": "capType", "type": "uint8" }, + { "indexed": false, "internalType": "uint256", "name": "oldCap", "type": "uint256" }, + { "indexed": false, "internalType": "uint256", "name": "newCap", "type": "uint256" } + ], + "name": "CapAdjusted", + "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": "caller", "type": "address" }, + { "indexed": true, "internalType": "address", "name": "market", "type": "address" }, + { "indexed": false, "internalType": "uint256", "name": "newLTV", "type": "uint256" } + ], + "name": "LTVAdjusted", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { "indexed": true, "internalType": "address", "name": "market", "type": "address" }, + { + "components": [ + { "internalType": "uint256", "name": "minBorrowCap", "type": "uint256" }, + { "internalType": "uint256", "name": "minSupplyCap", "type": "uint256" }, + { "internalType": "bool", "name": "enabled", "type": "bool" } + ], + "indexed": false, + "internalType": "struct IExecutor.MarketConfig", + "name": "config", + "type": "tuple" + } + ], + "name": "MarketConfigSet", + "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": true, "internalType": "address", "name": "market", "type": "address" } + ], + "name": "SupplyCapExceeding", + "type": "event" + }, + { + "inputs": [], + "name": "COMPTROLLER", + "outputs": [{ "internalType": "contract ICorePoolComptroller", "name": "", "type": "address" }], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "EBRAKE", + "outputs": [{ "internalType": "contract IEBrake", "name": "", "type": "address" }], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "IS_CORE_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": [{ "internalType": "address", "name": "market", "type": "address" }], + "name": "handleBorrowCapExceeding", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { "internalType": "address", "name": "market", "type": "address" }, + { "internalType": "enum IExecutor.CapType", "name": "capType", "type": "uint8" }, + { "internalType": "uint256", "name": "adjustedCap", "type": "uint256" } + ], + "name": "handleCapAdjust", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { "internalType": "address", "name": "market", "type": "address" }, + { "internalType": "uint256", "name": "adjustedLTV", "type": "uint256" } + ], + "name": "handleLTVAdjust", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [{ "internalType": "address", "name": "market", "type": "address" }], + "name": "handleSupplyCapExceeding", + "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": "marketConfigs", + "outputs": [ + { "internalType": "uint256", "name": "minBorrowCap", "type": "uint256" }, + { "internalType": "uint256", "name": "minSupplyCap", "type": "uint256" }, + { "internalType": "bool", "name": "enabled", "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": "address", "name": "accessControlManager_", "type": "address" }], + "name": "setAccessControlManager", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { "internalType": "address", "name": "market", "type": "address" }, + { + "components": [ + { "internalType": "uint256", "name": "minBorrowCap", "type": "uint256" }, + { "internalType": "uint256", "name": "minSupplyCap", "type": "uint256" }, + { "internalType": "bool", "name": "enabled", "type": "bool" } + ], + "internalType": "struct IExecutor.MarketConfig", + "name": "config", + "type": "tuple" + } + ], + "name": "setMarketConfig", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [{ "internalType": "address", "name": "newOwner", "type": "address" }], + "name": "transferOwnership", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + } +] diff --git a/simulations/vip-701/bscmainnet.ts b/simulations/vip-701/bscmainnet.ts index 32d40e0ac..803731594 100644 --- a/simulations/vip-701/bscmainnet.ts +++ b/simulations/vip-701/bscmainnet.ts @@ -1,6 +1,6 @@ import { SignerWithAddress } from "@nomiclabs/hardhat-ethers/signers"; import { expect } from "chai"; -import { Contract } from "ethers"; +import { BigNumber, Contract } from "ethers"; import { ethers } from "hardhat"; import { NETWORK_ADDRESSES } from "src/networkAddresses"; import { expectEvents, initMainnetUser } from "src/utils"; @@ -16,24 +16,49 @@ import vip701, { SIGNAL_MONITOR, } from "../../vips/vip-701/bscmainnet"; import ACCESS_CONTROL_MANAGER_ABI from "./abi/AccessControlManager.json"; +import COMPTROLLER_ABI from "./abi/Comptroller.json"; +import EXECUTOR_ABI from "./abi/Executor.json"; -const { NORMAL_TIMELOCK, FAST_TRACK_TIMELOCK, CRITICAL_TIMELOCK, GUARDIAN } = NETWORK_ADDRESSES.bscmainnet; +const { NORMAL_TIMELOCK, FAST_TRACK_TIMELOCK, CRITICAL_TIMELOCK, GUARDIAN, UNITROLLER } = NETWORK_ADDRESSES.bscmainnet; const EXECUTOR_GOVERNANCE_ACCOUNTS = [GUARDIAN, NORMAL_TIMELOCK, FAST_TRACK_TIMELOCK, CRITICAL_TIMELOCK]; -// block after Executor is deployed on BSC mainnet +// Listed Core Pool markets used for behavioural assertions. +// vUSDC drives the happy-path round-trip; vBNB stays unconfigured to exercise MarketNotConfigured. +const VUSDC = "0xecA88125a5ADbe82614ffC12D0DB554E2e2867C8"; +const VBNB = "0xA07c5b74C9B40447a954e1466938b865b6BBea36"; + +// IExecutor.CapType wire values — load-bearing. +const CAP_TYPE_BORROW = 0; +const CAP_TYPE_SUPPLY = 1; + +// Block after Executor is deployed on BSC mainnet. const BLOCK_NUMBER = 98248415; forking(BLOCK_NUMBER, async () => { let accessControlManager: Contract; + let executor: Contract; + let comptroller: Contract; + // BSC mainnet ACM's isAllowedToCall keys on msg.sender == target contract. + // We impersonate the relevant target contract for each permission lookup. let impersonatedExecutor: SignerWithAddress; let impersonatedEBrake: SignerWithAddress; + let monitorSigner: SignerWithAddress; + let criticalTimelockSigner: SignerWithAddress; + let randomSigner: SignerWithAddress; + before(async () => { accessControlManager = await ethers.getContractAt(ACCESS_CONTROL_MANAGER_ABI, ACM); + executor = await ethers.getContractAt(EXECUTOR_ABI, EXECUTOR); + comptroller = await ethers.getContractAt(COMPTROLLER_ABI, UNITROLLER); impersonatedExecutor = await initMainnetUser(EXECUTOR, ethers.utils.parseEther("1")); impersonatedEBrake = await initMainnetUser(EBRAKE, ethers.utils.parseEther("1")); + + monitorSigner = await initMainnetUser(SIGNAL_MONITOR, ethers.utils.parseEther("1")); + criticalTimelockSigner = await initMainnetUser(CRITICAL_TIMELOCK, ethers.utils.parseEther("1")); + randomSigner = await initMainnetUser("0x000000000000000000000000000000000000bEEF", ethers.utils.parseEther("1")); }); describe("Pre-VIP behavior", () => { @@ -62,6 +87,10 @@ forking(BLOCK_NUMBER, async () => { } } }); + + it("Signal monitor cannot call Executor handlers before the VIP runs", async () => { + await expect(executor.connect(monitorSigner).handleLTVAdjust(VUSDC, 0)).to.be.reverted; + }); }); testVip("VIP-701 [BNB Chain] Configure tighten-only Executor", await vip701(), { @@ -71,7 +100,7 @@ forking(BLOCK_NUMBER, async () => { }, }); - describe("Post-VIP behavior", () => { + describe("Post-VIP permission state", () => { it("Signal monitor should have all Executor action permissions", async () => { const acm = accessControlManager.connect(impersonatedExecutor); for (const sig of EXECUTOR_MONITOR_PERMS) { @@ -95,4 +124,164 @@ forking(BLOCK_NUMBER, async () => { } }); }); + + describe("Executor deployment linkage", () => { + it("Executor.EBRAKE should equal the configured EBrake address", async () => { + expect(await executor.EBRAKE()).to.equal(EBRAKE); + }); + + it("Executor.COMPTROLLER should equal the Core Pool Unitroller", async () => { + expect(await executor.COMPTROLLER()).to.equal(UNITROLLER); + }); + + it("Executor.IS_CORE_POOL should be true (BSC Diamond comptroller)", async () => { + expect(await executor.IS_CORE_POOL()).to.equal(true); + }); + }); + + describe("Post-VIP behaviour — setMarketConfig", () => { + it("reverts when called from an unauthorized account", async () => { + await expect( + executor.connect(randomSigner).setMarketConfig(VUSDC, { minBorrowCap: 0, minSupplyCap: 0, enabled: true }), + ).to.be.reverted; + }); + + it("reverts with ZeroAddress when market is the zero address", async () => { + await expect( + executor + .connect(criticalTimelockSigner) + .setMarketConfig(ethers.constants.AddressZero, { minBorrowCap: 0, minSupplyCap: 0, enabled: true }), + ).to.be.revertedWithCustomError(executor, "ZeroAddress"); + }); + + it("reverts when market is not listed in the comptroller", async () => { + const unlistedMarket = "0x000000000000000000000000000000000000DEAD"; + await expect( + executor + .connect(criticalTimelockSigner) + .setMarketConfig(unlistedMarket, { minBorrowCap: 0, minSupplyCap: 0, enabled: true }), + ).to.be.reverted; + }); + + it("Critical timelock can configure a listed market and the config is stored", async () => { + const currentBorrowCap: BigNumber = await comptroller.borrowCaps(VUSDC); + const currentSupplyCap: BigNumber = await comptroller.supplyCaps(VUSDC); + // Floor at half the live caps — high enough to actually reject below-floor adjustments, + // low enough that the tightening round-trip further down still has room to move. + const minBorrowCap = currentBorrowCap.div(2); + const minSupplyCap = currentSupplyCap.div(2); + + await expect( + executor.connect(criticalTimelockSigner).setMarketConfig(VUSDC, { minBorrowCap, minSupplyCap, enabled: true }), + ) + .to.emit(executor, "MarketConfigSet") + .withArgs(VUSDC, [minBorrowCap, minSupplyCap, true]); + + const stored = await executor.marketConfigs(VUSDC); + expect(stored.minBorrowCap).to.equal(minBorrowCap); + expect(stored.minSupplyCap).to.equal(minSupplyCap); + expect(stored.enabled).to.equal(true); + }); + }); + + describe("Post-VIP behaviour — handler access control", () => { + it("non-monitor caller cannot invoke handleLTVAdjust", async () => { + await expect(executor.connect(randomSigner).handleLTVAdjust(VUSDC, 0)).to.be.reverted; + }); + + it("non-monitor caller cannot invoke handleCapAdjust", async () => { + await expect(executor.connect(randomSigner).handleCapAdjust(VUSDC, CAP_TYPE_BORROW, 0)).to.be.reverted; + }); + + it("non-monitor caller cannot invoke handleSupplyCapExceeding", async () => { + await expect(executor.connect(randomSigner).handleSupplyCapExceeding(VUSDC)).to.be.reverted; + }); + + it("non-monitor caller cannot invoke handleBorrowCapExceeding", async () => { + await expect(executor.connect(randomSigner).handleBorrowCapExceeding(VUSDC)).to.be.reverted; + }); + + it("handler reverts with MarketNotConfigured for a market never registered (vBNB)", async () => { + await expect(executor.connect(monitorSigner).handleLTVAdjust(VBNB, 0)).to.be.revertedWithCustomError( + executor, + "MarketNotConfigured", + ); + }); + }); + + describe("Post-VIP behaviour — end-to-end signal → Executor → EBrake → Comptroller", () => { + it("handleLTVAdjust(newCF >= currentCF) traverses Executor + EBrake without reverting (idempotent path)", async () => { + // Forcing a real CF decrease at this fork block hits Comptroller's resilient-oracle + // validation ("invalid resilient oracle price"). Setting adjustedLTV to the current CF + // exercises the Monitor → Executor → EBrake auth chain — EBrake.decreaseCF then skips + // every pool's comptroller call (newCF >= currentCF → continue) and the Executor still + // emits the event. State-changing E2E is covered below via cap adjustments. + const [, currentCF] = await comptroller.markets(VUSDC); + expect(currentCF).to.be.gt(0); + + await expect(executor.connect(monitorSigner).handleLTVAdjust(VUSDC, currentCF)) + .to.emit(executor, "LTVAdjusted") + .withArgs(SIGNAL_MONITOR, VUSDC, currentCF); + }); + + it("handleCapAdjust(BORROW) lowers the borrow cap on the Comptroller and rejects below minBorrowCap", async () => { + const currentCap: BigNumber = await comptroller.borrowCaps(VUSDC); + const { minBorrowCap } = await executor.marketConfigs(VUSDC); + + // Below the configured floor — must revert. + await expect( + executor.connect(monitorSigner).handleCapAdjust(VUSDC, CAP_TYPE_BORROW, minBorrowCap.sub(1)), + ).to.be.revertedWithCustomError(executor, "CapBelowMinimum"); + + // Tighten within bounds. + const newCap = currentCap.sub(1); + await expect(executor.connect(monitorSigner).handleCapAdjust(VUSDC, CAP_TYPE_BORROW, newCap)) + .to.emit(executor, "CapAdjusted") + .withArgs(SIGNAL_MONITOR, VUSDC, CAP_TYPE_BORROW, currentCap, newCap); + + expect(await comptroller.borrowCaps(VUSDC)).to.equal(newCap); + }); + + it("handleCapAdjust(SUPPLY) lowers the supply cap on the Comptroller and rejects below minSupplyCap", async () => { + const currentCap: BigNumber = await comptroller.supplyCaps(VUSDC); + const { minSupplyCap } = await executor.marketConfigs(VUSDC); + + await expect( + executor.connect(monitorSigner).handleCapAdjust(VUSDC, CAP_TYPE_SUPPLY, minSupplyCap.sub(1)), + ).to.be.revertedWithCustomError(executor, "CapBelowMinimum"); + + const newCap = currentCap.sub(1); + await expect(executor.connect(monitorSigner).handleCapAdjust(VUSDC, CAP_TYPE_SUPPLY, newCap)) + .to.emit(executor, "CapAdjusted") + .withArgs(SIGNAL_MONITOR, VUSDC, CAP_TYPE_SUPPLY, currentCap, newCap); + + expect(await comptroller.supplyCaps(VUSDC)).to.equal(newCap); + }); + + it("handleSupplyCapExceeding reverts with CapNotBreached on a healthy market", async () => { + await expect(executor.connect(monitorSigner).handleSupplyCapExceeding(VUSDC)).to.be.revertedWithCustomError( + executor, + "CapNotBreached", + ); + }); + + it("handleBorrowCapExceeding reverts with CapNotBreached on a healthy market", async () => { + await expect(executor.connect(monitorSigner).handleBorrowCapExceeding(VUSDC)).to.be.revertedWithCustomError( + executor, + "CapNotBreached", + ); + }); + + it("handler reverts with MarketDisabled once config.enabled is flipped off", async () => { + const { minBorrowCap, minSupplyCap } = await executor.marketConfigs(VUSDC); + await executor + .connect(criticalTimelockSigner) + .setMarketConfig(VUSDC, { minBorrowCap, minSupplyCap, enabled: false }); + + await expect(executor.connect(monitorSigner).handleLTVAdjust(VUSDC, 0)).to.be.revertedWithCustomError( + executor, + "MarketDisabled", + ); + }); + }); }); From 7192b495bc43f7167e859d99d37edac4b57d23f9 Mon Sep 17 00:00:00 2001 From: Debugger022 Date: Fri, 15 May 2026 11:04:59 +0530 Subject: [PATCH 06/11] feat: accept ownership of Executor and EBrake --- simulations/vip-701/abi/EBrake.json | 683 ++++++++++++++++++++++++++++ simulations/vip-701/bscmainnet.ts | 25 +- vips/vip-701/bscmainnet.ts | 18 +- 3 files changed, 719 insertions(+), 7 deletions(-) create mode 100644 simulations/vip-701/abi/EBrake.json diff --git a/simulations/vip-701/abi/EBrake.json b/simulations/vip-701/abi/EBrake.json new file mode 100644 index 000000000..c7aedf09d --- /dev/null +++ b/simulations/vip-701/abi/EBrake.json @@ -0,0 +1,683 @@ +[ + { + "inputs": [ + { + "internalType": "contract ICorePoolComptroller", + "name": "corePoolComptroller_", + "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": 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": "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": [ + { + "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", + "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": "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": "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-701/bscmainnet.ts b/simulations/vip-701/bscmainnet.ts index 803731594..4cc938f0f 100644 --- a/simulations/vip-701/bscmainnet.ts +++ b/simulations/vip-701/bscmainnet.ts @@ -17,6 +17,7 @@ import vip701, { } from "../../vips/vip-701/bscmainnet"; import ACCESS_CONTROL_MANAGER_ABI from "./abi/AccessControlManager.json"; import COMPTROLLER_ABI from "./abi/Comptroller.json"; +import EBRAKE_ABI from "./abi/EBrake.json"; import EXECUTOR_ABI from "./abi/Executor.json"; const { NORMAL_TIMELOCK, FAST_TRACK_TIMELOCK, CRITICAL_TIMELOCK, GUARDIAN, UNITROLLER } = NETWORK_ADDRESSES.bscmainnet; @@ -37,6 +38,7 @@ const BLOCK_NUMBER = 98248415; forking(BLOCK_NUMBER, async () => { let accessControlManager: Contract; let executor: Contract; + let eBrake: Contract; let comptroller: Contract; // BSC mainnet ACM's isAllowedToCall keys on msg.sender == target contract. @@ -51,6 +53,7 @@ forking(BLOCK_NUMBER, async () => { before(async () => { accessControlManager = await ethers.getContractAt(ACCESS_CONTROL_MANAGER_ABI, ACM); executor = await ethers.getContractAt(EXECUTOR_ABI, EXECUTOR); + eBrake = await ethers.getContractAt(EBRAKE_ABI, EBRAKE); comptroller = await ethers.getContractAt(COMPTROLLER_ABI, UNITROLLER); impersonatedExecutor = await initMainnetUser(EXECUTOR, ethers.utils.parseEther("1")); @@ -91,12 +94,20 @@ forking(BLOCK_NUMBER, async () => { it("Signal monitor cannot call Executor handlers before the VIP runs", async () => { await expect(executor.connect(monitorSigner).handleLTVAdjust(VUSDC, 0)).to.be.reverted; }); + + it("Executor and EBrake are not yet owned by Normal Timelock (pendingOwner is set)", async () => { + expect(await executor.owner()).to.not.equal(NORMAL_TIMELOCK); + expect(await executor.pendingOwner()).to.equal(NORMAL_TIMELOCK); + expect(await eBrake.owner()).to.not.equal(NORMAL_TIMELOCK); + expect(await eBrake.pendingOwner()).to.equal(NORMAL_TIMELOCK); + }); }); testVip("VIP-701 [BNB Chain] Configure tighten-only Executor", await vip701(), { callbackAfterExecution: async txResponse => { - // RoleGranted: 4 (monitor on Executor) + 5 (Executor on EBrake) + 4 (Guardian + 3 timelocks setMarketConfig) = 13 + // 13 RoleGranted (4 monitor + 5 EBrake + 4 setMarketConfig) and 2 OwnershipTransferred (Executor + EBrake). await expectEvents(txResponse, [ACCESS_CONTROL_MANAGER_ABI], ["RoleGranted"], [13]); + await expectEvents(txResponse, [EXECUTOR_ABI], ["OwnershipTransferred"], [2]); }, }); @@ -139,6 +150,18 @@ forking(BLOCK_NUMBER, async () => { }); }); + describe("Post-VIP ownership state", () => { + it("Executor.owner() is Normal Timelock and pendingOwner is cleared", async () => { + expect(await executor.owner()).to.equal(NORMAL_TIMELOCK); + expect(await executor.pendingOwner()).to.equal(ethers.constants.AddressZero); + }); + + it("EBrake.owner() is Normal Timelock and pendingOwner is cleared", async () => { + expect(await eBrake.owner()).to.equal(NORMAL_TIMELOCK); + expect(await eBrake.pendingOwner()).to.equal(ethers.constants.AddressZero); + }); + }); + describe("Post-VIP behaviour — setMarketConfig", () => { it("reverts when called from an unauthorized account", async () => { await expect( diff --git a/vips/vip-701/bscmainnet.ts b/vips/vip-701/bscmainnet.ts index 3031ef8e8..428c71992 100644 --- a/vips/vip-701/bscmainnet.ts +++ b/vips/vip-701/bscmainnet.ts @@ -54,15 +54,17 @@ Depends on: VIP-610 (EBrake configuration), VPD-984 (EBrake Phase-0). #### Proposed Changes -**1. Grant Signal Monitor permissions on Executor action handlers** +**1. Accept ownership of Executor and EBrake** + +**2. Grant Signal Monitor permissions on Executor action handlers** - Authorize the off-chain monitor to call \`handleLTVAdjust\`, \`handleCapAdjust\`, \`handleSupplyCapExceeding\`, \`handleBorrowCapExceeding\` -**2. Grant Executor permissions on EBrake** +**3. Grant Executor permissions on EBrake** - Authorize the Executor to call \`pauseBorrow\`, \`pauseSupply\`, \`decreaseCF\`, \`setMarketBorrowCaps\`, \`setMarketSupplyCaps\` on EBrake -**3. Grant Guardian and all three Timelocks (Normal, Fast Track, Critical) permission to call \`setMarketConfig\` on Executor** +**4. Grant Guardian and all three Timelocks (Normal, Fast Track, Critical) permission to call \`setMarketConfig\` on Executor** - Lets governance set per-market bounds (\`minBorrowCap\`, \`minSupplyCap\`, \`enabled\`). Granting to all three timelocks + Guardian mirrors VIP-610 and lets Critical (~1h) disable a compromised market's automation instead of waiting 48h on Normal. @@ -77,13 +79,17 @@ Depends on: VIP-610 (EBrake configuration), VPD-984 (EBrake Phase-0). return makeProposal( [ - // 1. Signal monitor → Executor action handlers + // 1. Accept ownership + { target: EXECUTOR, signature: "acceptOwnership()", params: [] }, + { target: EBRAKE, signature: "acceptOwnership()", params: [] }, + + // 2. Signal monitor → Executor action handlers ...EXECUTOR_MONITOR_PERMS.map(sig => giveCallPermission(EXECUTOR, sig, SIGNAL_MONITOR)), - // 2. Executor → EBrake + // 3. Executor → EBrake ...EBRAKE_EXECUTOR_PERMS.map(sig => giveCallPermission(EBRAKE, sig, EXECUTOR)), - // 3. Guardian + all timelocks → Executor governance function + // 4. Guardian + all timelocks → Executor governance function ...[GUARDIAN, NORMAL_TIMELOCK, FAST_TRACK_TIMELOCK, CRITICAL_TIMELOCK].flatMap(account => EXECUTOR_GOVERNANCE_PERMS.map(sig => giveCallPermission(EXECUTOR, sig, account)), ), From ba78f736fe097acb61a6f9b4c9a5d8083fbdb51d Mon Sep 17 00:00:00 2001 From: Debugger022 Date: Fri, 15 May 2026 12:01:58 +0530 Subject: [PATCH 07/11] feat: add USDT transfer from treasury to Flux marketing wallet --- simulations/vip-701/abi/ERC20.json | 134 +++++++++++++++++++++++++++++ simulations/vip-701/bscmainnet.ts | 26 +++++- vips/vip-701/bscmainnet.ts | 18 +++- 3 files changed, 176 insertions(+), 2 deletions(-) create mode 100644 simulations/vip-701/abi/ERC20.json diff --git a/simulations/vip-701/abi/ERC20.json b/simulations/vip-701/abi/ERC20.json new file mode 100644 index 000000000..3a509c9c4 --- /dev/null +++ b/simulations/vip-701/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-701/bscmainnet.ts b/simulations/vip-701/bscmainnet.ts index 4cc938f0f..b91ee495c 100644 --- a/simulations/vip-701/bscmainnet.ts +++ b/simulations/vip-701/bscmainnet.ts @@ -13,14 +13,19 @@ import vip701, { EXECUTOR, EXECUTOR_GOVERNANCE_PERMS, EXECUTOR_MONITOR_PERMS, + FLUX_MARKETING_WALLET, SIGNAL_MONITOR, + USDT, + USDT_AMOUNT, } from "../../vips/vip-701/bscmainnet"; import ACCESS_CONTROL_MANAGER_ABI from "./abi/AccessControlManager.json"; import COMPTROLLER_ABI from "./abi/Comptroller.json"; import EBRAKE_ABI from "./abi/EBrake.json"; +import ERC20_ABI from "./abi/ERC20.json"; import EXECUTOR_ABI from "./abi/Executor.json"; -const { NORMAL_TIMELOCK, FAST_TRACK_TIMELOCK, CRITICAL_TIMELOCK, GUARDIAN, UNITROLLER } = NETWORK_ADDRESSES.bscmainnet; +const { NORMAL_TIMELOCK, FAST_TRACK_TIMELOCK, CRITICAL_TIMELOCK, GUARDIAN, UNITROLLER, VTREASURY } = + NETWORK_ADDRESSES.bscmainnet; const EXECUTOR_GOVERNANCE_ACCOUNTS = [GUARDIAN, NORMAL_TIMELOCK, FAST_TRACK_TIMELOCK, CRITICAL_TIMELOCK]; // Listed Core Pool markets used for behavioural assertions. @@ -40,6 +45,7 @@ forking(BLOCK_NUMBER, async () => { let executor: Contract; let eBrake: Contract; let comptroller: Contract; + let usdt: Contract; // BSC mainnet ACM's isAllowedToCall keys on msg.sender == target contract. // We impersonate the relevant target contract for each permission lookup. @@ -50,11 +56,16 @@ forking(BLOCK_NUMBER, async () => { let criticalTimelockSigner: SignerWithAddress; let randomSigner: SignerWithAddress; + // Captured before VIP execution so post-VIP assertions can verify the delta. + let treasuryUsdtBefore: BigNumber; + let fluxWalletUsdtBefore: BigNumber; + before(async () => { accessControlManager = await ethers.getContractAt(ACCESS_CONTROL_MANAGER_ABI, ACM); executor = await ethers.getContractAt(EXECUTOR_ABI, EXECUTOR); eBrake = await ethers.getContractAt(EBRAKE_ABI, EBRAKE); comptroller = await ethers.getContractAt(COMPTROLLER_ABI, UNITROLLER); + usdt = await ethers.getContractAt(ERC20_ABI, USDT); impersonatedExecutor = await initMainnetUser(EXECUTOR, ethers.utils.parseEther("1")); impersonatedEBrake = await initMainnetUser(EBRAKE, ethers.utils.parseEther("1")); @@ -62,6 +73,9 @@ forking(BLOCK_NUMBER, async () => { monitorSigner = await initMainnetUser(SIGNAL_MONITOR, ethers.utils.parseEther("1")); criticalTimelockSigner = await initMainnetUser(CRITICAL_TIMELOCK, ethers.utils.parseEther("1")); randomSigner = await initMainnetUser("0x000000000000000000000000000000000000bEEF", ethers.utils.parseEther("1")); + + treasuryUsdtBefore = await usdt.balanceOf(VTREASURY); + fluxWalletUsdtBefore = await usdt.balanceOf(FLUX_MARKETING_WALLET); }); describe("Pre-VIP behavior", () => { @@ -307,4 +321,14 @@ forking(BLOCK_NUMBER, async () => { ); }); }); + + describe("Post-VIP behaviour — treasury transfer", () => { + it("treasury USDT balance decreased by 25,000 USDT", async () => { + expect(await usdt.balanceOf(VTREASURY)).to.equal(treasuryUsdtBefore.sub(USDT_AMOUNT)); + }); + + it("Flux marketing wallet received 25,000 USDT", async () => { + expect(await usdt.balanceOf(FLUX_MARKETING_WALLET)).to.equal(fluxWalletUsdtBefore.add(USDT_AMOUNT)); + }); + }); }); diff --git a/vips/vip-701/bscmainnet.ts b/vips/vip-701/bscmainnet.ts index 428c71992..916d3d4b5 100644 --- a/vips/vip-701/bscmainnet.ts +++ b/vips/vip-701/bscmainnet.ts @@ -2,11 +2,16 @@ 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; +const { NORMAL_TIMELOCK, FAST_TRACK_TIMELOCK, CRITICAL_TIMELOCK, GUARDIAN, VTREASURY } = NETWORK_ADDRESSES.bscmainnet; // Access Control Manager export const ACM = NETWORK_ADDRESSES.bscmainnet.ACCESS_CONTROL_MANAGER; +// Marketing transfer +export const USDT = "0x55d398326f99059fF775485246999027B3197955"; +export const FLUX_MARKETING_WALLET = "0xBE0EdB1F457334B8d2DfEb3627567137E745A00B"; +export const USDT_AMOUNT = "25000000000000000000000"; // 25,000 USDT (18 decimals) + // EBrake (configured in VIP-610) export const EBRAKE = "0x35eBaBB99c7Fb7ba0C90bCc26e5d55Cdf89C23Ec"; @@ -68,6 +73,10 @@ Depends on: VIP-610 (EBrake configuration), VPD-984 (EBrake Phase-0). - Lets governance set per-market bounds (\`minBorrowCap\`, \`minSupplyCap\`, \`enabled\`). Granting to all three timelocks + Guardian mirrors VIP-610 and lets Critical (~1h) disable a compromised market's automation instead of waiting 48h on Normal. +**5. Transfer 25,000 USDT from Venus Treasury to Flux marketing wallet** + +- Funds the incoming Flux marketing campaign. Recipient: \`0xBE0EdB1F457334B8d2DfEb3627567137E745A00B\` (multisig shared with Fluid team). + #### References - [GitHub PR: VenusProtocol/venus-periphery#61](https://github.com/VenusProtocol/venus-periphery/pull/61) @@ -93,6 +102,13 @@ Depends on: VIP-610 (EBrake configuration), VPD-984 (EBrake Phase-0). ...[GUARDIAN, NORMAL_TIMELOCK, FAST_TRACK_TIMELOCK, CRITICAL_TIMELOCK].flatMap(account => EXECUTOR_GOVERNANCE_PERMS.map(sig => giveCallPermission(EXECUTOR, sig, account)), ), + + // 5. Transfer 25,000 USDT to Flux marketing wallet for upcoming campaign + { + target: VTREASURY, + signature: "withdrawTreasuryBEP20(address,uint256,address)", + params: [USDT, USDT_AMOUNT, FLUX_MARKETING_WALLET], + }, ], meta, ProposalType.REGULAR, From ef65ba33c84e6694ef9858a44c7834ae52fb0475 Mon Sep 17 00:00:00 2001 From: Debugger022 Date: Fri, 15 May 2026 13:47:56 +0530 Subject: [PATCH 08/11] feat(vip-701): initialise Executor market configs with 20% cap floors - Add a hardhat script that snapshots BSC core-pool markets and writes per-market 20% borrow/supply floors to coreMarketCaps.json - VIP imports the snapshot and emits one setMarketConfig per listed market instead of zero defaults - Simulation diffs every entry against the JSON so floors can't silently drift between snapshot and execution --- scripts/fetchCoreMarketCaps.ts | 117 +++++++++ simulations/vip-701/bscmainnet.ts | 25 +- vips/vip-701/bscmainnet.ts | 33 ++- vips/vip-701/coreMarketCaps.json | 382 ++++++++++++++++++++++++++++++ 4 files changed, 549 insertions(+), 8 deletions(-) create mode 100644 scripts/fetchCoreMarketCaps.ts create mode 100644 vips/vip-701/coreMarketCaps.json diff --git a/scripts/fetchCoreMarketCaps.ts b/scripts/fetchCoreMarketCaps.ts new file mode 100644 index 000000000..59f4a41c5 --- /dev/null +++ b/scripts/fetchCoreMarketCaps.ts @@ -0,0 +1,117 @@ +/** + * Fetch BSC Core Pool markets and compute 20% floors of their current borrow/supply caps. + * + * Run: + * npx hardhat run scripts/fetchCoreMarketCaps.ts --network bscmainnet + * + * Reads on-chain: + * - Unitroller.getAllMarkets() + * - For each market: poolMarkets(0, vToken) to keep only core-pool-listed entries + * - For each kept market: borrowCaps(vToken), supplyCaps(vToken), vToken.symbol() + * + * Writes: + * vips/vip-701/coreMarketCaps.json — consumed by vips/vip-701/bscmainnet.ts + * + * 20% of zero is zero, so uncapped markets stay at minCap=0 (which still permits monitor + * tightening on the LTV and cap-exceeding paths — only handleCapAdjust becomes a no-op). + */ +import fs from "fs"; +import { ethers } from "hardhat"; +import path from "path"; + +const UNITROLLER = "0xfD36E2c2a6789Db23113685031d7F16329158384"; +const CORE_POOL_ID = 0; + +const COMPTROLLER_ABI = [ + "function getAllMarkets() view returns (address[])", + "function poolMarkets(uint96,address) view returns (bool isListed,uint256,bool,uint256,uint256,uint96,bool)", + "function borrowCaps(address) view returns (uint256)", + "function supplyCaps(address) view returns (uint256)", +]; + +const VTOKEN_ABI = ["function symbol() view returns (string)"]; + +interface MarketEntry { + address: string; + symbol: string; + currentBorrowCap: string; + currentSupplyCap: string; + minBorrowCap: string; // 20% of currentBorrowCap, floored to wei + minSupplyCap: string; // 20% of currentSupplyCap, floored to wei +} + +async function main() { + const provider = ethers.provider; + const comptroller = new ethers.Contract(UNITROLLER, COMPTROLLER_ABI, provider); + + const block = await provider.getBlockNumber(); + console.log(`Fetching at block ${block}…`); + + // step 1: get all markets + const allMarkets: string[] = await comptroller.getAllMarkets(); + console.log(`getAllMarkets() returned ${allMarkets.length} markets`); + + const kept: MarketEntry[] = []; + const skipped: { address: string; reason: string }[] = []; + + for (const market of allMarkets) { + // step 2: check if it's listed in the core pool (poolId 0) + const [isListed] = await comptroller.poolMarkets(CORE_POOL_ID, market); + if (!isListed) { + skipped.push({ address: market, reason: "not listed in core pool (poolId 0)" }); + continue; + } + + // step 3: fetch current caps and symbol + const [currentBorrowCap, currentSupplyCap, symbol] = await Promise.all([ + comptroller.borrowCaps(market), + comptroller.supplyCaps(market), + new ethers.Contract(market, VTOKEN_ABI, provider).symbol(), + ]); + + // step 4: Compute 20% floors, rounding down to the nearest integer (wei) + const minBorrowCap = currentBorrowCap.mul(20).div(100); + const minSupplyCap = currentSupplyCap.mul(20).div(100); + + kept.push({ + address: market, + symbol, + currentBorrowCap: currentBorrowCap.toString(), + currentSupplyCap: currentSupplyCap.toString(), + minBorrowCap: minBorrowCap.toString(), + minSupplyCap: minSupplyCap.toString(), + }); + } + + console.log(`Kept ${kept.length} core-pool-listed markets, skipped ${skipped.length}`); + console.log("Skipped:", skipped); + + const out = { + _meta: { + network: "bscmainnet", + comptroller: UNITROLLER, + corePoolId: CORE_POOL_ID, + block, + fetchedAt: new Date().toISOString(), + floorPercent: 20, + }, + markets: kept, + skipped, + }; + + const outPath = path.resolve(__dirname, "../vips/vip-701/coreMarketCaps.json"); + fs.writeFileSync(outPath, `${JSON.stringify(out, null, 2)}\n`); + console.log(`Wrote ${outPath}`); + + // Print a human-readable summary table so the result is reviewable in the run log. + console.log("\n--- 20% floors per market ---"); + console.log("symbol".padEnd(28), "minBorrowCap".padEnd(28), "minSupplyCap"); + for (const m of kept) { + console.log(m.symbol.padEnd(28), m.minBorrowCap.padEnd(28), m.minSupplyCap); + } +} + +main().catch(err => { + console.error(err); + process.exit(1); +}); diff --git a/simulations/vip-701/bscmainnet.ts b/simulations/vip-701/bscmainnet.ts index b91ee495c..84ca42abd 100644 --- a/simulations/vip-701/bscmainnet.ts +++ b/simulations/vip-701/bscmainnet.ts @@ -8,6 +8,7 @@ import { forking, testVip } from "src/vip-framework"; import vip701, { ACM, + CORE_POOL_MARKET_CONFIGS, EBRAKE, EBRAKE_EXECUTOR_PERMS, EXECUTOR, @@ -28,10 +29,10 @@ const { NORMAL_TIMELOCK, FAST_TRACK_TIMELOCK, CRITICAL_TIMELOCK, GUARDIAN, UNITR NETWORK_ADDRESSES.bscmainnet; const EXECUTOR_GOVERNANCE_ACCOUNTS = [GUARDIAN, NORMAL_TIMELOCK, FAST_TRACK_TIMELOCK, CRITICAL_TIMELOCK]; -// Listed Core Pool markets used for behavioural assertions. -// vUSDC drives the happy-path round-trip; vBNB stays unconfigured to exercise MarketNotConfigured. +// Listed Core Pool market driving the happy-path round-trip. const VUSDC = "0xecA88125a5ADbe82614ffC12D0DB554E2e2867C8"; -const VBNB = "0xA07c5b74C9B40447a954e1466938b865b6BBea36"; +// Unlisted address used to exercise MarketNotConfigured (every Core Pool market is configured by the VIP). +const UNLISTED_MARKET = "0x000000000000000000000000000000000000bEEF"; // IExecutor.CapType wire values — load-bearing. const CAP_TYPE_BORROW = 0; @@ -119,9 +120,10 @@ forking(BLOCK_NUMBER, async () => { testVip("VIP-701 [BNB Chain] Configure tighten-only Executor", await vip701(), { callbackAfterExecution: async txResponse => { - // 13 RoleGranted (4 monitor + 5 EBrake + 4 setMarketConfig) and 2 OwnershipTransferred (Executor + EBrake). + // 13 RoleGranted, 2 OwnershipTransferred (Executor + EBrake), 1 MarketConfigSet per Core Pool market. await expectEvents(txResponse, [ACCESS_CONTROL_MANAGER_ABI], ["RoleGranted"], [13]); await expectEvents(txResponse, [EXECUTOR_ABI], ["OwnershipTransferred"], [2]); + await expectEvents(txResponse, [EXECUTOR_ABI], ["MarketConfigSet"], [CORE_POOL_MARKET_CONFIGS.length]); }, }); @@ -176,6 +178,17 @@ forking(BLOCK_NUMBER, async () => { }); }); + describe("Post-VIP Core Pool market configs", () => { + it("every Core Pool market matches the floors from coreMarketCaps.json", async () => { + for (const m of CORE_POOL_MARKET_CONFIGS) { + const cfg = await executor.marketConfigs(m.address); + expect(cfg.minBorrowCap).to.equal(m.minBorrowCap, `minBorrowCap for ${m.symbol} (${m.address})`); + expect(cfg.minSupplyCap).to.equal(m.minSupplyCap, `minSupplyCap for ${m.symbol} (${m.address})`); + expect(cfg.enabled).to.equal(true, `enabled for ${m.symbol} (${m.address})`); + } + }); + }); + describe("Post-VIP behaviour — setMarketConfig", () => { it("reverts when called from an unauthorized account", async () => { await expect( @@ -238,8 +251,8 @@ forking(BLOCK_NUMBER, async () => { await expect(executor.connect(randomSigner).handleBorrowCapExceeding(VUSDC)).to.be.reverted; }); - it("handler reverts with MarketNotConfigured for a market never registered (vBNB)", async () => { - await expect(executor.connect(monitorSigner).handleLTVAdjust(VBNB, 0)).to.be.revertedWithCustomError( + it("handler reverts with MarketNotConfigured for a market never registered", async () => { + await expect(executor.connect(monitorSigner).handleLTVAdjust(UNLISTED_MARKET, 0)).to.be.revertedWithCustomError( executor, "MarketNotConfigured", ); diff --git a/vips/vip-701/bscmainnet.ts b/vips/vip-701/bscmainnet.ts index 916d3d4b5..b8cb27870 100644 --- a/vips/vip-701/bscmainnet.ts +++ b/vips/vip-701/bscmainnet.ts @@ -2,6 +2,8 @@ import { NETWORK_ADDRESSES } from "src/networkAddresses"; import { ProposalType } from "src/types"; import { makeProposal } from "src/utils"; +import coreMarketCaps from "./coreMarketCaps.json"; + const { NORMAL_TIMELOCK, FAST_TRACK_TIMELOCK, CRITICAL_TIMELOCK, GUARDIAN, VTREASURY } = NETWORK_ADDRESSES.bscmainnet; // Access Control Manager @@ -41,6 +43,22 @@ export const EBRAKE_EXECUTOR_PERMS = [ "setMarketSupplyCaps(address[],uint256[])", ]; +// Per-market Executor configs sourced from scripts/fetchCoreMarketCaps.ts. +// The JSON snapshot lives in this folder; re-run the script to refresh it before publishing. +// minBorrowCap / minSupplyCap are 20% of the live caps at the snapshot block; +// markets that aren't listed in the core pool (poolId 0) are dropped by the script. +export const CORE_POOL_MARKET_CONFIGS: { + address: string; + symbol: string; + minBorrowCap: string; + minSupplyCap: string; +}[] = coreMarketCaps.markets.map(m => ({ + address: m.address, + symbol: m.symbol, + minBorrowCap: m.minBorrowCap, + minSupplyCap: m.minSupplyCap, +})); + const giveCallPermission = (contract: string, sig: string, account: string) => ({ target: ACM, signature: "giveCallPermission(address,string,address)", @@ -73,7 +91,11 @@ Depends on: VIP-610 (EBrake configuration), VPD-984 (EBrake Phase-0). - Lets governance set per-market bounds (\`minBorrowCap\`, \`minSupplyCap\`, \`enabled\`). Granting to all three timelocks + Guardian mirrors VIP-610 and lets Critical (~1h) disable a compromised market's automation instead of waiting 48h on Normal. -**5. Transfer 25,000 USDT from Venus Treasury to Flux marketing wallet** +**5. Initialise Executor market configs for every Core Pool market** + +- Call \`setMarketConfig\` on the Executor for each listed Core Pool vToken with \`enabled = true\` and per-market floors set to **20% of the current borrow/supply cap** at the snapshot block. Per-market values are produced by [scripts/fetchCoreMarketCaps.ts](scripts/fetchCoreMarketCaps.ts) and committed to [vips/vip-701/coreMarketCaps.json](vips/vip-701/coreMarketCaps.json); zero-cap markets stay at floor 0. + +**6. Transfer 25,000 USDT from Venus Treasury to Flux marketing wallet** - Funds the incoming Flux marketing campaign. Recipient: \`0xBE0EdB1F457334B8d2DfEb3627567137E745A00B\` (multisig shared with Fluid team). @@ -103,7 +125,14 @@ Depends on: VIP-610 (EBrake configuration), VPD-984 (EBrake Phase-0). EXECUTOR_GOVERNANCE_PERMS.map(sig => giveCallPermission(EXECUTOR, sig, account)), ), - // 5. Transfer 25,000 USDT to Flux marketing wallet for upcoming campaign + // 5. Initialise Executor market configs for every Core Pool market (20% floors from script snapshot) + ...CORE_POOL_MARKET_CONFIGS.map(m => ({ + target: EXECUTOR, + signature: "setMarketConfig(address,(uint256,uint256,bool))", + params: [m.address, [m.minBorrowCap, m.minSupplyCap, true]], + })), + + // 6. Transfer 25,000 USDT to Flux marketing wallet for upcoming campaign { target: VTREASURY, signature: "withdrawTreasuryBEP20(address,uint256,address)", diff --git a/vips/vip-701/coreMarketCaps.json b/vips/vip-701/coreMarketCaps.json new file mode 100644 index 000000000..66582b38b --- /dev/null +++ b/vips/vip-701/coreMarketCaps.json @@ -0,0 +1,382 @@ +{ + "_meta": { + "network": "bscmainnet", + "comptroller": "0xfD36E2c2a6789Db23113685031d7F16329158384", + "corePoolId": 0, + "block": 98393327, + "fetchedAt": "2026-05-15T07:50:26.547Z", + "floorPercent": 20 + }, + "markets": [ + { + "address": "0xecA88125a5ADbe82614ffC12D0DB554E2e2867C8", + "symbol": "vUSDC", + "currentBorrowCap": "324000000000000000000000000", + "currentSupplyCap": "360000000000000000000000000", + "minBorrowCap": "64800000000000000000000000", + "minSupplyCap": "72000000000000000000000000" + }, + { + "address": "0xfD5840Cd36d94D7229439859C0112a4185BC0255", + "symbol": "vUSDT", + "currentBorrowCap": "450000000000000000000000000", + "currentSupplyCap": "600000000000000000000000000", + "minBorrowCap": "90000000000000000000000000", + "minSupplyCap": "120000000000000000000000000" + }, + { + "address": "0x95c78222B3D6e262426483D42CfA53685A67Ab9D", + "symbol": "vBUSD", + "currentBorrowCap": "0", + "currentSupplyCap": "0", + "minBorrowCap": "0", + "minSupplyCap": "0" + }, + { + "address": "0x2fF3d0F6990a40261c66E1ff2017aCBc282EB6d0", + "symbol": "vSXP", + "currentBorrowCap": "0", + "currentSupplyCap": "0", + "minBorrowCap": "0", + "minSupplyCap": "0" + }, + { + "address": "0x151B1e2635A717bcDc836ECd6FbB62B674FE3E1D", + "symbol": "vXVS", + "currentBorrowCap": "0", + "currentSupplyCap": "1850000000000000000000000", + "minBorrowCap": "0", + "minSupplyCap": "370000000000000000000000" + }, + { + "address": "0xA07c5b74C9B40447a954e1466938b865b6BBea36", + "symbol": "vBNB", + "currentBorrowCap": "2008000000000000000000000", + "currentSupplyCap": "2672000000000000000000000", + "minBorrowCap": "401600000000000000000000", + "minSupplyCap": "534400000000000000000000" + }, + { + "address": "0x882C173bC7Ff3b7786CA16dfeD3DFFfb9Ee7847B", + "symbol": "vBTC", + "currentBorrowCap": "3531000000000000000000", + "currentSupplyCap": "22770000000000000000000", + "minBorrowCap": "706200000000000000000", + "minSupplyCap": "4554000000000000000000" + }, + { + "address": "0xf508fCD89b8bd15579dc79A6827cB4686A3592c8", + "symbol": "vETH", + "currentBorrowCap": "60000000000000000000000", + "currentSupplyCap": "100000000000000000000000", + "minBorrowCap": "12000000000000000000000", + "minSupplyCap": "20000000000000000000000" + }, + { + "address": "0x57A5297F2cB2c0AaC9D554660acd6D385Ab50c6B", + "symbol": "vLTC", + "currentBorrowCap": "20000000000000000000000", + "currentSupplyCap": "240000000000000000000000", + "minBorrowCap": "4000000000000000000000", + "minSupplyCap": "48000000000000000000000" + }, + { + "address": "0xB248a295732e0225acd3337607cc01068e3b9c10", + "symbol": "vXRP", + "currentBorrowCap": "6000000000000000000000000", + "currentSupplyCap": "24000000000000000000000000", + "minBorrowCap": "1200000000000000000000000", + "minSupplyCap": "4800000000000000000000000" + }, + { + "address": "0x5F0388EBc2B94FA8E123F404b79cCF5f40b29176", + "symbol": "vBCH", + "currentBorrowCap": "1000000000000000000000", + "currentSupplyCap": "10000000000000000000000", + "minBorrowCap": "200000000000000000000", + "minSupplyCap": "2000000000000000000000" + }, + { + "address": "0x1610bc33319e9398de5f57B33a5b184c806aD217", + "symbol": "vDOT", + "currentBorrowCap": "400000000000000000000000", + "currentSupplyCap": "1200000000000000000000000", + "minBorrowCap": "80000000000000000000000", + "minSupplyCap": "240000000000000000000000" + }, + { + "address": "0x650b940a1033B8A1b1873f78730FcFC73ec11f1f", + "symbol": "vLINK", + "currentBorrowCap": "80000000000000000000000", + "currentSupplyCap": "900000000000000000000000", + "minBorrowCap": "16000000000000000000000", + "minSupplyCap": "180000000000000000000000" + }, + { + "address": "0x334b3eCB4DCa3593BCCC3c7EBD1A1C1d1780FBF1", + "symbol": "vDAI", + "currentBorrowCap": "7500000000000000000000000", + "currentSupplyCap": "13910000000000000000000000", + "minBorrowCap": "1500000000000000000000000", + "minSupplyCap": "2782000000000000000000000" + }, + { + "address": "0xf91d58b5aE142DAcC749f58A49FCBac340Cb0343", + "symbol": "vFIL", + "currentBorrowCap": "90000000000000000000000", + "currentSupplyCap": "1200000000000000000000000", + "minBorrowCap": "18000000000000000000000", + "minSupplyCap": "240000000000000000000000" + }, + { + "address": "0x972207A639CC1B374B893cc33Fa251b55CEB7c07", + "symbol": "vBETH", + "currentBorrowCap": "1", + "currentSupplyCap": "0", + "minBorrowCap": "0", + "minSupplyCap": "0" + }, + { + "address": "0x9A0AF7FDb2065Ce470D72664DE73cAE409dA28Ec", + "symbol": "vADA", + "currentBorrowCap": "3000000000000000000000000", + "currentSupplyCap": "37510000000000000000000000", + "minBorrowCap": "600000000000000000000000", + "minSupplyCap": "7502000000000000000000000" + }, + { + "address": "0xec3422Ef92B2fb59e84c8B02Ba73F1fE84Ed8D71", + "symbol": "vDOGE", + "currentBorrowCap": "450000000000000", + "currentSupplyCap": "12000000000000000", + "minBorrowCap": "90000000000000", + "minSupplyCap": "2400000000000000" + }, + { + "address": "0x5c9476FcD6a4F9a3654139721c949c2233bBbBc8", + "symbol": "vMATIC", + "currentBorrowCap": "0", + "currentSupplyCap": "0", + "minBorrowCap": "0", + "minSupplyCap": "0" + }, + { + "address": "0x86aC3974e2BD0d60825230fa6F355fF11409df5c", + "symbol": "vCAKE", + "currentBorrowCap": "19200000000000000000000000", + "currentSupplyCap": "24000000000000000000000000", + "minBorrowCap": "3840000000000000000000000", + "minSupplyCap": "4800000000000000000000000" + }, + { + "address": "0x26DA28954763B92139ED49283625ceCAf52C6f94", + "symbol": "vAAVE", + "currentBorrowCap": "3000000000000000000000", + "currentSupplyCap": "20000000000000000000000", + "minBorrowCap": "600000000000000000000", + "minSupplyCap": "4000000000000000000000" + }, + { + "address": "0x08CEB3F4a7ed3500cA0982bcd0FC7816688084c3", + "symbol": "vTUSDOLD", + "currentBorrowCap": "0", + "currentSupplyCap": "0", + "minBorrowCap": "0", + "minSupplyCap": "0" + }, + { + "address": "0x61eDcFe8Dd6bA3c891CB9bEc2dc7657B3B422E93", + "symbol": "vTRXOLD", + "currentBorrowCap": "0", + "currentSupplyCap": "0", + "minBorrowCap": "0", + "minSupplyCap": "0" + }, + { + "address": "0xC5D3466aA484B040eE977073fcF337f2c00071c1", + "symbol": "vTRX", + "currentBorrowCap": "6000000000000", + "currentSupplyCap": "12000000000000", + "minBorrowCap": "1200000000000", + "minSupplyCap": "2400000000000" + }, + { + "address": "0x6CFdEc747f37DAf3b87a35a1D9c8AD3063A1A8A0", + "symbol": "vWBETH", + "currentBorrowCap": "16000000000000000000000", + "currentSupplyCap": "40000000000000000000000", + "minBorrowCap": "3200000000000000000000", + "minSupplyCap": "8000000000000000000000" + }, + { + "address": "0xBf762cd5991cA1DCdDaC9ae5C638F5B5Dc3Bee6E", + "symbol": "vTUSD", + "currentBorrowCap": "600000000000000000000000", + "currentSupplyCap": "750000000000000000000000", + "minBorrowCap": "120000000000000000000000", + "minSupplyCap": "150000000000000000000000" + }, + { + "address": "0x27FF564707786720C71A2e5c1490A63266683612", + "symbol": "vUNI", + "currentBorrowCap": "200000000000000000000000", + "currentSupplyCap": "2200000000000000000000000", + "minBorrowCap": "40000000000000000000000", + "minSupplyCap": "440000000000000000000000" + }, + { + "address": "0xC4eF4229FEc74Ccfe17B2bdeF7715fAC740BA0ba", + "symbol": "vFDUSD", + "currentBorrowCap": "80000000000000000000000000", + "currentSupplyCap": "100000000000000000000000000", + "minBorrowCap": "16000000000000000000000000", + "minSupplyCap": "20000000000000000000000000" + }, + { + "address": "0x4d41a36D04D97785bcEA57b057C412b278e6Edcc", + "symbol": "vTWT", + "currentBorrowCap": "1000000000000000000000000", + "currentSupplyCap": "8000000000000000000000000", + "minBorrowCap": "200000000000000000000000", + "minSupplyCap": "1600000000000000000000000" + }, + { + "address": "0xf841cb62c19fCd4fF5CD0AaB5939f3140BaaC3Ea", + "symbol": "vSolvBTC", + "currentBorrowCap": "110000000000000000000", + "currentSupplyCap": "3000000000000000000000", + "minBorrowCap": "22000000000000000000", + "minSupplyCap": "600000000000000000000" + }, + { + "address": "0x86e06EAfa6A1eA631Eab51DE500E3D474933739f", + "symbol": "vTHE", + "currentBorrowCap": "8000000000000000000000000", + "currentSupplyCap": "14500000000000000000000000", + "minBorrowCap": "1600000000000000000000000", + "minSupplyCap": "2900000000000000000000000" + }, + { + "address": "0xBf515bA4D1b52FFdCeaBF20d31D705Ce789F2cEC", + "symbol": "vSOL", + "currentBorrowCap": "18000000000000000000000", + "currentSupplyCap": "72000000000000000000000", + "minBorrowCap": "3600000000000000000000", + "minSupplyCap": "14400000000000000000000" + }, + { + "address": "0x689E0daB47Ab16bcae87Ec18491692BF621Dc6Ab", + "symbol": "vlisUSD", + "currentBorrowCap": "10000000000000000000000000", + "currentSupplyCap": "12000000000000000000000000", + "minBorrowCap": "2000000000000000000000000", + "minSupplyCap": "2400000000000000000000000" + }, + { + "address": "0x9e4E5fed5Ac5B9F732d0D850A615206330Bf1866", + "symbol": "vPT-sUSDE-26JUN2025", + "currentBorrowCap": "0", + "currentSupplyCap": "2000000000000000000000000", + "minBorrowCap": "0", + "minSupplyCap": "400000000000000000000000" + }, + { + "address": "0x699658323d58eE25c69F1a29d476946ab011bD18", + "symbol": "vsUSDe", + "currentBorrowCap": "0", + "currentSupplyCap": "4000000000000000000000000", + "minBorrowCap": "0", + "minSupplyCap": "800000000000000000000000" + }, + { + "address": "0x74ca6930108F775CC667894EEa33843e691680d7", + "symbol": "vUSDe", + "currentBorrowCap": "1600000000000000000000000", + "currentSupplyCap": "2000000000000000000000000", + "minBorrowCap": "320000000000000000000000", + "minSupplyCap": "400000000000000000000000" + }, + { + "address": "0x0C1DA220D301155b87318B90692Da8dc43B67340", + "symbol": "vUSD1", + "currentBorrowCap": "14400000000000000000000000", + "currentSupplyCap": "16000000000000000000000000", + "minBorrowCap": "2880000000000000000000000", + "minSupplyCap": "3200000000000000000000000" + }, + { + "address": "0xd804dE60aFD05EE6B89aab5D152258fD461B07D5", + "symbol": "vxSolvBTC", + "currentBorrowCap": "0", + "currentSupplyCap": "2000000000000000000000", + "minBorrowCap": "0", + "minSupplyCap": "400000000000000000000" + }, + { + "address": "0xCC1dB43a06d97f736C7B045AedD03C6707c09BDF", + "symbol": "vasBNB", + "currentBorrowCap": "0", + "currentSupplyCap": "216000000000000000000000", + "minBorrowCap": "0", + "minSupplyCap": "43200000000000000000000" + }, + { + "address": "0x6bCa74586218dB34cdB402295796b79663d816e9", + "symbol": "vWBNB", + "currentBorrowCap": "2008000000000000000000000", + "currentSupplyCap": "2672000000000000000000000", + "minBorrowCap": "401600000000000000000000", + "minSupplyCap": "534400000000000000000000" + }, + { + "address": "0x89c910Eb8c90df818b4649b508Ba22130Dc73Adc", + "symbol": "vslisBNB", + "currentBorrowCap": "0", + "currentSupplyCap": "20000000000000000000000", + "minBorrowCap": "0", + "minSupplyCap": "4000000000000000000000" + }, + { + "address": "0x3d5E269787d562b74aCC55F18Bd26C5D09Fa245E", + "symbol": "vU", + "currentBorrowCap": "210000000000000000000000000", + "currentSupplyCap": "210000000000000000000000000", + "minBorrowCap": "42000000000000000000000000", + "minSupplyCap": "42000000000000000000000000" + }, + { + "address": "0x6d3BD68E90B42615cb5abF4B8DE92b154ADc435e", + "symbol": "vPT-clisBNB-25JUN2026", + "currentBorrowCap": "0", + "currentSupplyCap": "25000000000000000000000", + "minBorrowCap": "0", + "minSupplyCap": "5000000000000000000000" + }, + { + "address": "0x92e6Ea74a1A3047DabF4186405a21c7D63a0612A", + "symbol": "vXAUM", + "currentBorrowCap": "0", + "currentSupplyCap": "200000000000000000000", + "minBorrowCap": "0", + "minSupplyCap": "40000000000000000000" + } + ], + "skipped": [ + { + "address": "0xeBD0070237a0713E8D94fEf1B728d3d993d290ef", + "reason": "not listed in core pool (poolId 0)" + }, + { + "address": "0x78366446547D062f45b4C0f320cDaa6d710D87bb", + "reason": "not listed in core pool (poolId 0)" + }, + { + "address": "0xb91A659E88B51474767CD97EF3196A3e7cEDD2c8", + "reason": "not listed in core pool (poolId 0)" + }, + { + "address": "0x6D0cDb3355c93A0cD20071aBbb3622731a95c73E", + "reason": "not listed in core pool (poolId 0)" + } + ] +} From db67a7cf527a8f30d257c0fd4efb8a0dc30a8b8d Mon Sep 17 00:00:00 2001 From: Debugger022 Date: Fri, 15 May 2026 14:40:43 +0530 Subject: [PATCH 09/11] feat(vip-701): override 20% floors with VIP-622 post-cap targets - Add vip622Overrides.json hardcoding 20% floors for the 19 markets PR #706 right-sizes but hasn't yet executed on-chain - VIP merges overrides over the script snapshot per address, with a per-market source tag so reviewers can see at a glance which floors came from live state vs the VIP-622 hardcode - Unblocks publishing VIP-701 without waiting for VIP-622 to land --- vips/vip-701/bscmainnet.ts | 39 +++++++--- vips/vip-701/vip622Overrides.json | 124 ++++++++++++++++++++++++++++++ 2 files changed, 152 insertions(+), 11 deletions(-) create mode 100644 vips/vip-701/vip622Overrides.json diff --git a/vips/vip-701/bscmainnet.ts b/vips/vip-701/bscmainnet.ts index b8cb27870..5edbaf5ee 100644 --- a/vips/vip-701/bscmainnet.ts +++ b/vips/vip-701/bscmainnet.ts @@ -3,6 +3,7 @@ import { ProposalType } from "src/types"; import { makeProposal } from "src/utils"; import coreMarketCaps from "./coreMarketCaps.json"; +import vip622Overrides from "./vip622Overrides.json"; const { NORMAL_TIMELOCK, FAST_TRACK_TIMELOCK, CRITICAL_TIMELOCK, GUARDIAN, VTREASURY } = NETWORK_ADDRESSES.bscmainnet; @@ -43,21 +44,37 @@ export const EBRAKE_EXECUTOR_PERMS = [ "setMarketSupplyCaps(address[],uint256[])", ]; -// Per-market Executor configs sourced from scripts/fetchCoreMarketCaps.ts. -// The JSON snapshot lives in this folder; re-run the script to refresh it before publishing. -// minBorrowCap / minSupplyCap are 20% of the live caps at the snapshot block; -// markets that aren't listed in the core pool (poolId 0) are dropped by the script. +// Per-market Executor configs. +// +// Default source: scripts/fetchCoreMarketCaps.ts → coreMarketCaps.json (20% of live on-chain caps). +// Override source: vip622Overrides.json (20% of post-VIP-622 caps, hardcoded for markets that +// PR #706 right-sizes but hasn't executed on-chain yet). Override wins per address. +const overrideByAddress = new Map(vip622Overrides.markets.map(o => [o.address.toLowerCase(), o] as const)); + export const CORE_POOL_MARKET_CONFIGS: { address: string; symbol: string; minBorrowCap: string; minSupplyCap: string; -}[] = coreMarketCaps.markets.map(m => ({ - address: m.address, - symbol: m.symbol, - minBorrowCap: m.minBorrowCap, - minSupplyCap: m.minSupplyCap, -})); + source: "script" | "vip-622"; +}[] = coreMarketCaps.markets.map(m => { + const override = overrideByAddress.get(m.address.toLowerCase()); + return override + ? { + address: m.address, + symbol: m.symbol, + minBorrowCap: override.minBorrowCap, + minSupplyCap: override.minSupplyCap, + source: "vip-622", + } + : { + address: m.address, + symbol: m.symbol, + minBorrowCap: m.minBorrowCap, + minSupplyCap: m.minSupplyCap, + source: "script", + }; +}); const giveCallPermission = (contract: string, sig: string, account: string) => ({ target: ACM, @@ -93,7 +110,7 @@ Depends on: VIP-610 (EBrake configuration), VPD-984 (EBrake Phase-0). **5. Initialise Executor market configs for every Core Pool market** -- Call \`setMarketConfig\` on the Executor for each listed Core Pool vToken with \`enabled = true\` and per-market floors set to **20% of the current borrow/supply cap** at the snapshot block. Per-market values are produced by [scripts/fetchCoreMarketCaps.ts](scripts/fetchCoreMarketCaps.ts) and committed to [vips/vip-701/coreMarketCaps.json](vips/vip-701/coreMarketCaps.json); zero-cap markets stay at floor 0. +- Call \`setMarketConfig\` on the Executor for each listed Core Pool vToken with \`enabled = true\` and per-market floors set to **20% of the effective borrow/supply cap**. Default source is [scripts/fetchCoreMarketCaps.ts](scripts/fetchCoreMarketCaps.ts) → [vips/vip-701/coreMarketCaps.json](vips/vip-701/coreMarketCaps.json) (20% of live caps). For markets being right-sized by VIP-622 (PR #706) but not yet executed, [vips/vip-701/vip622Overrides.json](vips/vip-701/vip622Overrides.json) hardcodes 20% of the post-VIP-622 caps so VIP-701 can publish without waiting for VIP-622 to land on-chain. **6. Transfer 25,000 USDT from Venus Treasury to Flux marketing wallet** diff --git a/vips/vip-701/vip622Overrides.json b/vips/vip-701/vip622Overrides.json new file mode 100644 index 000000000..e61ab3cd4 --- /dev/null +++ b/vips/vip-701/vip622Overrides.json @@ -0,0 +1,124 @@ +{ + "_meta": { + "source": "Hardcoded from VIP-622 (PR https://github.com/VenusProtocol/vips/pull/706) post-VIP caps", + "rationale": "VIP-622 right-sizes caps on these markets but is not yet executed on-chain, so coreMarketCaps.json (which reads live state) still reflects the pre-VIP-622 caps. For these markets we override the script-derived 20% floors with 20% of the post-VIP-622 caps; for every other market the script values from coreMarketCaps.json are used as-is.", + "appliesTo": "vips/vip-701/coreMarketCaps.json", + "decimalsNote": "Values are in raw wei. DOGE uses 8 dp, TRX uses 6 dp, everything else 18 dp." + }, + "markets": [ + { + "address": "0x9A0AF7FDb2065Ce470D72664DE73cAE409dA28Ec", + "symbol": "vADA", + "minBorrowCap": "600000000000000000000000", + "minSupplyCap": "3000000000000000000000000" + }, + { + "address": "0x5F0388EBc2B94FA8E123F404b79cCF5f40b29176", + "symbol": "vBCH", + "minBorrowCap": "200000000000000000000", + "minSupplyCap": "1000000000000000000000" + }, + { + "address": "0xec3422Ef92B2fb59e84c8B02Ba73F1fE84Ed8D71", + "symbol": "vDOGE", + "minBorrowCap": "60000000000000", + "minSupplyCap": "1600000000000000" + }, + { + "address": "0xf508fCD89b8bd15579dc79A6827cB4686A3592c8", + "symbol": "vETH", + "minBorrowCap": "7400000000000000000000", + "minSupplyCap": "14400000000000000000000" + }, + { + "address": "0xC4eF4229FEc74Ccfe17B2bdeF7715fAC740BA0ba", + "symbol": "vFDUSD", + "minBorrowCap": "4000000000000000000000000", + "minSupplyCap": "7400000000000000000000000" + }, + { + "address": "0xf91d58b5aE142DAcC749f58A49FCBac340Cb0343", + "symbol": "vFIL", + "minBorrowCap": "0", + "minSupplyCap": "0" + }, + { + "address": "0x650b940a1033B8A1b1873f78730FcFC73ec11f1f", + "symbol": "vLINK", + "minBorrowCap": "4000000000000000000000", + "minSupplyCap": "80000000000000000000000" + }, + { + "address": "0x689E0daB47Ab16bcae87Ec18491692BF621Dc6Ab", + "symbol": "vlisUSD", + "minBorrowCap": "800000000000000000000000", + "minSupplyCap": "1000000000000000000000000" + }, + { + "address": "0x57A5297F2cB2c0AaC9D554660acd6D385Ab50c6B", + "symbol": "vLTC", + "minBorrowCap": "1600000000000000000000", + "minSupplyCap": "10000000000000000000000" + }, + { + "address": "0x89c910Eb8c90df818b4649b508Ba22130Dc73Adc", + "symbol": "vslisBNB", + "minBorrowCap": "0", + "minSupplyCap": "400000000000000000000" + }, + { + "address": "0xf841cb62c19fCd4fF5CD0AaB5939f3140BaaC3Ea", + "symbol": "vSolvBTC", + "minBorrowCap": "4000000000000000000", + "minSupplyCap": "600000000000000000000" + }, + { + "address": "0x86e06EAfa6A1eA631Eab51DE500E3D474933739f", + "symbol": "vTHE", + "minBorrowCap": "0", + "minSupplyCap": "0" + }, + { + "address": "0xC5D3466aA484B040eE977073fcF337f2c00071c1", + "symbol": "vTRX", + "minBorrowCap": "200000000000", + "minSupplyCap": "600000000000" + }, + { + "address": "0xBf762cd5991cA1DCdDaC9ae5C638F5B5Dc3Bee6E", + "symbol": "vTUSD", + "minBorrowCap": "0", + "minSupplyCap": "0" + }, + { + "address": "0x4d41a36D04D97785bcEA57b057C412b278e6Edcc", + "symbol": "vTWT", + "minBorrowCap": "10000000000000000000000", + "minSupplyCap": "400000000000000000000000" + }, + { + "address": "0x0C1DA220D301155b87318B90692Da8dc43B67340", + "symbol": "vUSD1", + "minBorrowCap": "800000000000000000000000", + "minSupplyCap": "1000000000000000000000000" + }, + { + "address": "0x6CFdEc747f37DAf3b87a35a1D9c8AD3063A1A8A0", + "symbol": "vWBETH", + "minBorrowCap": "200000000000000000000", + "minSupplyCap": "8000000000000000000000" + }, + { + "address": "0xB248a295732e0225acd3337607cc01068e3b9c10", + "symbol": "vXRP", + "minBorrowCap": "200000000000000000000000", + "minSupplyCap": "1500000000000000000000000" + }, + { + "address": "0xd804dE60aFD05EE6B89aab5D152258fD461B07D5", + "symbol": "vxSolvBTC", + "minBorrowCap": "0", + "minSupplyCap": "240000000000000000000" + } + ] +} From cd3a12aa7f1a5ebe05387a3ca6abc800097e49e6 Mon Sep 17 00:00:00 2001 From: Debugger022 Date: Fri, 15 May 2026 16:11:25 +0530 Subject: [PATCH 10/11] refactor(vip-701): drive 20% floors from VIP-622 data file - Script imports marketCapChanges / delistAssets from vips/vip-622/data/bscmainnet.ts directly; the hand-transcribed vip622Overrides.json is removed so any upstream edit flows through on the next run. - Pipeline drops markets whose 20% floors round to zero, removing BUSD / SXP / MATIC / TUSDOLD / TRXOLD / BETH / FIL / TUSD / THE from the config set. - VIP iterates the baked coreMarketCaps.json with no in-VIP merge logic; capSource on each entry is the audit trail. - Simulation pre-executes VIP-622 and asserts each Executor floor equals 20% of the live post-VIP-622 cap, and validates every skipped market against its reason. --- scripts/fetchCoreMarketCaps.ts | 136 ++++++++---- simulations/vip-701/bscmainnet.ts | 82 ++++++- vips/vip-701/bscmainnet.ts | 37 +--- vips/vip-701/coreMarketCaps.json | 351 +++++++++++++++--------------- vips/vip-701/vip622Overrides.json | 124 ----------- 5 files changed, 364 insertions(+), 366 deletions(-) delete mode 100644 vips/vip-701/vip622Overrides.json diff --git a/scripts/fetchCoreMarketCaps.ts b/scripts/fetchCoreMarketCaps.ts index 59f4a41c5..8f7c8bed3 100644 --- a/scripts/fetchCoreMarketCaps.ts +++ b/scripts/fetchCoreMarketCaps.ts @@ -1,26 +1,33 @@ /** - * Fetch BSC Core Pool markets and compute 20% floors of their current borrow/supply caps. + * Build the Executor's per-market 20% floor table for VIP-701. + * + * Pipeline (per vToken returned by Unitroller.getAllMarkets()): + * 1. Skip if not listed in the core pool (poolMarkets(0, vToken).isListed === false). + * 2. Pick the effective caps: + * - If the market is touched by queued VIP-622 (marketCapChanges or delistAssets in + * vips/vip-622/data/bscmainnet.ts), use the post-VIP-622 caps from that file. + * - Otherwise read the live caps from Comptroller.borrowCaps / supplyCaps. + * 3. Compute 20% floors. Integer division means a 1-wei dust cap rounds to 0. + * 4. Skip the market entirely if both 20% floors are zero — covers fully delisted + * markets (THE/TUSD/FIL via VIP-622) and dormant zero-cap stubs + * (BUSD/SXP/MATIC/TUSDOLD/TRXOLD/vBETH). * * Run: * npx hardhat run scripts/fetchCoreMarketCaps.ts --network bscmainnet * - * Reads on-chain: - * - Unitroller.getAllMarkets() - * - For each market: poolMarkets(0, vToken) to keep only core-pool-listed entries - * - For each kept market: borrowCaps(vToken), supplyCaps(vToken), vToken.symbol() - * - * Writes: - * vips/vip-701/coreMarketCaps.json — consumed by vips/vip-701/bscmainnet.ts - * - * 20% of zero is zero, so uncapped markets stay at minCap=0 (which still permits monitor - * tightening on the LTV and cap-exceeding paths — only handleCapAdjust becomes a no-op). + * Output: + * vips/vip-701/coreMarketCaps.json — consumed directly by vips/vip-701/bscmainnet.ts. */ +import { BigNumber } from "ethers"; import fs from "fs"; import { ethers } from "hardhat"; import path from "path"; +import { delistAssets, marketCapChanges } from "../vips/vip-622/data/bscmainnet"; + const UNITROLLER = "0xfD36E2c2a6789Db23113685031d7F16329158384"; const CORE_POOL_ID = 0; +const FLOOR_PERCENT = 20; const COMPTROLLER_ABI = [ "function getAllMarkets() view returns (address[])", @@ -28,62 +35,113 @@ const COMPTROLLER_ABI = [ "function borrowCaps(address) view returns (uint256)", "function supplyCaps(address) view returns (uint256)", ]; - const VTOKEN_ABI = ["function symbol() view returns (string)"]; -interface MarketEntry { +interface PostVip622Cap { + symbol: string; + borrowCap: BigNumber; + supplyCap: BigNumber; +} + +interface KeptMarket { address: string; symbol: string; - currentBorrowCap: string; - currentSupplyCap: string; - minBorrowCap: string; // 20% of currentBorrowCap, floored to wei - minSupplyCap: string; // 20% of currentSupplyCap, floored to wei + capSource: "live" | "vip-622"; + effectiveBorrowCap: string; + effectiveSupplyCap: string; + minBorrowCap: string; + minSupplyCap: string; +} +interface SkippedMarket { + address: string; + symbol?: string; + reason: string; +} + +// Build the VIP-622 override map directly from PR #706's data file. +// Single source of truth: any future edit to vips/vip-622/data/bscmainnet.ts flows here. +function buildVip622Overrides(): Map { + const overrides = new Map(); + for (const m of marketCapChanges) { + overrides.set(m.vToken.toLowerCase(), { + symbol: m.symbol, + borrowCap: BigNumber.from(m.borrowCap.new), + supplyCap: BigNumber.from(m.supplyCap.new), + }); + } + for (const d of delistAssets) { + // delistAssets force caps to 0; marketCapChanges may also list the same vToken (no-op), + // but the delist intent wins so we overwrite unconditionally. + overrides.set(d.vToken.toLowerCase(), { + symbol: d.symbol, + borrowCap: BigNumber.from(0), + supplyCap: BigNumber.from(0), + }); + } + return overrides; } async function main() { const provider = ethers.provider; const comptroller = new ethers.Contract(UNITROLLER, COMPTROLLER_ABI, provider); - const block = await provider.getBlockNumber(); console.log(`Fetching at block ${block}…`); - // step 1: get all markets + const vip622Overrides = buildVip622Overrides(); + console.log(`VIP-622 supplies post-caps for ${vip622Overrides.size} markets`); + const allMarkets: string[] = await comptroller.getAllMarkets(); console.log(`getAllMarkets() returned ${allMarkets.length} markets`); - const kept: MarketEntry[] = []; - const skipped: { address: string; reason: string }[] = []; + const kept: KeptMarket[] = []; + const skipped: SkippedMarket[] = []; for (const market of allMarkets) { - // step 2: check if it's listed in the core pool (poolId 0) const [isListed] = await comptroller.poolMarkets(CORE_POOL_ID, market); if (!isListed) { skipped.push({ address: market, reason: "not listed in core pool (poolId 0)" }); continue; } - // step 3: fetch current caps and symbol - const [currentBorrowCap, currentSupplyCap, symbol] = await Promise.all([ - comptroller.borrowCaps(market), - comptroller.supplyCaps(market), - new ethers.Contract(market, VTOKEN_ABI, provider).symbol(), - ]); + const override = vip622Overrides.get(market.toLowerCase()); + let borrowCap: BigNumber; + let supplyCap: BigNumber; + let capSource: "live" | "vip-622"; + if (override) { + borrowCap = override.borrowCap; + supplyCap = override.supplyCap; + capSource = "vip-622"; + } else { + [borrowCap, supplyCap] = await Promise.all([comptroller.borrowCaps(market), comptroller.supplyCaps(market)]); + capSource = "live"; + } + + const minBorrowCap = borrowCap.mul(FLOOR_PERCENT).div(100); + const minSupplyCap = supplyCap.mul(FLOOR_PERCENT).div(100); + + const symbol = await new ethers.Contract(market, VTOKEN_ABI, provider).symbol(); - // step 4: Compute 20% floors, rounding down to the nearest integer (wei) - const minBorrowCap = currentBorrowCap.mul(20).div(100); - const minSupplyCap = currentSupplyCap.mul(20).div(100); + if (minBorrowCap.isZero() && minSupplyCap.isZero()) { + skipped.push({ + address: market, + symbol, + reason: `${capSource} 20% floors are both zero (delisted / dormant)`, + }); + continue; + } kept.push({ address: market, symbol, - currentBorrowCap: currentBorrowCap.toString(), - currentSupplyCap: currentSupplyCap.toString(), + capSource, + effectiveBorrowCap: borrowCap.toString(), + effectiveSupplyCap: supplyCap.toString(), minBorrowCap: minBorrowCap.toString(), minSupplyCap: minSupplyCap.toString(), }); } - console.log(`Kept ${kept.length} core-pool-listed markets, skipped ${skipped.length}`); + console.log(`Kept ${kept.length} markets, skipped ${skipped.length}`); console.log("Skipped:", skipped); const out = { @@ -93,7 +151,8 @@ async function main() { corePoolId: CORE_POOL_ID, block, fetchedAt: new Date().toISOString(), - floorPercent: 20, + floorPercent: FLOOR_PERCENT, + vip622DataSource: "vips/vip-622/data/bscmainnet.ts (PR https://github.com/VenusProtocol/vips/pull/706)", }, markets: kept, skipped, @@ -103,11 +162,10 @@ async function main() { fs.writeFileSync(outPath, `${JSON.stringify(out, null, 2)}\n`); console.log(`Wrote ${outPath}`); - // Print a human-readable summary table so the result is reviewable in the run log. - console.log("\n--- 20% floors per market ---"); - console.log("symbol".padEnd(28), "minBorrowCap".padEnd(28), "minSupplyCap"); + console.log("\n--- Per-market 20% floors ---"); + console.log("symbol".padEnd(28), "source".padEnd(10), "minBorrowCap".padEnd(28), "minSupplyCap"); for (const m of kept) { - console.log(m.symbol.padEnd(28), m.minBorrowCap.padEnd(28), m.minSupplyCap); + console.log(m.symbol.padEnd(28), m.capSource.padEnd(10), m.minBorrowCap.padEnd(28), m.minSupplyCap); } } diff --git a/simulations/vip-701/bscmainnet.ts b/simulations/vip-701/bscmainnet.ts index 84ca42abd..0b966bf7a 100644 --- a/simulations/vip-701/bscmainnet.ts +++ b/simulations/vip-701/bscmainnet.ts @@ -4,8 +4,9 @@ import { BigNumber, 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 { forking, pretendExecutingVip, testVip } from "src/vip-framework"; +import { vip622 } from "../../vips/vip-622/bscmainnet"; import vip701, { ACM, CORE_POOL_MARKET_CONFIGS, @@ -19,6 +20,7 @@ import vip701, { USDT, USDT_AMOUNT, } from "../../vips/vip-701/bscmainnet"; +import coreMarketCaps from "../../vips/vip-701/coreMarketCaps.json"; import ACCESS_CONTROL_MANAGER_ABI from "./abi/AccessControlManager.json"; import COMPTROLLER_ABI from "./abi/Comptroller.json"; import EBRAKE_ABI from "./abi/EBrake.json"; @@ -75,6 +77,12 @@ forking(BLOCK_NUMBER, async () => { criticalTimelockSigner = await initMainnetUser(CRITICAL_TIMELOCK, ethers.utils.parseEther("1")); randomSigner = await initMainnetUser("0x000000000000000000000000000000000000bEEF", ethers.utils.parseEther("1")); + // VIP-622 is queued at this fork block. coreMarketCaps.json was built off the same + // VIP-622 data file, so pre-executing it here makes on-chain caps match the snapshot + // the VIP-701 floors were computed from — the post-VIP-701 assertion below can then + // verify each stored floor equals 20% of the comptroller's live cap. + await pretendExecutingVip(await vip622(), NORMAL_TIMELOCK); + treasuryUsdtBefore = await usdt.balanceOf(VTREASURY); fluxWalletUsdtBefore = await usdt.balanceOf(FLUX_MARKETING_WALLET); }); @@ -179,16 +187,80 @@ forking(BLOCK_NUMBER, async () => { }); describe("Post-VIP Core Pool market configs", () => { - it("every Core Pool market matches the floors from coreMarketCaps.json", async () => { + // With VIP-622 pre-executed in `before`, the comptroller's borrow/supply caps now reflect + // the post-VIP-622 state. The script computed VIP-701's floors off the same VIP-622 data, + // so the Executor's stored floor for every configured market must equal 20% of the live cap + // and the live cap must equal the snapshot the script saw. Catches drift between the two. + it("every Core Pool market's stored floors equal 20% of the post-VIP-622 on-chain caps", async () => { for (const m of CORE_POOL_MARKET_CONFIGS) { - const cfg = await executor.marketConfigs(m.address); - expect(cfg.minBorrowCap).to.equal(m.minBorrowCap, `minBorrowCap for ${m.symbol} (${m.address})`); - expect(cfg.minSupplyCap).to.equal(m.minSupplyCap, `minSupplyCap for ${m.symbol} (${m.address})`); + const [liveBorrowCap, liveSupplyCap, cfg] = await Promise.all([ + comptroller.borrowCaps(m.address), + comptroller.supplyCaps(m.address), + executor.marketConfigs(m.address), + ]); + const expectedMinBorrow = liveBorrowCap.mul(20).div(100); + const expectedMinSupply = liveSupplyCap.mul(20).div(100); + + expect(cfg.minBorrowCap).to.equal( + expectedMinBorrow, + `minBorrowCap for ${m.symbol} (${ + m.address + }): stored ${cfg.minBorrowCap.toString()} vs 20% of live ${liveBorrowCap.toString()}`, + ); + expect(cfg.minSupplyCap).to.equal( + expectedMinSupply, + `minSupplyCap for ${m.symbol} (${ + m.address + }): stored ${cfg.minSupplyCap.toString()} vs 20% of live ${liveSupplyCap.toString()}`, + ); expect(cfg.enabled).to.equal(true, `enabled for ${m.symbol} (${m.address})`); } }); }); + describe("Skipped markets are skipped for the right reason", () => { + for (const s of coreMarketCaps.skipped) { + const label = `${s.symbol ?? "(unknown symbol)"} ${s.address}`; + if (s.reason.includes("not listed in core pool")) { + it(`${label}: isListed === false in core pool`, async () => { + const [isListed] = await comptroller.markets(s.address); + expect(isListed).to.equal(false); + }); + } else if (s.reason.includes("20% floors are both zero")) { + // VIP-622 has been pre-executed, so live caps already reflect the effective state + // the script used. Both 20% values must round to zero — otherwise the script + // dropped a market that should have stayed in. + it(`${label}: 20% of live borrow & supply caps both round to zero`, async () => { + const [liveBorrow, liveSupply] = await Promise.all([ + comptroller.borrowCaps(s.address), + comptroller.supplyCaps(s.address), + ]); + expect(liveBorrow.mul(20).div(100)).to.equal( + 0, + `unexpected non-zero 20% borrow floor (cap ${liveBorrow.toString()})`, + ); + expect(liveSupply.mul(20).div(100)).to.equal( + 0, + `unexpected non-zero 20% supply floor (cap ${liveSupply.toString()})`, + ); + }); + } else { + it(`${label}: unknown skip reason "${s.reason}"`, () => { + throw new Error(`Unhandled skip reason: ${s.reason}. Update the test or the script.`); + }); + } + } + + it("Executor was NOT configured for any skipped market", async () => { + for (const s of coreMarketCaps.skipped) { + const cfg = await executor.marketConfigs(s.address); + expect(cfg.enabled).to.equal(false, `${s.symbol ?? s.address} should be unconfigured`); + expect(cfg.minBorrowCap).to.equal(0, `${s.symbol ?? s.address} should have zero stored borrow floor`); + expect(cfg.minSupplyCap).to.equal(0, `${s.symbol ?? s.address} should have zero stored supply floor`); + } + }); + }); + describe("Post-VIP behaviour — setMarketConfig", () => { it("reverts when called from an unauthorized account", async () => { await expect( diff --git a/vips/vip-701/bscmainnet.ts b/vips/vip-701/bscmainnet.ts index 5edbaf5ee..9738c1df4 100644 --- a/vips/vip-701/bscmainnet.ts +++ b/vips/vip-701/bscmainnet.ts @@ -3,7 +3,6 @@ import { ProposalType } from "src/types"; import { makeProposal } from "src/utils"; import coreMarketCaps from "./coreMarketCaps.json"; -import vip622Overrides from "./vip622Overrides.json"; const { NORMAL_TIMELOCK, FAST_TRACK_TIMELOCK, CRITICAL_TIMELOCK, GUARDIAN, VTREASURY } = NETWORK_ADDRESSES.bscmainnet; @@ -44,37 +43,21 @@ export const EBRAKE_EXECUTOR_PERMS = [ "setMarketSupplyCaps(address[],uint256[])", ]; -// Per-market Executor configs. -// -// Default source: scripts/fetchCoreMarketCaps.ts → coreMarketCaps.json (20% of live on-chain caps). -// Override source: vip622Overrides.json (20% of post-VIP-622 caps, hardcoded for markets that -// PR #706 right-sizes but hasn't executed on-chain yet). Override wins per address. -const overrideByAddress = new Map(vip622Overrides.markets.map(o => [o.address.toLowerCase(), o] as const)); - +// Per-market Executor configs — fully baked by scripts/fetchCoreMarketCaps.ts. +// The script already applies VIP-622 overrides and drops unlisted / zero-floor markets, +// so the VIP just emits one setMarketConfig per entry. Each entry's `capSource` in +// coreMarketCaps.json records whether the floor came from live state or VIP-622. export const CORE_POOL_MARKET_CONFIGS: { address: string; symbol: string; minBorrowCap: string; minSupplyCap: string; - source: "script" | "vip-622"; -}[] = coreMarketCaps.markets.map(m => { - const override = overrideByAddress.get(m.address.toLowerCase()); - return override - ? { - address: m.address, - symbol: m.symbol, - minBorrowCap: override.minBorrowCap, - minSupplyCap: override.minSupplyCap, - source: "vip-622", - } - : { - address: m.address, - symbol: m.symbol, - minBorrowCap: m.minBorrowCap, - minSupplyCap: m.minSupplyCap, - source: "script", - }; -}); +}[] = coreMarketCaps.markets.map(m => ({ + address: m.address, + symbol: m.symbol, + minBorrowCap: m.minBorrowCap, + minSupplyCap: m.minSupplyCap, +})); const giveCallPermission = (contract: string, sig: string, account: string) => ({ target: ACM, diff --git a/vips/vip-701/coreMarketCaps.json b/vips/vip-701/coreMarketCaps.json index 66582b38b..438462474 100644 --- a/vips/vip-701/coreMarketCaps.json +++ b/vips/vip-701/coreMarketCaps.json @@ -3,369 +3,368 @@ "network": "bscmainnet", "comptroller": "0xfD36E2c2a6789Db23113685031d7F16329158384", "corePoolId": 0, - "block": 98393327, - "fetchedAt": "2026-05-15T07:50:26.547Z", - "floorPercent": 20 + "block": 98413702, + "fetchedAt": "2026-05-15T10:23:37.041Z", + "floorPercent": 20, + "vip622DataSource": "vips/vip-622/data/bscmainnet.ts (PR https://github.com/VenusProtocol/vips/pull/706)" }, "markets": [ { "address": "0xecA88125a5ADbe82614ffC12D0DB554E2e2867C8", "symbol": "vUSDC", - "currentBorrowCap": "324000000000000000000000000", - "currentSupplyCap": "360000000000000000000000000", + "capSource": "vip-622", + "effectiveBorrowCap": "324000000000000000000000000", + "effectiveSupplyCap": "360000000000000000000000000", "minBorrowCap": "64800000000000000000000000", "minSupplyCap": "72000000000000000000000000" }, { "address": "0xfD5840Cd36d94D7229439859C0112a4185BC0255", "symbol": "vUSDT", - "currentBorrowCap": "450000000000000000000000000", - "currentSupplyCap": "600000000000000000000000000", + "capSource": "live", + "effectiveBorrowCap": "450000000000000000000000000", + "effectiveSupplyCap": "600000000000000000000000000", "minBorrowCap": "90000000000000000000000000", "minSupplyCap": "120000000000000000000000000" }, - { - "address": "0x95c78222B3D6e262426483D42CfA53685A67Ab9D", - "symbol": "vBUSD", - "currentBorrowCap": "0", - "currentSupplyCap": "0", - "minBorrowCap": "0", - "minSupplyCap": "0" - }, - { - "address": "0x2fF3d0F6990a40261c66E1ff2017aCBc282EB6d0", - "symbol": "vSXP", - "currentBorrowCap": "0", - "currentSupplyCap": "0", - "minBorrowCap": "0", - "minSupplyCap": "0" - }, { "address": "0x151B1e2635A717bcDc836ECd6FbB62B674FE3E1D", "symbol": "vXVS", - "currentBorrowCap": "0", - "currentSupplyCap": "1850000000000000000000000", + "capSource": "vip-622", + "effectiveBorrowCap": "0", + "effectiveSupplyCap": "1850000000000000000000000", "minBorrowCap": "0", "minSupplyCap": "370000000000000000000000" }, { "address": "0xA07c5b74C9B40447a954e1466938b865b6BBea36", "symbol": "vBNB", - "currentBorrowCap": "2008000000000000000000000", - "currentSupplyCap": "2672000000000000000000000", + "capSource": "live", + "effectiveBorrowCap": "2008000000000000000000000", + "effectiveSupplyCap": "2672000000000000000000000", "minBorrowCap": "401600000000000000000000", "minSupplyCap": "534400000000000000000000" }, { "address": "0x882C173bC7Ff3b7786CA16dfeD3DFFfb9Ee7847B", "symbol": "vBTC", - "currentBorrowCap": "3531000000000000000000", - "currentSupplyCap": "22770000000000000000000", + "capSource": "live", + "effectiveBorrowCap": "3531000000000000000000", + "effectiveSupplyCap": "22770000000000000000000", "minBorrowCap": "706200000000000000000", "minSupplyCap": "4554000000000000000000" }, { "address": "0xf508fCD89b8bd15579dc79A6827cB4686A3592c8", "symbol": "vETH", - "currentBorrowCap": "60000000000000000000000", - "currentSupplyCap": "100000000000000000000000", - "minBorrowCap": "12000000000000000000000", - "minSupplyCap": "20000000000000000000000" + "capSource": "vip-622", + "effectiveBorrowCap": "37000000000000000000000", + "effectiveSupplyCap": "72000000000000000000000", + "minBorrowCap": "7400000000000000000000", + "minSupplyCap": "14400000000000000000000" }, { "address": "0x57A5297F2cB2c0AaC9D554660acd6D385Ab50c6B", "symbol": "vLTC", - "currentBorrowCap": "20000000000000000000000", - "currentSupplyCap": "240000000000000000000000", - "minBorrowCap": "4000000000000000000000", - "minSupplyCap": "48000000000000000000000" + "capSource": "vip-622", + "effectiveBorrowCap": "8000000000000000000000", + "effectiveSupplyCap": "50000000000000000000000", + "minBorrowCap": "1600000000000000000000", + "minSupplyCap": "10000000000000000000000" }, { "address": "0xB248a295732e0225acd3337607cc01068e3b9c10", "symbol": "vXRP", - "currentBorrowCap": "6000000000000000000000000", - "currentSupplyCap": "24000000000000000000000000", - "minBorrowCap": "1200000000000000000000000", - "minSupplyCap": "4800000000000000000000000" + "capSource": "vip-622", + "effectiveBorrowCap": "1000000000000000000000000", + "effectiveSupplyCap": "7500000000000000000000000", + "minBorrowCap": "200000000000000000000000", + "minSupplyCap": "1500000000000000000000000" }, { "address": "0x5F0388EBc2B94FA8E123F404b79cCF5f40b29176", "symbol": "vBCH", - "currentBorrowCap": "1000000000000000000000", - "currentSupplyCap": "10000000000000000000000", + "capSource": "vip-622", + "effectiveBorrowCap": "1000000000000000000000", + "effectiveSupplyCap": "5000000000000000000000", "minBorrowCap": "200000000000000000000", - "minSupplyCap": "2000000000000000000000" + "minSupplyCap": "1000000000000000000000" }, { "address": "0x1610bc33319e9398de5f57B33a5b184c806aD217", "symbol": "vDOT", - "currentBorrowCap": "400000000000000000000000", - "currentSupplyCap": "1200000000000000000000000", + "capSource": "vip-622", + "effectiveBorrowCap": "400000000000000000000000", + "effectiveSupplyCap": "1200000000000000000000000", "minBorrowCap": "80000000000000000000000", "minSupplyCap": "240000000000000000000000" }, { "address": "0x650b940a1033B8A1b1873f78730FcFC73ec11f1f", "symbol": "vLINK", - "currentBorrowCap": "80000000000000000000000", - "currentSupplyCap": "900000000000000000000000", - "minBorrowCap": "16000000000000000000000", - "minSupplyCap": "180000000000000000000000" + "capSource": "vip-622", + "effectiveBorrowCap": "20000000000000000000000", + "effectiveSupplyCap": "400000000000000000000000", + "minBorrowCap": "4000000000000000000000", + "minSupplyCap": "80000000000000000000000" }, { "address": "0x334b3eCB4DCa3593BCCC3c7EBD1A1C1d1780FBF1", "symbol": "vDAI", - "currentBorrowCap": "7500000000000000000000000", - "currentSupplyCap": "13910000000000000000000000", + "capSource": "vip-622", + "effectiveBorrowCap": "7500000000000000000000000", + "effectiveSupplyCap": "13910000000000000000000000", "minBorrowCap": "1500000000000000000000000", "minSupplyCap": "2782000000000000000000000" }, - { - "address": "0xf91d58b5aE142DAcC749f58A49FCBac340Cb0343", - "symbol": "vFIL", - "currentBorrowCap": "90000000000000000000000", - "currentSupplyCap": "1200000000000000000000000", - "minBorrowCap": "18000000000000000000000", - "minSupplyCap": "240000000000000000000000" - }, - { - "address": "0x972207A639CC1B374B893cc33Fa251b55CEB7c07", - "symbol": "vBETH", - "currentBorrowCap": "1", - "currentSupplyCap": "0", - "minBorrowCap": "0", - "minSupplyCap": "0" - }, { "address": "0x9A0AF7FDb2065Ce470D72664DE73cAE409dA28Ec", "symbol": "vADA", - "currentBorrowCap": "3000000000000000000000000", - "currentSupplyCap": "37510000000000000000000000", + "capSource": "vip-622", + "effectiveBorrowCap": "3000000000000000000000000", + "effectiveSupplyCap": "15000000000000000000000000", "minBorrowCap": "600000000000000000000000", - "minSupplyCap": "7502000000000000000000000" + "minSupplyCap": "3000000000000000000000000" }, { "address": "0xec3422Ef92B2fb59e84c8B02Ba73F1fE84Ed8D71", "symbol": "vDOGE", - "currentBorrowCap": "450000000000000", - "currentSupplyCap": "12000000000000000", - "minBorrowCap": "90000000000000", - "minSupplyCap": "2400000000000000" - }, - { - "address": "0x5c9476FcD6a4F9a3654139721c949c2233bBbBc8", - "symbol": "vMATIC", - "currentBorrowCap": "0", - "currentSupplyCap": "0", - "minBorrowCap": "0", - "minSupplyCap": "0" + "capSource": "vip-622", + "effectiveBorrowCap": "300000000000000", + "effectiveSupplyCap": "8000000000000000", + "minBorrowCap": "60000000000000", + "minSupplyCap": "1600000000000000" }, { "address": "0x86aC3974e2BD0d60825230fa6F355fF11409df5c", "symbol": "vCAKE", - "currentBorrowCap": "19200000000000000000000000", - "currentSupplyCap": "24000000000000000000000000", + "capSource": "vip-622", + "effectiveBorrowCap": "19200000000000000000000000", + "effectiveSupplyCap": "24000000000000000000000000", "minBorrowCap": "3840000000000000000000000", "minSupplyCap": "4800000000000000000000000" }, { "address": "0x26DA28954763B92139ED49283625ceCAf52C6f94", "symbol": "vAAVE", - "currentBorrowCap": "3000000000000000000000", - "currentSupplyCap": "20000000000000000000000", + "capSource": "vip-622", + "effectiveBorrowCap": "3000000000000000000000", + "effectiveSupplyCap": "20000000000000000000000", "minBorrowCap": "600000000000000000000", "minSupplyCap": "4000000000000000000000" }, - { - "address": "0x08CEB3F4a7ed3500cA0982bcd0FC7816688084c3", - "symbol": "vTUSDOLD", - "currentBorrowCap": "0", - "currentSupplyCap": "0", - "minBorrowCap": "0", - "minSupplyCap": "0" - }, - { - "address": "0x61eDcFe8Dd6bA3c891CB9bEc2dc7657B3B422E93", - "symbol": "vTRXOLD", - "currentBorrowCap": "0", - "currentSupplyCap": "0", - "minBorrowCap": "0", - "minSupplyCap": "0" - }, { "address": "0xC5D3466aA484B040eE977073fcF337f2c00071c1", "symbol": "vTRX", - "currentBorrowCap": "6000000000000", - "currentSupplyCap": "12000000000000", - "minBorrowCap": "1200000000000", - "minSupplyCap": "2400000000000" + "capSource": "vip-622", + "effectiveBorrowCap": "1000000000000", + "effectiveSupplyCap": "3000000000000", + "minBorrowCap": "200000000000", + "minSupplyCap": "600000000000" }, { "address": "0x6CFdEc747f37DAf3b87a35a1D9c8AD3063A1A8A0", "symbol": "vWBETH", - "currentBorrowCap": "16000000000000000000000", - "currentSupplyCap": "40000000000000000000000", - "minBorrowCap": "3200000000000000000000", + "capSource": "vip-622", + "effectiveBorrowCap": "1000000000000000000000", + "effectiveSupplyCap": "40000000000000000000000", + "minBorrowCap": "200000000000000000000", "minSupplyCap": "8000000000000000000000" }, - { - "address": "0xBf762cd5991cA1DCdDaC9ae5C638F5B5Dc3Bee6E", - "symbol": "vTUSD", - "currentBorrowCap": "600000000000000000000000", - "currentSupplyCap": "750000000000000000000000", - "minBorrowCap": "120000000000000000000000", - "minSupplyCap": "150000000000000000000000" - }, { "address": "0x27FF564707786720C71A2e5c1490A63266683612", "symbol": "vUNI", - "currentBorrowCap": "200000000000000000000000", - "currentSupplyCap": "2200000000000000000000000", + "capSource": "vip-622", + "effectiveBorrowCap": "200000000000000000000000", + "effectiveSupplyCap": "2200000000000000000000000", "minBorrowCap": "40000000000000000000000", "minSupplyCap": "440000000000000000000000" }, { "address": "0xC4eF4229FEc74Ccfe17B2bdeF7715fAC740BA0ba", "symbol": "vFDUSD", - "currentBorrowCap": "80000000000000000000000000", - "currentSupplyCap": "100000000000000000000000000", - "minBorrowCap": "16000000000000000000000000", - "minSupplyCap": "20000000000000000000000000" + "capSource": "vip-622", + "effectiveBorrowCap": "20000000000000000000000000", + "effectiveSupplyCap": "37000000000000000000000000", + "minBorrowCap": "4000000000000000000000000", + "minSupplyCap": "7400000000000000000000000" }, { "address": "0x4d41a36D04D97785bcEA57b057C412b278e6Edcc", "symbol": "vTWT", - "currentBorrowCap": "1000000000000000000000000", - "currentSupplyCap": "8000000000000000000000000", - "minBorrowCap": "200000000000000000000000", - "minSupplyCap": "1600000000000000000000000" + "capSource": "vip-622", + "effectiveBorrowCap": "50000000000000000000000", + "effectiveSupplyCap": "2000000000000000000000000", + "minBorrowCap": "10000000000000000000000", + "minSupplyCap": "400000000000000000000000" }, { "address": "0xf841cb62c19fCd4fF5CD0AaB5939f3140BaaC3Ea", "symbol": "vSolvBTC", - "currentBorrowCap": "110000000000000000000", - "currentSupplyCap": "3000000000000000000000", - "minBorrowCap": "22000000000000000000", + "capSource": "vip-622", + "effectiveBorrowCap": "20000000000000000000", + "effectiveSupplyCap": "3000000000000000000000", + "minBorrowCap": "4000000000000000000", "minSupplyCap": "600000000000000000000" }, - { - "address": "0x86e06EAfa6A1eA631Eab51DE500E3D474933739f", - "symbol": "vTHE", - "currentBorrowCap": "8000000000000000000000000", - "currentSupplyCap": "14500000000000000000000000", - "minBorrowCap": "1600000000000000000000000", - "minSupplyCap": "2900000000000000000000000" - }, { "address": "0xBf515bA4D1b52FFdCeaBF20d31D705Ce789F2cEC", "symbol": "vSOL", - "currentBorrowCap": "18000000000000000000000", - "currentSupplyCap": "72000000000000000000000", + "capSource": "live", + "effectiveBorrowCap": "18000000000000000000000", + "effectiveSupplyCap": "72000000000000000000000", "minBorrowCap": "3600000000000000000000", "minSupplyCap": "14400000000000000000000" }, { "address": "0x689E0daB47Ab16bcae87Ec18491692BF621Dc6Ab", "symbol": "vlisUSD", - "currentBorrowCap": "10000000000000000000000000", - "currentSupplyCap": "12000000000000000000000000", - "minBorrowCap": "2000000000000000000000000", - "minSupplyCap": "2400000000000000000000000" + "capSource": "vip-622", + "effectiveBorrowCap": "4000000000000000000000000", + "effectiveSupplyCap": "5000000000000000000000000", + "minBorrowCap": "800000000000000000000000", + "minSupplyCap": "1000000000000000000000000" }, { "address": "0x9e4E5fed5Ac5B9F732d0D850A615206330Bf1866", "symbol": "vPT-sUSDE-26JUN2025", - "currentBorrowCap": "0", - "currentSupplyCap": "2000000000000000000000000", + "capSource": "live", + "effectiveBorrowCap": "0", + "effectiveSupplyCap": "2000000000000000000000000", "minBorrowCap": "0", "minSupplyCap": "400000000000000000000000" }, { "address": "0x699658323d58eE25c69F1a29d476946ab011bD18", "symbol": "vsUSDe", - "currentBorrowCap": "0", - "currentSupplyCap": "4000000000000000000000000", + "capSource": "live", + "effectiveBorrowCap": "0", + "effectiveSupplyCap": "4000000000000000000000000", "minBorrowCap": "0", "minSupplyCap": "800000000000000000000000" }, { "address": "0x74ca6930108F775CC667894EEa33843e691680d7", "symbol": "vUSDe", - "currentBorrowCap": "1600000000000000000000000", - "currentSupplyCap": "2000000000000000000000000", + "capSource": "live", + "effectiveBorrowCap": "1600000000000000000000000", + "effectiveSupplyCap": "2000000000000000000000000", "minBorrowCap": "320000000000000000000000", "minSupplyCap": "400000000000000000000000" }, { "address": "0x0C1DA220D301155b87318B90692Da8dc43B67340", "symbol": "vUSD1", - "currentBorrowCap": "14400000000000000000000000", - "currentSupplyCap": "16000000000000000000000000", - "minBorrowCap": "2880000000000000000000000", - "minSupplyCap": "3200000000000000000000000" + "capSource": "vip-622", + "effectiveBorrowCap": "4000000000000000000000000", + "effectiveSupplyCap": "5000000000000000000000000", + "minBorrowCap": "800000000000000000000000", + "minSupplyCap": "1000000000000000000000000" }, { "address": "0xd804dE60aFD05EE6B89aab5D152258fD461B07D5", "symbol": "vxSolvBTC", - "currentBorrowCap": "0", - "currentSupplyCap": "2000000000000000000000", + "capSource": "vip-622", + "effectiveBorrowCap": "0", + "effectiveSupplyCap": "1200000000000000000000", "minBorrowCap": "0", - "minSupplyCap": "400000000000000000000" + "minSupplyCap": "240000000000000000000" }, { "address": "0xCC1dB43a06d97f736C7B045AedD03C6707c09BDF", "symbol": "vasBNB", - "currentBorrowCap": "0", - "currentSupplyCap": "216000000000000000000000", + "capSource": "vip-622", + "effectiveBorrowCap": "0", + "effectiveSupplyCap": "216000000000000000000000", "minBorrowCap": "0", "minSupplyCap": "43200000000000000000000" }, { "address": "0x6bCa74586218dB34cdB402295796b79663d816e9", "symbol": "vWBNB", - "currentBorrowCap": "2008000000000000000000000", - "currentSupplyCap": "2672000000000000000000000", + "capSource": "live", + "effectiveBorrowCap": "2008000000000000000000000", + "effectiveSupplyCap": "2672000000000000000000000", "minBorrowCap": "401600000000000000000000", "minSupplyCap": "534400000000000000000000" }, { "address": "0x89c910Eb8c90df818b4649b508Ba22130Dc73Adc", "symbol": "vslisBNB", - "currentBorrowCap": "0", - "currentSupplyCap": "20000000000000000000000", + "capSource": "vip-622", + "effectiveBorrowCap": "0", + "effectiveSupplyCap": "2000000000000000000000", "minBorrowCap": "0", - "minSupplyCap": "4000000000000000000000" + "minSupplyCap": "400000000000000000000" }, { "address": "0x3d5E269787d562b74aCC55F18Bd26C5D09Fa245E", "symbol": "vU", - "currentBorrowCap": "210000000000000000000000000", - "currentSupplyCap": "210000000000000000000000000", + "capSource": "live", + "effectiveBorrowCap": "210000000000000000000000000", + "effectiveSupplyCap": "210000000000000000000000000", "minBorrowCap": "42000000000000000000000000", "minSupplyCap": "42000000000000000000000000" }, { "address": "0x6d3BD68E90B42615cb5abF4B8DE92b154ADc435e", "symbol": "vPT-clisBNB-25JUN2026", - "currentBorrowCap": "0", - "currentSupplyCap": "25000000000000000000000", + "capSource": "vip-622", + "effectiveBorrowCap": "0", + "effectiveSupplyCap": "25000000000000000000000", "minBorrowCap": "0", "minSupplyCap": "5000000000000000000000" }, { "address": "0x92e6Ea74a1A3047DabF4186405a21c7D63a0612A", "symbol": "vXAUM", - "currentBorrowCap": "0", - "currentSupplyCap": "200000000000000000000", + "capSource": "live", + "effectiveBorrowCap": "0", + "effectiveSupplyCap": "200000000000000000000", "minBorrowCap": "0", "minSupplyCap": "40000000000000000000" } ], "skipped": [ + { + "address": "0x95c78222B3D6e262426483D42CfA53685A67Ab9D", + "symbol": "vBUSD", + "reason": "live 20% floors are both zero (delisted / dormant)" + }, + { + "address": "0x2fF3d0F6990a40261c66E1ff2017aCBc282EB6d0", + "symbol": "vSXP", + "reason": "live 20% floors are both zero (delisted / dormant)" + }, + { + "address": "0xf91d58b5aE142DAcC749f58A49FCBac340Cb0343", + "symbol": "vFIL", + "reason": "vip-622 20% floors are both zero (delisted / dormant)" + }, + { + "address": "0x972207A639CC1B374B893cc33Fa251b55CEB7c07", + "symbol": "vBETH", + "reason": "live 20% floors are both zero (delisted / dormant)" + }, { "address": "0xeBD0070237a0713E8D94fEf1B728d3d993d290ef", "reason": "not listed in core pool (poolId 0)" }, + { + "address": "0x5c9476FcD6a4F9a3654139721c949c2233bBbBc8", + "symbol": "vMATIC", + "reason": "live 20% floors are both zero (delisted / dormant)" + }, + { + "address": "0x08CEB3F4a7ed3500cA0982bcd0FC7816688084c3", + "symbol": "vTUSDOLD", + "reason": "live 20% floors are both zero (delisted / dormant)" + }, + { + "address": "0x61eDcFe8Dd6bA3c891CB9bEc2dc7657B3B422E93", + "symbol": "vTRXOLD", + "reason": "live 20% floors are both zero (delisted / dormant)" + }, { "address": "0x78366446547D062f45b4C0f320cDaa6d710D87bb", "reason": "not listed in core pool (poolId 0)" @@ -374,6 +373,16 @@ "address": "0xb91A659E88B51474767CD97EF3196A3e7cEDD2c8", "reason": "not listed in core pool (poolId 0)" }, + { + "address": "0xBf762cd5991cA1DCdDaC9ae5C638F5B5Dc3Bee6E", + "symbol": "vTUSD", + "reason": "vip-622 20% floors are both zero (delisted / dormant)" + }, + { + "address": "0x86e06EAfa6A1eA631Eab51DE500E3D474933739f", + "symbol": "vTHE", + "reason": "vip-622 20% floors are both zero (delisted / dormant)" + }, { "address": "0x6D0cDb3355c93A0cD20071aBbb3622731a95c73E", "reason": "not listed in core pool (poolId 0)" diff --git a/vips/vip-701/vip622Overrides.json b/vips/vip-701/vip622Overrides.json deleted file mode 100644 index e61ab3cd4..000000000 --- a/vips/vip-701/vip622Overrides.json +++ /dev/null @@ -1,124 +0,0 @@ -{ - "_meta": { - "source": "Hardcoded from VIP-622 (PR https://github.com/VenusProtocol/vips/pull/706) post-VIP caps", - "rationale": "VIP-622 right-sizes caps on these markets but is not yet executed on-chain, so coreMarketCaps.json (which reads live state) still reflects the pre-VIP-622 caps. For these markets we override the script-derived 20% floors with 20% of the post-VIP-622 caps; for every other market the script values from coreMarketCaps.json are used as-is.", - "appliesTo": "vips/vip-701/coreMarketCaps.json", - "decimalsNote": "Values are in raw wei. DOGE uses 8 dp, TRX uses 6 dp, everything else 18 dp." - }, - "markets": [ - { - "address": "0x9A0AF7FDb2065Ce470D72664DE73cAE409dA28Ec", - "symbol": "vADA", - "minBorrowCap": "600000000000000000000000", - "minSupplyCap": "3000000000000000000000000" - }, - { - "address": "0x5F0388EBc2B94FA8E123F404b79cCF5f40b29176", - "symbol": "vBCH", - "minBorrowCap": "200000000000000000000", - "minSupplyCap": "1000000000000000000000" - }, - { - "address": "0xec3422Ef92B2fb59e84c8B02Ba73F1fE84Ed8D71", - "symbol": "vDOGE", - "minBorrowCap": "60000000000000", - "minSupplyCap": "1600000000000000" - }, - { - "address": "0xf508fCD89b8bd15579dc79A6827cB4686A3592c8", - "symbol": "vETH", - "minBorrowCap": "7400000000000000000000", - "minSupplyCap": "14400000000000000000000" - }, - { - "address": "0xC4eF4229FEc74Ccfe17B2bdeF7715fAC740BA0ba", - "symbol": "vFDUSD", - "minBorrowCap": "4000000000000000000000000", - "minSupplyCap": "7400000000000000000000000" - }, - { - "address": "0xf91d58b5aE142DAcC749f58A49FCBac340Cb0343", - "symbol": "vFIL", - "minBorrowCap": "0", - "minSupplyCap": "0" - }, - { - "address": "0x650b940a1033B8A1b1873f78730FcFC73ec11f1f", - "symbol": "vLINK", - "minBorrowCap": "4000000000000000000000", - "minSupplyCap": "80000000000000000000000" - }, - { - "address": "0x689E0daB47Ab16bcae87Ec18491692BF621Dc6Ab", - "symbol": "vlisUSD", - "minBorrowCap": "800000000000000000000000", - "minSupplyCap": "1000000000000000000000000" - }, - { - "address": "0x57A5297F2cB2c0AaC9D554660acd6D385Ab50c6B", - "symbol": "vLTC", - "minBorrowCap": "1600000000000000000000", - "minSupplyCap": "10000000000000000000000" - }, - { - "address": "0x89c910Eb8c90df818b4649b508Ba22130Dc73Adc", - "symbol": "vslisBNB", - "minBorrowCap": "0", - "minSupplyCap": "400000000000000000000" - }, - { - "address": "0xf841cb62c19fCd4fF5CD0AaB5939f3140BaaC3Ea", - "symbol": "vSolvBTC", - "minBorrowCap": "4000000000000000000", - "minSupplyCap": "600000000000000000000" - }, - { - "address": "0x86e06EAfa6A1eA631Eab51DE500E3D474933739f", - "symbol": "vTHE", - "minBorrowCap": "0", - "minSupplyCap": "0" - }, - { - "address": "0xC5D3466aA484B040eE977073fcF337f2c00071c1", - "symbol": "vTRX", - "minBorrowCap": "200000000000", - "minSupplyCap": "600000000000" - }, - { - "address": "0xBf762cd5991cA1DCdDaC9ae5C638F5B5Dc3Bee6E", - "symbol": "vTUSD", - "minBorrowCap": "0", - "minSupplyCap": "0" - }, - { - "address": "0x4d41a36D04D97785bcEA57b057C412b278e6Edcc", - "symbol": "vTWT", - "minBorrowCap": "10000000000000000000000", - "minSupplyCap": "400000000000000000000000" - }, - { - "address": "0x0C1DA220D301155b87318B90692Da8dc43B67340", - "symbol": "vUSD1", - "minBorrowCap": "800000000000000000000000", - "minSupplyCap": "1000000000000000000000000" - }, - { - "address": "0x6CFdEc747f37DAf3b87a35a1D9c8AD3063A1A8A0", - "symbol": "vWBETH", - "minBorrowCap": "200000000000000000000", - "minSupplyCap": "8000000000000000000000" - }, - { - "address": "0xB248a295732e0225acd3337607cc01068e3b9c10", - "symbol": "vXRP", - "minBorrowCap": "200000000000000000000000", - "minSupplyCap": "1500000000000000000000000" - }, - { - "address": "0xd804dE60aFD05EE6B89aab5D152258fD461B07D5", - "symbol": "vxSolvBTC", - "minBorrowCap": "0", - "minSupplyCap": "240000000000000000000" - } - ] -} From 93b76d1de3833fc3919c9d6a0bba8410205216aa Mon Sep 17 00:00:00 2001 From: Fred Date: Fri, 15 May 2026 11:28:53 +0000 Subject: [PATCH 11/11] chore: prepare VIP-623 for proposal --- .../abi/AccessControlManager.json | 0 .../{vip-701 => vip-623}/abi/Comptroller.json | 0 .../{vip-701 => vip-623}/abi/EBrake.json | 0 .../{vip-701 => vip-623}/abi/ERC20.json | 0 .../{vip-701 => vip-623}/abi/Executor.json | 0 .../{vip-701 => vip-623}/bscmainnet.ts | 12 ++--- .../{vip-701 => vip-623}/bsctestnet.ts | 6 +-- vips/{vip-701 => vip-623}/bscmainnet.ts | 48 ++++++++++++++----- vips/{vip-701 => vip-623}/bsctestnet.ts | 6 +-- vips/{vip-701 => vip-623}/coreMarketCaps.json | 0 10 files changed, 48 insertions(+), 24 deletions(-) rename simulations/{vip-701 => vip-623}/abi/AccessControlManager.json (100%) rename simulations/{vip-701 => vip-623}/abi/Comptroller.json (100%) rename simulations/{vip-701 => vip-623}/abi/EBrake.json (100%) rename simulations/{vip-701 => vip-623}/abi/ERC20.json (100%) rename simulations/{vip-701 => vip-623}/abi/Executor.json (100%) rename simulations/{vip-701 => vip-623}/bscmainnet.ts (97%) rename simulations/{vip-701 => vip-623}/bsctestnet.ts (96%) rename vips/{vip-701 => vip-623}/bscmainnet.ts (57%) rename vips/{vip-701 => vip-623}/bsctestnet.ts (96%) rename vips/{vip-701 => vip-623}/coreMarketCaps.json (100%) diff --git a/simulations/vip-701/abi/AccessControlManager.json b/simulations/vip-623/abi/AccessControlManager.json similarity index 100% rename from simulations/vip-701/abi/AccessControlManager.json rename to simulations/vip-623/abi/AccessControlManager.json diff --git a/simulations/vip-701/abi/Comptroller.json b/simulations/vip-623/abi/Comptroller.json similarity index 100% rename from simulations/vip-701/abi/Comptroller.json rename to simulations/vip-623/abi/Comptroller.json diff --git a/simulations/vip-701/abi/EBrake.json b/simulations/vip-623/abi/EBrake.json similarity index 100% rename from simulations/vip-701/abi/EBrake.json rename to simulations/vip-623/abi/EBrake.json diff --git a/simulations/vip-701/abi/ERC20.json b/simulations/vip-623/abi/ERC20.json similarity index 100% rename from simulations/vip-701/abi/ERC20.json rename to simulations/vip-623/abi/ERC20.json diff --git a/simulations/vip-701/abi/Executor.json b/simulations/vip-623/abi/Executor.json similarity index 100% rename from simulations/vip-701/abi/Executor.json rename to simulations/vip-623/abi/Executor.json diff --git a/simulations/vip-701/bscmainnet.ts b/simulations/vip-623/bscmainnet.ts similarity index 97% rename from simulations/vip-701/bscmainnet.ts rename to simulations/vip-623/bscmainnet.ts index 0b966bf7a..883e39ab3 100644 --- a/simulations/vip-701/bscmainnet.ts +++ b/simulations/vip-623/bscmainnet.ts @@ -7,7 +7,7 @@ import { expectEvents, initMainnetUser } from "src/utils"; import { forking, pretendExecutingVip, testVip } from "src/vip-framework"; import { vip622 } from "../../vips/vip-622/bscmainnet"; -import vip701, { +import vip623, { ACM, CORE_POOL_MARKET_CONFIGS, EBRAKE, @@ -19,8 +19,8 @@ import vip701, { SIGNAL_MONITOR, USDT, USDT_AMOUNT, -} from "../../vips/vip-701/bscmainnet"; -import coreMarketCaps from "../../vips/vip-701/coreMarketCaps.json"; +} from "../../vips/vip-623/bscmainnet"; +import coreMarketCaps from "../../vips/vip-623/coreMarketCaps.json"; import ACCESS_CONTROL_MANAGER_ABI from "./abi/AccessControlManager.json"; import COMPTROLLER_ABI from "./abi/Comptroller.json"; import EBRAKE_ABI from "./abi/EBrake.json"; @@ -79,7 +79,7 @@ forking(BLOCK_NUMBER, async () => { // VIP-622 is queued at this fork block. coreMarketCaps.json was built off the same // VIP-622 data file, so pre-executing it here makes on-chain caps match the snapshot - // the VIP-701 floors were computed from — the post-VIP-701 assertion below can then + // the VIP-623 floors were computed from — the post-VIP-623 assertion below can then // verify each stored floor equals 20% of the comptroller's live cap. await pretendExecutingVip(await vip622(), NORMAL_TIMELOCK); @@ -126,7 +126,7 @@ forking(BLOCK_NUMBER, async () => { }); }); - testVip("VIP-701 [BNB Chain] Configure tighten-only Executor", await vip701(), { + testVip("VIP-623 [BNB Chain] EBrake Executor Phase -1 Activation & Flux Campaign Funding", await vip623(), { callbackAfterExecution: async txResponse => { // 13 RoleGranted, 2 OwnershipTransferred (Executor + EBrake), 1 MarketConfigSet per Core Pool market. await expectEvents(txResponse, [ACCESS_CONTROL_MANAGER_ABI], ["RoleGranted"], [13]); @@ -188,7 +188,7 @@ forking(BLOCK_NUMBER, async () => { describe("Post-VIP Core Pool market configs", () => { // With VIP-622 pre-executed in `before`, the comptroller's borrow/supply caps now reflect - // the post-VIP-622 state. The script computed VIP-701's floors off the same VIP-622 data, + // the post-VIP-622 state. The script computed VIP-623's floors off the same VIP-622 data, // so the Executor's stored floor for every configured market must equal 20% of the live cap // and the live cap must equal the snapshot the script saw. Catches drift between the two. it("every Core Pool market's stored floors equal 20% of the post-VIP-622 on-chain caps", async () => { diff --git a/simulations/vip-701/bsctestnet.ts b/simulations/vip-623/bsctestnet.ts similarity index 96% rename from simulations/vip-701/bsctestnet.ts rename to simulations/vip-623/bsctestnet.ts index 02e51792f..ed8e20b11 100644 --- a/simulations/vip-701/bsctestnet.ts +++ b/simulations/vip-623/bsctestnet.ts @@ -6,7 +6,7 @@ import { NETWORK_ADDRESSES } from "src/networkAddresses"; import { expectEvents, initMainnetUser } from "src/utils"; import { forking, testVip } from "src/vip-framework"; -import vip701Testnet, { +import vip623Testnet, { ACM, EBRAKE, EBRAKE_EXECUTOR_PERMS, @@ -14,7 +14,7 @@ import vip701Testnet, { EXECUTOR_GOVERNANCE_PERMS, EXECUTOR_MONITOR_PERMS, SIGNAL_MONITOR, -} from "../../vips/vip-701/bsctestnet"; +} from "../../vips/vip-623/bsctestnet"; import ACCESS_CONTROL_MANAGER_ABI from "./abi/AccessControlManager.json"; const { NORMAL_TIMELOCK, FAST_TRACK_TIMELOCK, CRITICAL_TIMELOCK, GUARDIAN } = NETWORK_ADDRESSES.bsctestnet; @@ -64,7 +64,7 @@ forking(BLOCK_NUMBER, async () => { }); }); - testVip("VIP-701 [BNB Testnet] Configure tighten-only Executor", await vip701Testnet(), { + testVip("VIP-623 [BNB Testnet] Configure tighten-only Executor", await vip623Testnet(), { callbackAfterExecution: async txResponse => { // RoleGranted: 4 (monitor on Executor) + 5 (Executor on EBrake) + 4 (Guardian + 3 timelocks setMarketConfig) = 13 await expectEvents(txResponse, [ACCESS_CONTROL_MANAGER_ABI], ["RoleGranted"], [13]); diff --git a/vips/vip-701/bscmainnet.ts b/vips/vip-623/bscmainnet.ts similarity index 57% rename from vips/vip-701/bscmainnet.ts rename to vips/vip-623/bscmainnet.ts index 9738c1df4..fb47dae5d 100644 --- a/vips/vip-701/bscmainnet.ts +++ b/vips/vip-623/bscmainnet.ts @@ -65,15 +65,38 @@ const giveCallPermission = (contract: string, sig: string, account: string) => ( params: [contract, sig, account], }); -export const vip701 = () => { +export const vip623 = () => { const meta = { version: "v2", - title: "VIP-701 [BNB Chain] Configure tighten-only Executor for signal-driven risk parameter control", - description: `#### Description + title: "VIP-623 [BNB Chain] EBrake Executor Phase -1 Activation & Flux Campaign Funding", + description: `#### Summary -This VIP configures the **Executor** contract on BNB Chain mainnet — the validation layer between off-chain signal monitors and EBrake. It validates bounds on-chain and routes tightening actions to EBrake; it cannot loosen parameters. Recovery is exclusively through governance VIPs. +This VIP bundles two BNB Chain actions: -Depends on: VIP-610 (EBrake configuration), VPD-984 (EBrake Phase-0). +1. **EBrake Executor Contract — Phase -1 Activation** — Bring the new tighten-only Executor contract into governance control, enabling off-chain signal monitors to route emergency tightening actions through pre-validated on-chain bounds. The Executor cannot loosen parameters; all recovery still requires a governance VIP. +2. **Binance Wallet × Flux Campaign Funding** — Transfer of 25,000 USDT from the Venus Treasury to the joint Binance Wallet × Flux campaign address. Fluid Protocol contributes the matching amount to the same address as partner funding. + +#### Description + +**EBrake Executor Contract — Phase -1 Activation** + +The Executor is the validation layer between off-chain signal monitors and the EBrake contract. It validates parameter bounds on-chain and routes tightening actions to EBrake. The contract is tighten-only: it cannot raise LTV, raise caps, or unpause. All recovery actions go through a governance VIP. + +Once activated, a signal that detects a breach (e.g. supply cap exceeded via oracle drift) invokes the Executor, which validates the current on-chain state and forwards the tightening call to EBrake within a single transaction — without VIP latency. + +- Source PR: [VenusProtocol/venus-periphery#61](https://github.com/VenusProtocol/venus-periphery/pull/61) (VPD-925) +- Audits: CertiK (2026-04-27), Hashdit (2026-05-08) — reports filed in the PR under audits/ +- Depends on: VIP-610 (EBrake configuration), VPD-984 (EBrake Phase-0) + +**Binance Wallet × Flux Campaign Funding** + +Venus is co-funding a joint campaign with Binance Wallet and Fluid Protocol (Flux) on BNB Chain. Funding flows to a shared multisig address; Fluid contributes the matching amount via their own treasury action, outside this VIP. + +- From: Venus Treasury +- To: 0xBE0EdB1F457334B8d2DfEb3627567137E745A00B +- Token: USDT +- Amount: 25,000 USDT +- Partner contribution: Fluid Protocol — 25,000 USDT to the same address (separate action) #### Proposed Changes @@ -81,27 +104,28 @@ Depends on: VIP-610 (EBrake configuration), VPD-984 (EBrake Phase-0). **2. Grant Signal Monitor permissions on Executor action handlers** -- Authorize the off-chain monitor to call \`handleLTVAdjust\`, \`handleCapAdjust\`, \`handleSupplyCapExceeding\`, \`handleBorrowCapExceeding\` +- Authorize the off-chain monitor to call handleLTVAdjust, handleCapAdjust, handleSupplyCapExceeding, handleBorrowCapExceeding **3. Grant Executor permissions on EBrake** -- Authorize the Executor to call \`pauseBorrow\`, \`pauseSupply\`, \`decreaseCF\`, \`setMarketBorrowCaps\`, \`setMarketSupplyCaps\` on EBrake +- Authorize the Executor to call pauseBorrow, pauseSupply, decreaseCF, setMarketBorrowCaps, setMarketSupplyCaps on EBrake -**4. Grant Guardian and all three Timelocks (Normal, Fast Track, Critical) permission to call \`setMarketConfig\` on Executor** +**4. Grant Guardian and all three Timelocks (Normal, Fast Track, Critical) permission to call setMarketConfig on Executor** -- Lets governance set per-market bounds (\`minBorrowCap\`, \`minSupplyCap\`, \`enabled\`). Granting to all three timelocks + Guardian mirrors VIP-610 and lets Critical (~1h) disable a compromised market's automation instead of waiting 48h on Normal. +- Lets governance set per-market bounds (minBorrowCap, minSupplyCap, enabled). Granting to all three timelocks + Guardian mirrors VIP-610 and lets Critical (~1h) disable a compromised market's automation instead of waiting 48h on Normal. **5. Initialise Executor market configs for every Core Pool market** -- Call \`setMarketConfig\` on the Executor for each listed Core Pool vToken with \`enabled = true\` and per-market floors set to **20% of the effective borrow/supply cap**. Default source is [scripts/fetchCoreMarketCaps.ts](scripts/fetchCoreMarketCaps.ts) → [vips/vip-701/coreMarketCaps.json](vips/vip-701/coreMarketCaps.json) (20% of live caps). For markets being right-sized by VIP-622 (PR #706) but not yet executed, [vips/vip-701/vip622Overrides.json](vips/vip-701/vip622Overrides.json) hardcodes 20% of the post-VIP-622 caps so VIP-701 can publish without waiting for VIP-622 to land on-chain. +- Call setMarketConfig on the Executor for each listed Core Pool vToken with enabled = true and per-market floors set to **20% of the effective borrow/supply cap**. Floors are baked by [scripts/fetchCoreMarketCaps.ts](scripts/fetchCoreMarketCaps.ts) into [vips/vip-623/coreMarketCaps.json](vips/vip-623/coreMarketCaps.json); the script already applies VIP-622 (PR #706) post-cap targets where applicable and drops unlisted / zero-floor markets, so this VIP can publish ahead of VIP-622 landing on-chain. **6. Transfer 25,000 USDT from Venus Treasury to Flux marketing wallet** -- Funds the incoming Flux marketing campaign. Recipient: \`0xBE0EdB1F457334B8d2DfEb3627567137E745A00B\` (multisig shared with Fluid team). +- Funds the incoming Flux marketing campaign. Recipient: 0xBE0EdB1F457334B8d2DfEb3627567137E745A00B (multisig shared with Fluid team). #### References - [GitHub PR: VenusProtocol/venus-periphery#61](https://github.com/VenusProtocol/venus-periphery/pull/61) +- [Community post: May 2026 Risk Parameter Update / Asset Off-boarding](https://community.venus.io/t/may-2026-risk-parameter-update-asset-off-boarding/5785) - VPD-925 — Phase -1 Executor`, forDescription: "I agree that Venus Protocol should proceed with this proposal", againstDescription: "I do not think that Venus Protocol should proceed with this proposal", @@ -144,4 +168,4 @@ Depends on: VIP-610 (EBrake configuration), VPD-984 (EBrake Phase-0). ); }; -export default vip701; +export default vip623; diff --git a/vips/vip-701/bsctestnet.ts b/vips/vip-623/bsctestnet.ts similarity index 96% rename from vips/vip-701/bsctestnet.ts rename to vips/vip-623/bsctestnet.ts index 34b4f9d7f..df00cb89e 100644 --- a/vips/vip-701/bsctestnet.ts +++ b/vips/vip-623/bsctestnet.ts @@ -41,10 +41,10 @@ const giveCallPermission = (contract: string, sig: string, account: string) => ( params: [contract, sig, account], }); -export const vip701Testnet = () => { +export const vip623Testnet = () => { const meta = { version: "v2", - title: "VIP-701 [BNB Testnet] Configure tighten-only Executor for signal-driven risk parameter control", + title: "VIP-623 [BNB Testnet] Configure tighten-only Executor for signal-driven risk parameter control", description: `#### Summary Configures the **Executor** contract on BSC testnet — tighten-only validation layer between off-chain signal monitors and EBrake. @@ -80,4 +80,4 @@ Configures the **Executor** contract on BSC testnet — tighten-only validation ); }; -export default vip701Testnet; +export default vip623Testnet; diff --git a/vips/vip-701/coreMarketCaps.json b/vips/vip-623/coreMarketCaps.json similarity index 100% rename from vips/vip-701/coreMarketCaps.json rename to vips/vip-623/coreMarketCaps.json