From f1feea77f04044a5299c969cbe8d4ffa84c1fb8f Mon Sep 17 00:00:00 2001 From: GitGuru7 Date: Thu, 7 May 2026 11:18:48 +0530 Subject: [PATCH 01/10] feat: add vip for configuring fixed rate vault on BSC mainnet --- simulations/vip-664/bscmainnet.ts | 282 ++++++++++++++++++++++++++++ vips/vip-664/addGrantPermissions.ts | 20 +- vips/vip-664/bscmainnet.ts | 242 ++++++++++++++++++++++++ 3 files changed, 542 insertions(+), 2 deletions(-) create mode 100644 simulations/vip-664/bscmainnet.ts create mode 100644 vips/vip-664/bscmainnet.ts diff --git a/simulations/vip-664/bscmainnet.ts b/simulations/vip-664/bscmainnet.ts new file mode 100644 index 000000000..b1bff4719 --- /dev/null +++ b/simulations/vip-664/bscmainnet.ts @@ -0,0 +1,282 @@ +import { expect } from "chai"; +import { Contract } from "ethers"; +import { parseEther } from "ethers/lib/utils"; +import { ethers } from "hardhat"; +import { NETWORK_ADDRESSES } from "src/networkAddresses"; +import { expectEvents } from "src/utils"; +import { forking, testVip } from "src/vip-framework"; + +import vip664, { + ACM_AGGREGATOR, + ADAPTER_FUNCTIONS, + ADAPTER_GUARDIAN_FUNCTIONS, + CONTROLLER_FUNCTIONS, + CONTROLLER_GUARDIAN_FUNCTIONS, + DEFAULT_ADMIN_ROLE, + EXPECTED_PERMISSION_GRANTED_EVENTS, + INSTITUTIONAL_VAULT_CONTROLLER, + INSTITUTION_POSITION_TOKEN, + LIQUIDATION_ADAPTER, + LIQUIDATOR_WHITELIST, + SETTLER_WHITELIST, +} from "../../vips/vip-664/bscmainnet"; +import ACM_AGGREGATOR_ABI from "./abi/ACMAggregator.json"; +import ACCESS_CONTROL_MANAGER_ABI from "./abi/AccessControlManager.json"; +import INSTITUTION_POSITION_TOKEN_ABI from "./abi/InstitutionPositionToken.json"; +import INSTITUTIONAL_VAULT_CONTROLLER_ABI from "./abi/InstitutionalVaultController.json"; +import LIQUIDATION_ADAPTER_ABI from "./abi/LiquidationAdapter.json"; + +const { bscmainnet } = NETWORK_ADDRESSES; +const { NORMAL_TIMELOCK, FAST_TRACK_TIMELOCK, CRITICAL_TIMELOCK, GUARDIAN, ACCESS_CONTROL_MANAGER } = bscmainnet; + +const FORK_BLOCK = 96701105; + +forking(FORK_BLOCK, async () => { + let accessControlManager: Contract; + let controller: Contract; + let liquidationAdapter: Contract; + let positionToken: Contract; + + before(async () => { + accessControlManager = new ethers.Contract(ACCESS_CONTROL_MANAGER, ACCESS_CONTROL_MANAGER_ABI, ethers.provider); + controller = new ethers.Contract( + INSTITUTIONAL_VAULT_CONTROLLER, + INSTITUTIONAL_VAULT_CONTROLLER_ABI, + ethers.provider, + ); + liquidationAdapter = new ethers.Contract(LIQUIDATION_ADAPTER, LIQUIDATION_ADAPTER_ABI, ethers.provider); + positionToken = new ethers.Contract(INSTITUTION_POSITION_TOKEN, INSTITUTION_POSITION_TOKEN_ABI, ethers.provider); + }); + + // ────────────────────────────────────────────────────────────────────── + // Pre-VIP: verify deployments + // ────────────────────────────────────────────────────────────────────── + describe("Pre-VIP: verify deployments", () => { + it("InstitutionalVaultController proxy should be deployed", async () => { + const code = await ethers.provider.getCode(INSTITUTIONAL_VAULT_CONTROLLER); + expect(code).to.not.equal("0x"); + }); + + it("LiquidationAdapter proxy should be deployed", async () => { + const code = await ethers.provider.getCode(LIQUIDATION_ADAPTER); + expect(code).to.not.equal("0x"); + }); + + it("InstitutionPositionToken should be deployed", async () => { + const code = await ethers.provider.getCode(INSTITUTION_POSITION_TOKEN); + expect(code).to.not.equal("0x"); + }); + + it("InstitutionPositionToken pending owner should be the controller proxy (transferOwnership was called in deploy script)", async () => { + const pendingOwner = await positionToken.pendingOwner(); + expect(pendingOwner).to.equal(INSTITUTIONAL_VAULT_CONTROLLER); + }); + + it("NORMAL_TIMELOCK should not yet have setLiquidationAdapter permission on controller", async () => { + const allowed = await accessControlManager.hasPermission( + NORMAL_TIMELOCK, + INSTITUTIONAL_VAULT_CONTROLLER, + "setLiquidationAdapter(address)", + ); + expect(allowed).to.be.false; + }); + + it("NORMAL_TIMELOCK should not yet have acceptPositionTokenOwnership permission on controller", async () => { + const allowed = await accessControlManager.hasPermission( + NORMAL_TIMELOCK, + INSTITUTIONAL_VAULT_CONTROLLER, + "acceptPositionTokenOwnership()", + ); + expect(allowed).to.be.false; + }); + + it("controller pendingOwner should be NORMAL_TIMELOCK (transferOwnership was called in deploy script)", async () => { + const pendingOwner = await controller.pendingOwner(); + expect(pendingOwner).to.equal(NORMAL_TIMELOCK); + }); + + it("liquidationAdapter pendingOwner should be NORMAL_TIMELOCK (transferOwnership was called in deploy script)", async () => { + const pendingOwner = await liquidationAdapter.pendingOwner(); + expect(pendingOwner).to.equal(NORMAL_TIMELOCK); + }); + + it("liquidationAdapter protocolLiquidationShare should be 0.5e18", async () => { + const share = await liquidationAdapter.protocolLiquidationShare(); + expect(share).to.equal(parseEther("0.5")); + }); + + it("liquidationAdapter closeFactor should be 0.5e18", async () => { + const factor = await liquidationAdapter.closeFactor(); + expect(factor).to.equal(parseEther("0.5")); + }); + + it("controller liquidationAdapter should be address(0) before VIP", async () => { + const adapter = await controller.liquidationAdapter(); + expect(adapter).to.equal(ethers.constants.AddressZero); + }); + }); + + // ────────────────────────────────────────────────────────────────────── + // VIP execution + // ────────────────────────────────────────────────────────────────────── + testVip("VIP-664 [BNB Chain] Configure Institutional Fixed Rate Vault System", await vip664(), { + callbackAfterExecution: async txResponse => { + await expectEvents( + txResponse, + [ACCESS_CONTROL_MANAGER_ABI], + ["PermissionGranted", "RoleGranted", "RoleRevoked"], + [EXPECTED_PERMISSION_GRANTED_EVENTS, EXPECTED_PERMISSION_GRANTED_EVENTS + 1, 1], + ); + await expectEvents( + txResponse, + [ACM_AGGREGATOR_ABI], + ["GrantPermissionsAdded", "GrantPermissionsExecuted"], + [1, 1], + ); + }, + }); + + // ────────────────────────────────────────────────────────────────────── + // Post-VIP: ACM permission and system wiring checks + // ────────────────────────────────────────────────────────────────────── + + describe("Post-VIP: verify setup", () => { + describe("InstitutionalVaultController — NORMAL_TIMELOCK permissions", () => { + for (const funcSig of CONTROLLER_FUNCTIONS) { + it(`NORMAL_TIMELOCK should have permission: ${funcSig}`, async () => { + const allowed = await accessControlManager.hasPermission( + NORMAL_TIMELOCK, + INSTITUTIONAL_VAULT_CONTROLLER, + funcSig, + ); + expect(allowed).to.be.true; + }); + } + }); + + describe("InstitutionalVaultController — FAST_TRACK_TIMELOCK permissions", () => { + for (const funcSig of CONTROLLER_FUNCTIONS) { + it(`FAST_TRACK_TIMELOCK should have permission: ${funcSig}`, async () => { + const allowed = await accessControlManager.hasPermission( + FAST_TRACK_TIMELOCK, + INSTITUTIONAL_VAULT_CONTROLLER, + funcSig, + ); + expect(allowed).to.be.true; + }); + } + }); + + describe("InstitutionalVaultController — CRITICAL_TIMELOCK permissions", () => { + for (const funcSig of CONTROLLER_FUNCTIONS) { + it(`CRITICAL_TIMELOCK should have permission: ${funcSig}`, async () => { + const allowed = await accessControlManager.hasPermission( + CRITICAL_TIMELOCK, + INSTITUTIONAL_VAULT_CONTROLLER, + funcSig, + ); + expect(allowed).to.be.true; + }); + } + }); + + describe("InstitutionalVaultController — GUARDIAN permissions", () => { + for (const funcSig of CONTROLLER_GUARDIAN_FUNCTIONS) { + it(`GUARDIAN should have permission: ${funcSig}`, async () => { + const allowed = await accessControlManager.hasPermission(GUARDIAN, INSTITUTIONAL_VAULT_CONTROLLER, funcSig); + expect(allowed).to.be.true; + }); + } + }); + + describe("LiquidationAdapter — NORMAL_TIMELOCK permissions", () => { + for (const funcSig of ADAPTER_FUNCTIONS) { + it(`NORMAL_TIMELOCK should have permission: ${funcSig}`, async () => { + const allowed = await accessControlManager.hasPermission(NORMAL_TIMELOCK, LIQUIDATION_ADAPTER, funcSig); + expect(allowed).to.be.true; + }); + } + }); + + describe("LiquidationAdapter — CRITICAL_TIMELOCK permissions", () => { + for (const funcSig of ADAPTER_FUNCTIONS) { + it(`CRITICAL_TIMELOCK should have permission: ${funcSig}`, async () => { + const allowed = await accessControlManager.hasPermission(CRITICAL_TIMELOCK, LIQUIDATION_ADAPTER, funcSig); + expect(allowed).to.be.true; + }); + } + }); + + describe("LiquidationAdapter — FAST_TRACK_TIMELOCK permissions", () => { + for (const funcSig of ADAPTER_FUNCTIONS) { + it(`FAST_TRACK_TIMELOCK should have permission: ${funcSig}`, async () => { + const allowed = await accessControlManager.hasPermission(FAST_TRACK_TIMELOCK, LIQUIDATION_ADAPTER, funcSig); + expect(allowed).to.be.true; + }); + } + }); + + describe("LiquidationAdapter — GUARDIAN permissions", () => { + for (const funcSig of ADAPTER_GUARDIAN_FUNCTIONS) { + it(`GUARDIAN should have permission: ${funcSig}`, async () => { + const allowed = await accessControlManager.hasPermission(GUARDIAN, LIQUIDATION_ADAPTER, funcSig); + expect(allowed).to.be.true; + }); + } + }); + + describe("Ownership acceptance", () => { + it("controller owner should be NORMAL_TIMELOCK", async () => { + const owner = await controller.owner(); + expect(owner).to.equal(NORMAL_TIMELOCK); + }); + + it("liquidationAdapter owner should be NORMAL_TIMELOCK", async () => { + const owner = await liquidationAdapter.owner(); + expect(owner).to.equal(NORMAL_TIMELOCK); + }); + }); + + describe("ACM Aggregator cleanup", () => { + it("DEFAULT_ADMIN_ROLE should be revoked from ACM Aggregator", async () => { + expect(await accessControlManager.hasRole(DEFAULT_ADMIN_ROLE, ACM_AGGREGATOR)).to.be.false; + }); + }); + + describe("System wiring", () => { + it("controller liquidationAdapter should be set to the adapter proxy", async () => { + const adapter = await controller.liquidationAdapter(); + expect(adapter).to.equal(LIQUIDATION_ADAPTER); + }); + + it("InstitutionPositionToken owner should be the controller proxy (ownership accepted)", async () => { + const owner = await positionToken.owner(); + expect(owner).to.equal(INSTITUTIONAL_VAULT_CONTROLLER); + }); + }); + + describe("Liquidator whitelist", () => { + for (const account of LIQUIDATOR_WHITELIST) { + it(`${account} should be a whitelisted liquidator`, async () => { + expect(await liquidationAdapter.liquidatorWhitelist(account)).to.be.true; + }); + } + + it("GUARDIAN should NOT be a whitelisted liquidator", async () => { + expect(await liquidationAdapter.liquidatorWhitelist(GUARDIAN)).to.be.false; + }); + }); + + describe("Settler whitelist", () => { + for (const account of SETTLER_WHITELIST) { + it(`${account} should be a whitelisted settler`, async () => { + expect(await liquidationAdapter.settlerWhitelist(account)).to.be.true; + }); + } + + it("GUARDIAN should NOT be a whitelisted settler", async () => { + expect(await liquidationAdapter.settlerWhitelist(GUARDIAN)).to.be.false; + }); + }); + }); +}); diff --git a/vips/vip-664/addGrantPermissions.ts b/vips/vip-664/addGrantPermissions.ts index d18cae838..0d24b68c1 100644 --- a/vips/vip-664/addGrantPermissions.ts +++ b/vips/vip-664/addGrantPermissions.ts @@ -1,6 +1,12 @@ -import { ethers } from "hardhat"; +import hre, { ethers } from "hardhat"; -import { ACM_AGGREGATOR, PERMISSIONS } from "./bsctestnet"; +import * as bscmainnet from "./bscmainnet"; +import * as bsctestnet from "./bsctestnet"; + +const NETWORK_CONFIG: Record = { + bsctestnet: { ACM_AGGREGATOR: bsctestnet.ACM_AGGREGATOR, PERMISSIONS: bsctestnet.PERMISSIONS }, + bscmainnet: { ACM_AGGREGATOR: bscmainnet.ACM_AGGREGATOR, PERMISSIONS: bscmainnet.PERMISSIONS }, +}; const ACM_AGGREGATOR_ABI = [ { @@ -30,6 +36,16 @@ const ACM_AGGREGATOR_ABI = [ ]; async function main() { + const networkName = hre.network.name; + const config = NETWORK_CONFIG[networkName]; + if (!config) { + throw new Error( + `addGrantPermissions: unsupported network "${networkName}". Use --network bsctestnet or --network bscmainnet.`, + ); + } + const { ACM_AGGREGATOR, PERMISSIONS } = config; + + console.log(`Network: ${networkName}, ACM Aggregator: ${ACM_AGGREGATOR}`); console.log(`Total PERMISSIONS to add: ${PERMISSIONS.length}`); const [signer] = await ethers.getSigners(); diff --git a/vips/vip-664/bscmainnet.ts b/vips/vip-664/bscmainnet.ts new file mode 100644 index 000000000..0bdd158fb --- /dev/null +++ b/vips/vip-664/bscmainnet.ts @@ -0,0 +1,242 @@ +import { NETWORK_ADDRESSES } from "src/networkAddresses"; +import { ProposalType } from "src/types"; +import { makeProposal } from "src/utils"; + +const { bscmainnet } = NETWORK_ADDRESSES; +const { NORMAL_TIMELOCK, FAST_TRACK_TIMELOCK, CRITICAL_TIMELOCK, GUARDIAN, ACCESS_CONTROL_MANAGER } = bscmainnet; + +// ────────────────────────────────────────────────────────────────────── +// Deployed addresses +// ────────────────────────────────────────────────────────────────────── + +// TODO: replace with mainnet deployment address before proposing +export const INSTITUTIONAL_VAULT_CONTROLLER = "0x0000000000000000000000000000000000000000"; +// TODO: replace with mainnet deployment address before proposing +export const LIQUIDATION_ADAPTER = "0x0000000000000000000000000000000000000000"; +// TODO: replace with mainnet deployment address before proposing +export const INSTITUTION_POSITION_TOKEN = "0x0000000000000000000000000000000000000000"; + +// ────────────────────────────────────────────────────────────────────── +// ACM Aggregator +// ────────────────────────────────────────────────────────────────────── + +export const ACM_AGGREGATOR = "0x8b443Ea6726E56DF4C4F62f80F0556bB9B2a7c64"; +export const DEFAULT_ADMIN_ROLE = "0x0000000000000000000000000000000000000000000000000000000000000000"; +export const ACM_AGGREGATOR_INDEX = 1; + +// ────────────────────────────────────────────────────────────────────── +// ACM-gated function signatures — InstitutionalVaultController +// ────────────────────────────────────────────────────────────────────── + +export const CONTROLLER_FUNCTIONS = [ + "acceptPositionTokenOwnership()", + "createVault(VaultConfig,InstitutionalConfig,RiskConfig,string,string)", + "openVault(address)", + "partialPauseVault(address)", + "completePauseVault(address)", + "unpauseVault(address)", + "closeVault(address)", + "sweep(address,address)", + "approvePositionTransfer(address)", + "revokePositionTransfer(address)", + "setLiquidationThreshold(address,uint256)", + "setLiquidationIncentive(address,uint256)", + "setLatePenaltyRate(address,uint256)", + "setVaultImplementation(address)", + "setLiquidationAdapter(address)", + "setOracle(address)", + "setProtocolShareReserve(address)", + "setComptroller(address)", + "setTreasury(address)", +]; + +export const CONTROLLER_GUARDIAN_FUNCTIONS = [ + "partialPauseVault(address)", + "completePauseVault(address)", + "unpauseVault(address)", + "openVault(address)", + "closeVault(address)", + "sweep(address,address)", +]; + +// ────────────────────────────────────────────────────────────────────── +// ACM-gated function signatures — LiquidationAdapter +// ────────────────────────────────────────────────────────────────────── + +export const ADAPTER_FUNCTIONS = [ + "setLiquidatorWhitelist(address,bool)", + "setSettlerWhitelist(address,bool)", + "setProtocolLiquidationShare(uint256)", + "setCloseFactor(uint256)", + "sweepProtocolShareToReserve(address)", +]; + +export const ADAPTER_GUARDIAN_FUNCTIONS = [ + "sweepProtocolShareToReserve(address)", + "setSettlerWhitelist(address,bool)", + "setLiquidatorWhitelist(address,bool)", +]; + +// Total PermissionGranted events: 19*3 + 6 + 5*3 + 3 = 81 +export const EXPECTED_PERMISSION_GRANTED_EVENTS = 81; + +// ────────────────────────────────────────────────────────────────────── +// Liquidator / Settler whitelists +// ────────────────────────────────────────────────────────────────────── +// On mainnet the Guardian is NOT whitelisted; dedicated operator addresses +// are used instead. Populate before proposing. + +// TODO: replace with mainnet liquidator addresses before proposing +export const LIQUIDATOR_WHITELIST: string[] = []; +// TODO: replace with mainnet settler addresses before proposing +export const SETTLER_WHITELIST: string[] = []; + +// ────────────────────────────────────────────────────────────────────── +// Full permissions array (pre-loaded into ACM Aggregator via script) +// ────────────────────────────────────────────────────────────────────── + +export const PERMISSIONS: [string, string, string][] = [ + // InstitutionalVaultController — all 19 functions × 3 timelocks + ...CONTROLLER_FUNCTIONS.flatMap(sig => [ + [INSTITUTIONAL_VAULT_CONTROLLER, sig, NORMAL_TIMELOCK] as [string, string, string], + [INSTITUTIONAL_VAULT_CONTROLLER, sig, FAST_TRACK_TIMELOCK] as [string, string, string], + [INSTITUTIONAL_VAULT_CONTROLLER, sig, CRITICAL_TIMELOCK] as [string, string, string], + ]), + + // InstitutionalVaultController — 6 guardian functions + ...CONTROLLER_GUARDIAN_FUNCTIONS.map( + sig => [INSTITUTIONAL_VAULT_CONTROLLER, sig, GUARDIAN] as [string, string, string], + ), + + // LiquidationAdapter — all 5 functions × 3 timelocks + ...ADAPTER_FUNCTIONS.flatMap(sig => [ + [LIQUIDATION_ADAPTER, sig, NORMAL_TIMELOCK] as [string, string, string], + [LIQUIDATION_ADAPTER, sig, FAST_TRACK_TIMELOCK] as [string, string, string], + [LIQUIDATION_ADAPTER, sig, CRITICAL_TIMELOCK] as [string, string, string], + ]), + + // LiquidationAdapter — 3 guardian functions + ...ADAPTER_GUARDIAN_FUNCTIONS.map(sig => [LIQUIDATION_ADAPTER, sig, GUARDIAN] as [string, string, string]), +]; + +export const vip664 = () => { + const meta = { + version: "v1", + title: "VIP-664 [BNB Chain] Configure Institutional Fixed Rate Vault System", + description: `#### Summary + +If passed, this VIP will configure the Institutional Fixed Rate Vault system on BNB Chain: + +1. Grant ACM permissions (81 total) via \`ACMCommandsAggregator\` to all three governance timelocks (Normal, Fast-track, Critical) for all access-controlled functions on \`InstitutionalVaultController\` and \`LiquidationAdapter\`, and to the Guardian for operational functions on both contracts. +2. Accept ownership of \`InstitutionalVaultController\` and \`LiquidationAdapter\` (two-step Ownable2Step transfer initiated in deploy script). +3. Set the \`LiquidationAdapter\` on the controller via \`setLiquidationAdapter()\`. +4. Complete the two-step position token ownership transfer via \`acceptPositionTokenOwnership()\`. +5. Whitelist a dedicated set of liquidator and settler addresses on the \`LiquidationAdapter\`. + +#### Deployed Contracts + +- **InstitutionalVaultController** (proxy): ${INSTITUTIONAL_VAULT_CONTROLLER} +- **LiquidationAdapter** (proxy): ${LIQUIDATION_ADAPTER} +- **InstitutionPositionToken**: ${INSTITUTION_POSITION_TOKEN} + +#### Permission Summary + +| Timelock | Contract | Permissions | +|---|---|---| +| Normal | InstitutionalVaultController | 19 functions (all operational + setter functions) | +| Fast-track | InstitutionalVaultController | 19 functions (all operational + setter functions) | +| Critical | InstitutionalVaultController | 19 functions (all operational + setter functions) | +| Guardian | InstitutionalVaultController | 6 functions (pause/unpause + open/close/sweep) | +| Normal | LiquidationAdapter | 5 functions (all setter functions) | +| Fast-track | LiquidationAdapter | 5 functions (all setter functions) | +| Critical | LiquidationAdapter | 5 functions (all setter functions) | +| Guardian | LiquidationAdapter | 3 functions (sweep + whitelist management) |`, + 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( + [ + // ────────────────────────────────────────────────────────────────────── + // Phase 1 — ACM permissions via Aggregator + // ────────────────────────────────────────────────────────────────────── + // Load the batch into the aggregator, grant admin role, execute batched + // permissions, then revoke admin role. + + { + target: ACM_AGGREGATOR, + signature: "addGrantPermissions((address,string,address)[])", + params: [PERMISSIONS], + }, + { + target: ACCESS_CONTROL_MANAGER, + signature: "grantRole(bytes32,address)", + params: [DEFAULT_ADMIN_ROLE, ACM_AGGREGATOR], + }, + { + target: ACM_AGGREGATOR, + signature: "executeGrantPermissions(uint256)", + params: [ACM_AGGREGATOR_INDEX], + }, + { + target: ACCESS_CONTROL_MANAGER, + signature: "revokeRole(bytes32,address)", + params: [DEFAULT_ADMIN_ROLE, ACM_AGGREGATOR], + }, + + // ────────────────────────────────────────────────────────────────────── + // Phase 2 — Ownership acceptance + // ────────────────────────────────────────────────────────────────────── + // Deploy script called transferOwnership(NORMAL_TIMELOCK) on both contracts. + // acceptOwnership() completes the Ownable2Step transfer. + + { + target: INSTITUTIONAL_VAULT_CONTROLLER, + signature: "acceptOwnership()", + params: [], + }, + { + target: LIQUIDATION_ADAPTER, + signature: "acceptOwnership()", + params: [], + }, + + // ────────────────────────────────────────────────────────────────────── + // Phase 3 — System wiring + // ────────────────────────────────────────────────────────────────────── + // Note: NORMAL_TIMELOCK must have the ACM permission for both calls below, + // which is granted in Phase 1 above. Commands execute atomically in order. + + { + target: INSTITUTIONAL_VAULT_CONTROLLER, + signature: "setLiquidationAdapter(address)", + params: [LIQUIDATION_ADAPTER], + }, + { + target: INSTITUTIONAL_VAULT_CONTROLLER, + signature: "acceptPositionTokenOwnership()", + params: [], + }, + + // ────────────────────────────────────────────────────────────────────── + // Phase 4 — Liquidator / settler whitelist configuration + // ────────────────────────────────────────────────────────────────────── + + ...LIQUIDATOR_WHITELIST.map(account => ({ + target: LIQUIDATION_ADAPTER, + signature: "setLiquidatorWhitelist(address,bool)", + params: [account, true], + })), + ...SETTLER_WHITELIST.map(account => ({ + target: LIQUIDATION_ADAPTER, + signature: "setSettlerWhitelist(address,bool)", + params: [account, true], + })), + ], + meta, + ProposalType.REGULAR, + ); +}; + +export default vip664; From 6c2b8c1f6cb49d8affa935a4564f58819c0a3065 Mon Sep 17 00:00:00 2001 From: GitGuru7 Date: Fri, 8 May 2026 08:53:26 +0530 Subject: [PATCH 02/10] fix: update acm grants --- simulations/vip-664/bscmainnet.ts | 250 ++++++++------------------- vips/vip-664/bscmainnet.ts | 274 ++++++++++++++---------------- 2 files changed, 198 insertions(+), 326 deletions(-) diff --git a/simulations/vip-664/bscmainnet.ts b/simulations/vip-664/bscmainnet.ts index b1bff4719..04cb283e3 100644 --- a/simulations/vip-664/bscmainnet.ts +++ b/simulations/vip-664/bscmainnet.ts @@ -8,16 +8,13 @@ import { forking, testVip } from "src/vip-framework"; import vip664, { ACM_AGGREGATOR, - ADAPTER_FUNCTIONS, - ADAPTER_GUARDIAN_FUNCTIONS, - CONTROLLER_FUNCTIONS, - CONTROLLER_GUARDIAN_FUNCTIONS, DEFAULT_ADMIN_ROLE, EXPECTED_PERMISSION_GRANTED_EVENTS, INSTITUTIONAL_VAULT_CONTROLLER, INSTITUTION_POSITION_TOKEN, LIQUIDATION_ADAPTER, LIQUIDATOR_WHITELIST, + PERMISSION_ENTRIES, SETTLER_WHITELIST, } from "../../vips/vip-664/bscmainnet"; import ACM_AGGREGATOR_ABI from "./abi/ACMAggregator.json"; @@ -26,11 +23,24 @@ import INSTITUTION_POSITION_TOKEN_ABI from "./abi/InstitutionPositionToken.json" import INSTITUTIONAL_VAULT_CONTROLLER_ABI from "./abi/InstitutionalVaultController.json"; import LIQUIDATION_ADAPTER_ABI from "./abi/LiquidationAdapter.json"; -const { bscmainnet } = NETWORK_ADDRESSES; -const { NORMAL_TIMELOCK, FAST_TRACK_TIMELOCK, CRITICAL_TIMELOCK, GUARDIAN, ACCESS_CONTROL_MANAGER } = bscmainnet; +const { + NORMAL_TIMELOCK: NORMAL, + FAST_TRACK_TIMELOCK: FAST_TRACK, + CRITICAL_TIMELOCK: CRITICAL, + GUARDIAN, + ACCESS_CONTROL_MANAGER, +} = NETWORK_ADDRESSES.bscmainnet; const FORK_BLOCK = 96701105; +// To make test names readable. +const LABEL: Record = { + [NORMAL]: "Normal", + [FAST_TRACK]: "FastTrack", + [CRITICAL]: "Critical", + [GUARDIAN]: "Guardian", +}; + forking(FORK_BLOCK, async () => { let accessControlManager: Contract; let controller: Contract; @@ -48,77 +58,56 @@ forking(FORK_BLOCK, async () => { positionToken = new ethers.Contract(INSTITUTION_POSITION_TOKEN, INSTITUTION_POSITION_TOKEN_ABI, ethers.provider); }); - // ────────────────────────────────────────────────────────────────────── - // Pre-VIP: verify deployments - // ────────────────────────────────────────────────────────────────────── + // Contracts deployed and deploy-script state is in place. describe("Pre-VIP: verify deployments", () => { it("InstitutionalVaultController proxy should be deployed", async () => { - const code = await ethers.provider.getCode(INSTITUTIONAL_VAULT_CONTROLLER); - expect(code).to.not.equal("0x"); + expect(await ethers.provider.getCode(INSTITUTIONAL_VAULT_CONTROLLER)).to.not.equal("0x"); }); it("LiquidationAdapter proxy should be deployed", async () => { - const code = await ethers.provider.getCode(LIQUIDATION_ADAPTER); - expect(code).to.not.equal("0x"); + expect(await ethers.provider.getCode(LIQUIDATION_ADAPTER)).to.not.equal("0x"); }); it("InstitutionPositionToken should be deployed", async () => { - const code = await ethers.provider.getCode(INSTITUTION_POSITION_TOKEN); - expect(code).to.not.equal("0x"); - }); - - it("InstitutionPositionToken pending owner should be the controller proxy (transferOwnership was called in deploy script)", async () => { - const pendingOwner = await positionToken.pendingOwner(); - expect(pendingOwner).to.equal(INSTITUTIONAL_VAULT_CONTROLLER); + expect(await ethers.provider.getCode(INSTITUTION_POSITION_TOKEN)).to.not.equal("0x"); }); - it("NORMAL_TIMELOCK should not yet have setLiquidationAdapter permission on controller", async () => { - const allowed = await accessControlManager.hasPermission( - NORMAL_TIMELOCK, - INSTITUTIONAL_VAULT_CONTROLLER, - "setLiquidationAdapter(address)", - ); - expect(allowed).to.be.false; - }); - - it("NORMAL_TIMELOCK should not yet have acceptPositionTokenOwnership permission on controller", async () => { - const allowed = await accessControlManager.hasPermission( - NORMAL_TIMELOCK, - INSTITUTIONAL_VAULT_CONTROLLER, - "acceptPositionTokenOwnership()", - ); - expect(allowed).to.be.false; + it("InstitutionPositionToken pendingOwner should be the controller proxy", async () => { + expect(await positionToken.pendingOwner()).to.equal(INSTITUTIONAL_VAULT_CONTROLLER); }); - it("controller pendingOwner should be NORMAL_TIMELOCK (transferOwnership was called in deploy script)", async () => { - const pendingOwner = await controller.pendingOwner(); - expect(pendingOwner).to.equal(NORMAL_TIMELOCK); + it("controller pendingOwner should be Normal timelock", async () => { + expect(await controller.pendingOwner()).to.equal(NORMAL); }); - it("liquidationAdapter pendingOwner should be NORMAL_TIMELOCK (transferOwnership was called in deploy script)", async () => { - const pendingOwner = await liquidationAdapter.pendingOwner(); - expect(pendingOwner).to.equal(NORMAL_TIMELOCK); + it("liquidationAdapter pendingOwner should be Normal timelock", async () => { + expect(await liquidationAdapter.pendingOwner()).to.equal(NORMAL); }); it("liquidationAdapter protocolLiquidationShare should be 0.5e18", async () => { - const share = await liquidationAdapter.protocolLiquidationShare(); - expect(share).to.equal(parseEther("0.5")); + expect(await liquidationAdapter.protocolLiquidationShare()).to.equal(parseEther("0.5")); }); it("liquidationAdapter closeFactor should be 0.5e18", async () => { - const factor = await liquidationAdapter.closeFactor(); - expect(factor).to.equal(parseEther("0.5")); + expect(await liquidationAdapter.closeFactor()).to.equal(parseEther("0.5")); }); it("controller liquidationAdapter should be address(0) before VIP", async () => { - const adapter = await controller.liquidationAdapter(); - expect(adapter).to.equal(ethers.constants.AddressZero); + expect(await controller.liquidationAdapter()).to.equal(ethers.constants.AddressZero); }); }); - // ────────────────────────────────────────────────────────────────────── - // VIP execution - // ────────────────────────────────────────────────────────────────────── + // None of the planned grants exist yet. + describe("Pre-VIP: ACM permissions not yet granted", () => { + for (const { target, fn, callers } of PERMISSION_ENTRIES) { + for (const account of callers) { + it(`${LABEL[account]} should NOT yet have permission: ${fn} on ${target}`, async () => { + expect(await accessControlManager.hasPermission(account, target, fn)).to.be.false; + }); + } + } + }); + testVip("VIP-664 [BNB Chain] Configure Institutional Fixed Rate Vault System", await vip664(), { callbackAfterExecution: async txResponse => { await expectEvents( @@ -136,147 +125,58 @@ forking(FORK_BLOCK, async () => { }, }); - // ────────────────────────────────────────────────────────────────────── - // Post-VIP: ACM permission and system wiring checks - // ────────────────────────────────────────────────────────────────────── - - describe("Post-VIP: verify setup", () => { - describe("InstitutionalVaultController — NORMAL_TIMELOCK permissions", () => { - for (const funcSig of CONTROLLER_FUNCTIONS) { - it(`NORMAL_TIMELOCK should have permission: ${funcSig}`, async () => { - const allowed = await accessControlManager.hasPermission( - NORMAL_TIMELOCK, - INSTITUTIONAL_VAULT_CONTROLLER, - funcSig, - ); - expect(allowed).to.be.true; - }); - } - }); - - describe("InstitutionalVaultController — FAST_TRACK_TIMELOCK permissions", () => { - for (const funcSig of CONTROLLER_FUNCTIONS) { - it(`FAST_TRACK_TIMELOCK should have permission: ${funcSig}`, async () => { - const allowed = await accessControlManager.hasPermission( - FAST_TRACK_TIMELOCK, - INSTITUTIONAL_VAULT_CONTROLLER, - funcSig, - ); - expect(allowed).to.be.true; - }); - } - }); - - describe("InstitutionalVaultController — CRITICAL_TIMELOCK permissions", () => { - for (const funcSig of CONTROLLER_FUNCTIONS) { - it(`CRITICAL_TIMELOCK should have permission: ${funcSig}`, async () => { - const allowed = await accessControlManager.hasPermission( - CRITICAL_TIMELOCK, - INSTITUTIONAL_VAULT_CONTROLLER, - funcSig, - ); - expect(allowed).to.be.true; - }); - } - }); - - describe("InstitutionalVaultController — GUARDIAN permissions", () => { - for (const funcSig of CONTROLLER_GUARDIAN_FUNCTIONS) { - it(`GUARDIAN should have permission: ${funcSig}`, async () => { - const allowed = await accessControlManager.hasPermission(GUARDIAN, INSTITUTIONAL_VAULT_CONTROLLER, funcSig); - expect(allowed).to.be.true; - }); - } - }); - - describe("LiquidationAdapter — NORMAL_TIMELOCK permissions", () => { - for (const funcSig of ADAPTER_FUNCTIONS) { - it(`NORMAL_TIMELOCK should have permission: ${funcSig}`, async () => { - const allowed = await accessControlManager.hasPermission(NORMAL_TIMELOCK, LIQUIDATION_ADAPTER, funcSig); - expect(allowed).to.be.true; + // Every planned grant is now active. + describe("Post-VIP: ACM permissions granted", () => { + for (const { target, fn, callers } of PERMISSION_ENTRIES) { + for (const account of callers) { + it(`${LABEL[account]} should have permission: ${fn} on ${target}`, async () => { + expect(await accessControlManager.hasPermission(account, target, fn)).to.be.true; }); } - }); - - describe("LiquidationAdapter — CRITICAL_TIMELOCK permissions", () => { - for (const funcSig of ADAPTER_FUNCTIONS) { - it(`CRITICAL_TIMELOCK should have permission: ${funcSig}`, async () => { - const allowed = await accessControlManager.hasPermission(CRITICAL_TIMELOCK, LIQUIDATION_ADAPTER, funcSig); - expect(allowed).to.be.true; - }); - } - }); + } + }); - describe("LiquidationAdapter — FAST_TRACK_TIMELOCK permissions", () => { - for (const funcSig of ADAPTER_FUNCTIONS) { - it(`FAST_TRACK_TIMELOCK should have permission: ${funcSig}`, async () => { - const allowed = await accessControlManager.hasPermission(FAST_TRACK_TIMELOCK, LIQUIDATION_ADAPTER, funcSig); - expect(allowed).to.be.true; - }); - } + // Ownership accepted, adapter wired, admin role revoked. + describe("Post-VIP: ownership and wiring", () => { + it("controller owner should be Normal timelock", async () => { + expect(await controller.owner()).to.equal(NORMAL); }); - describe("LiquidationAdapter — GUARDIAN permissions", () => { - for (const funcSig of ADAPTER_GUARDIAN_FUNCTIONS) { - it(`GUARDIAN should have permission: ${funcSig}`, async () => { - const allowed = await accessControlManager.hasPermission(GUARDIAN, LIQUIDATION_ADAPTER, funcSig); - expect(allowed).to.be.true; - }); - } + it("liquidationAdapter owner should be Normal timelock", async () => { + expect(await liquidationAdapter.owner()).to.equal(NORMAL); }); - describe("Ownership acceptance", () => { - it("controller owner should be NORMAL_TIMELOCK", async () => { - const owner = await controller.owner(); - expect(owner).to.equal(NORMAL_TIMELOCK); - }); - - it("liquidationAdapter owner should be NORMAL_TIMELOCK", async () => { - const owner = await liquidationAdapter.owner(); - expect(owner).to.equal(NORMAL_TIMELOCK); - }); + it("InstitutionPositionToken owner should be the controller proxy", async () => { + expect(await positionToken.owner()).to.equal(INSTITUTIONAL_VAULT_CONTROLLER); }); - describe("ACM Aggregator cleanup", () => { - it("DEFAULT_ADMIN_ROLE should be revoked from ACM Aggregator", async () => { - expect(await accessControlManager.hasRole(DEFAULT_ADMIN_ROLE, ACM_AGGREGATOR)).to.be.false; - }); + it("controller liquidationAdapter should be set to the adapter proxy", async () => { + expect(await controller.liquidationAdapter()).to.equal(LIQUIDATION_ADAPTER); }); - describe("System wiring", () => { - it("controller liquidationAdapter should be set to the adapter proxy", async () => { - const adapter = await controller.liquidationAdapter(); - expect(adapter).to.equal(LIQUIDATION_ADAPTER); - }); - - it("InstitutionPositionToken owner should be the controller proxy (ownership accepted)", async () => { - const owner = await positionToken.owner(); - expect(owner).to.equal(INSTITUTIONAL_VAULT_CONTROLLER); - }); + it("DEFAULT_ADMIN_ROLE should be revoked from ACM Aggregator", async () => { + expect(await accessControlManager.hasRole(DEFAULT_ADMIN_ROLE, ACM_AGGREGATOR)).to.be.false; }); + }); - describe("Liquidator whitelist", () => { - for (const account of LIQUIDATOR_WHITELIST) { - it(`${account} should be a whitelisted liquidator`, async () => { - expect(await liquidationAdapter.liquidatorWhitelist(account)).to.be.true; - }); - } - - it("GUARDIAN should NOT be a whitelisted liquidator", async () => { - expect(await liquidationAdapter.liquidatorWhitelist(GUARDIAN)).to.be.false; + // Dedicated operator addresses whitelisted; Guardian explicitly is not. + describe("Post-VIP: liquidator/settler whitelists", () => { + for (const account of LIQUIDATOR_WHITELIST) { + it(`${account} should be a whitelisted liquidator`, async () => { + expect(await liquidationAdapter.liquidatorWhitelist(account)).to.be.true; }); + } + it("GUARDIAN should NOT be a whitelisted liquidator", async () => { + expect(await liquidationAdapter.liquidatorWhitelist(GUARDIAN)).to.be.false; }); - describe("Settler whitelist", () => { - for (const account of SETTLER_WHITELIST) { - it(`${account} should be a whitelisted settler`, async () => { - expect(await liquidationAdapter.settlerWhitelist(account)).to.be.true; - }); - } - - it("GUARDIAN should NOT be a whitelisted settler", async () => { - expect(await liquidationAdapter.settlerWhitelist(GUARDIAN)).to.be.false; + for (const account of SETTLER_WHITELIST) { + it(`${account} should be a whitelisted settler`, async () => { + expect(await liquidationAdapter.settlerWhitelist(account)).to.be.true; }); + } + it("GUARDIAN should NOT be a whitelisted settler", async () => { + expect(await liquidationAdapter.settlerWhitelist(GUARDIAN)).to.be.false; }); }); }); diff --git a/vips/vip-664/bscmainnet.ts b/vips/vip-664/bscmainnet.ts index 0bdd158fb..4fcb426d4 100644 --- a/vips/vip-664/bscmainnet.ts +++ b/vips/vip-664/bscmainnet.ts @@ -2,122 +2,133 @@ import { NETWORK_ADDRESSES } from "src/networkAddresses"; import { ProposalType } from "src/types"; import { makeProposal } from "src/utils"; -const { bscmainnet } = NETWORK_ADDRESSES; -const { NORMAL_TIMELOCK, FAST_TRACK_TIMELOCK, CRITICAL_TIMELOCK, GUARDIAN, ACCESS_CONTROL_MANAGER } = bscmainnet; - -// ────────────────────────────────────────────────────────────────────── -// Deployed addresses -// ────────────────────────────────────────────────────────────────────── - -// TODO: replace with mainnet deployment address before proposing +const { + NORMAL_TIMELOCK: NORMAL, + FAST_TRACK_TIMELOCK: FAST_TRACK, + CRITICAL_TIMELOCK: CRITICAL, + GUARDIAN, + ACCESS_CONTROL_MANAGER, +} = NETWORK_ADDRESSES.bscmainnet; + +export interface PermissionEntry { + target: string; + fn: string; + callers: string[]; +} + +// Deployed addresses (TODO: replace with mainnet addresses before proposing) export const INSTITUTIONAL_VAULT_CONTROLLER = "0x0000000000000000000000000000000000000000"; -// TODO: replace with mainnet deployment address before proposing export const LIQUIDATION_ADAPTER = "0x0000000000000000000000000000000000000000"; -// TODO: replace with mainnet deployment address before proposing export const INSTITUTION_POSITION_TOKEN = "0x0000000000000000000000000000000000000000"; -// ────────────────────────────────────────────────────────────────────── -// ACM Aggregator -// ────────────────────────────────────────────────────────────────────── - +// ACM aggregator (mainnet) export const ACM_AGGREGATOR = "0x8b443Ea6726E56DF4C4F62f80F0556bB9B2a7c64"; export const DEFAULT_ADMIN_ROLE = "0x0000000000000000000000000000000000000000000000000000000000000000"; export const ACM_AGGREGATOR_INDEX = 1; -// ────────────────────────────────────────────────────────────────────── -// ACM-gated function signatures — InstitutionalVaultController -// ────────────────────────────────────────────────────────────────────── - -export const CONTROLLER_FUNCTIONS = [ - "acceptPositionTokenOwnership()", - "createVault(VaultConfig,InstitutionalConfig,RiskConfig,string,string)", - "openVault(address)", - "partialPauseVault(address)", - "completePauseVault(address)", - "unpauseVault(address)", - "closeVault(address)", - "sweep(address,address)", - "approvePositionTransfer(address)", - "revokePositionTransfer(address)", - "setLiquidationThreshold(address,uint256)", - "setLiquidationIncentive(address,uint256)", - "setLatePenaltyRate(address,uint256)", - "setVaultImplementation(address)", - "setLiquidationAdapter(address)", - "setOracle(address)", - "setProtocolShareReserve(address)", - "setComptroller(address)", - "setTreasury(address)", +export const LIQUIDATOR_WHITELIST: string[] = []; // TBD: populate before proposing. +export const SETTLER_WHITELIST: string[] = []; // TBD: populate before proposing. + +export const PERMISSION_ENTRIES: PermissionEntry[] = [ + // InstitutionalVaultController + { target: INSTITUTIONAL_VAULT_CONTROLLER, fn: "acceptPositionTokenOwnership()", callers: [NORMAL] }, + { + target: INSTITUTIONAL_VAULT_CONTROLLER, + fn: "createVault(VaultConfig,InstitutionalConfig,RiskConfig,string,string)", + callers: [NORMAL, FAST_TRACK, CRITICAL], + }, + { + target: INSTITUTIONAL_VAULT_CONTROLLER, + fn: "openVault(address)", + callers: [NORMAL, FAST_TRACK, CRITICAL, GUARDIAN], + }, + { + target: INSTITUTIONAL_VAULT_CONTROLLER, + fn: "partialPauseVault(address)", + callers: [NORMAL, FAST_TRACK, CRITICAL, GUARDIAN], + }, + { + target: INSTITUTIONAL_VAULT_CONTROLLER, + fn: "completePauseVault(address)", + callers: [NORMAL, FAST_TRACK, CRITICAL, GUARDIAN], + }, + { + target: INSTITUTIONAL_VAULT_CONTROLLER, + fn: "unpauseVault(address)", + callers: [NORMAL, FAST_TRACK, CRITICAL, GUARDIAN], + }, + { + target: INSTITUTIONAL_VAULT_CONTROLLER, + fn: "closeVault(address)", + callers: [NORMAL, FAST_TRACK, CRITICAL, GUARDIAN], + }, + { + target: INSTITUTIONAL_VAULT_CONTROLLER, + fn: "sweep(address,address)", + callers: [NORMAL, FAST_TRACK, CRITICAL, GUARDIAN], + }, + { + target: INSTITUTIONAL_VAULT_CONTROLLER, + fn: "approvePositionTransfer(address)", + callers: [NORMAL, FAST_TRACK, CRITICAL], + }, + { + target: INSTITUTIONAL_VAULT_CONTROLLER, + fn: "revokePositionTransfer(address)", + callers: [NORMAL, FAST_TRACK, CRITICAL, GUARDIAN], + }, + { + target: INSTITUTIONAL_VAULT_CONTROLLER, + fn: "setLiquidationThreshold(address,uint256)", + callers: [NORMAL, FAST_TRACK, CRITICAL], + }, + { + target: INSTITUTIONAL_VAULT_CONTROLLER, + fn: "setLiquidationIncentive(address,uint256)", + callers: [NORMAL, FAST_TRACK, CRITICAL], + }, + { + target: INSTITUTIONAL_VAULT_CONTROLLER, + fn: "setLatePenaltyRate(address,uint256)", + callers: [NORMAL, FAST_TRACK, CRITICAL], + }, + { + target: INSTITUTIONAL_VAULT_CONTROLLER, + fn: "setVaultImplementation(address)", + callers: [NORMAL, FAST_TRACK, CRITICAL], + }, + { target: INSTITUTIONAL_VAULT_CONTROLLER, fn: "setLiquidationAdapter(address)", callers: [NORMAL, GUARDIAN] }, + { target: INSTITUTIONAL_VAULT_CONTROLLER, fn: "setOracle(address)", callers: [NORMAL, GUARDIAN] }, + { target: INSTITUTIONAL_VAULT_CONTROLLER, fn: "setProtocolShareReserve(address)", callers: [NORMAL, GUARDIAN] }, + { target: INSTITUTIONAL_VAULT_CONTROLLER, fn: "setComptroller(address)", callers: [NORMAL, GUARDIAN] }, + { target: INSTITUTIONAL_VAULT_CONTROLLER, fn: "setTreasury(address)", callers: [NORMAL, GUARDIAN] }, + + // LiquidationAdapter + { + target: LIQUIDATION_ADAPTER, + fn: "setLiquidatorWhitelist(address,bool)", + callers: [NORMAL, FAST_TRACK, CRITICAL, GUARDIAN], + }, + { + target: LIQUIDATION_ADAPTER, + fn: "setSettlerWhitelist(address,bool)", + callers: [NORMAL, FAST_TRACK, CRITICAL, GUARDIAN], + }, + { target: LIQUIDATION_ADAPTER, fn: "setProtocolLiquidationShare(uint256)", callers: [NORMAL, FAST_TRACK, CRITICAL] }, + { target: LIQUIDATION_ADAPTER, fn: "setCloseFactor(uint256)", callers: [NORMAL, FAST_TRACK, CRITICAL] }, + { + target: LIQUIDATION_ADAPTER, + fn: "sweepProtocolShareToReserve(address)", + callers: [NORMAL, FAST_TRACK, CRITICAL, GUARDIAN], + }, ]; -export const CONTROLLER_GUARDIAN_FUNCTIONS = [ - "partialPauseVault(address)", - "completePauseVault(address)", - "unpauseVault(address)", - "openVault(address)", - "closeVault(address)", - "sweep(address,address)", -]; +export const buildPermissions = (table: PermissionEntry[]): [string, string, string][] => + table.flatMap(({ target, fn, callers }) => callers.map(c => [target, fn, c] as [string, string, string])); -// ────────────────────────────────────────────────────────────────────── -// ACM-gated function signatures — LiquidationAdapter -// ────────────────────────────────────────────────────────────────────── +export const PERMISSIONS: [string, string, string][] = buildPermissions(PERMISSION_ENTRIES); -export const ADAPTER_FUNCTIONS = [ - "setLiquidatorWhitelist(address,bool)", - "setSettlerWhitelist(address,bool)", - "setProtocolLiquidationShare(uint256)", - "setCloseFactor(uint256)", - "sweepProtocolShareToReserve(address)", -]; - -export const ADAPTER_GUARDIAN_FUNCTIONS = [ - "sweepProtocolShareToReserve(address)", - "setSettlerWhitelist(address,bool)", - "setLiquidatorWhitelist(address,bool)", -]; - -// Total PermissionGranted events: 19*3 + 6 + 5*3 + 3 = 81 -export const EXPECTED_PERMISSION_GRANTED_EVENTS = 81; - -// ────────────────────────────────────────────────────────────────────── -// Liquidator / Settler whitelists -// ────────────────────────────────────────────────────────────────────── -// On mainnet the Guardian is NOT whitelisted; dedicated operator addresses -// are used instead. Populate before proposing. - -// TODO: replace with mainnet liquidator addresses before proposing -export const LIQUIDATOR_WHITELIST: string[] = []; -// TODO: replace with mainnet settler addresses before proposing -export const SETTLER_WHITELIST: string[] = []; - -// ────────────────────────────────────────────────────────────────────── -// Full permissions array (pre-loaded into ACM Aggregator via script) -// ────────────────────────────────────────────────────────────────────── - -export const PERMISSIONS: [string, string, string][] = [ - // InstitutionalVaultController — all 19 functions × 3 timelocks - ...CONTROLLER_FUNCTIONS.flatMap(sig => [ - [INSTITUTIONAL_VAULT_CONTROLLER, sig, NORMAL_TIMELOCK] as [string, string, string], - [INSTITUTIONAL_VAULT_CONTROLLER, sig, FAST_TRACK_TIMELOCK] as [string, string, string], - [INSTITUTIONAL_VAULT_CONTROLLER, sig, CRITICAL_TIMELOCK] as [string, string, string], - ]), - - // InstitutionalVaultController — 6 guardian functions - ...CONTROLLER_GUARDIAN_FUNCTIONS.map( - sig => [INSTITUTIONAL_VAULT_CONTROLLER, sig, GUARDIAN] as [string, string, string], - ), - - // LiquidationAdapter — all 5 functions × 3 timelocks - ...ADAPTER_FUNCTIONS.flatMap(sig => [ - [LIQUIDATION_ADAPTER, sig, NORMAL_TIMELOCK] as [string, string, string], - [LIQUIDATION_ADAPTER, sig, FAST_TRACK_TIMELOCK] as [string, string, string], - [LIQUIDATION_ADAPTER, sig, CRITICAL_TIMELOCK] as [string, string, string], - ]), - - // LiquidationAdapter — 3 guardian functions - ...ADAPTER_GUARDIAN_FUNCTIONS.map(sig => [LIQUIDATION_ADAPTER, sig, GUARDIAN] as [string, string, string]), -]; +export const EXPECTED_PERMISSION_GRANTED_EVENTS = PERMISSIONS.length; export const vip664 = () => { const meta = { @@ -127,7 +138,7 @@ export const vip664 = () => { If passed, this VIP will configure the Institutional Fixed Rate Vault system on BNB Chain: -1. Grant ACM permissions (81 total) via \`ACMCommandsAggregator\` to all three governance timelocks (Normal, Fast-track, Critical) for all access-controlled functions on \`InstitutionalVaultController\` and \`LiquidationAdapter\`, and to the Guardian for operational functions on both contracts. +1. Grant ACM permissions (${EXPECTED_PERMISSION_GRANTED_EVENTS} total) via \`ACMCommandsAggregator\` to the appropriate set of timelocks (Normal, Fast-track, Critical) and the Guardian for each access-controlled function on \`InstitutionalVaultController\` and \`LiquidationAdapter\`. 2. Accept ownership of \`InstitutionalVaultController\` and \`LiquidationAdapter\` (two-step Ownable2Step transfer initiated in deploy script). 3. Set the \`LiquidationAdapter\` on the controller via \`setLiquidationAdapter()\`. 4. Complete the two-step position token ownership transfer via \`acceptPositionTokenOwnership()\`. @@ -137,20 +148,7 @@ If passed, this VIP will configure the Institutional Fixed Rate Vault system on - **InstitutionalVaultController** (proxy): ${INSTITUTIONAL_VAULT_CONTROLLER} - **LiquidationAdapter** (proxy): ${LIQUIDATION_ADAPTER} -- **InstitutionPositionToken**: ${INSTITUTION_POSITION_TOKEN} - -#### Permission Summary - -| Timelock | Contract | Permissions | -|---|---|---| -| Normal | InstitutionalVaultController | 19 functions (all operational + setter functions) | -| Fast-track | InstitutionalVaultController | 19 functions (all operational + setter functions) | -| Critical | InstitutionalVaultController | 19 functions (all operational + setter functions) | -| Guardian | InstitutionalVaultController | 6 functions (pause/unpause + open/close/sweep) | -| Normal | LiquidationAdapter | 5 functions (all setter functions) | -| Fast-track | LiquidationAdapter | 5 functions (all setter functions) | -| Critical | LiquidationAdapter | 5 functions (all setter functions) | -| Guardian | LiquidationAdapter | 3 functions (sweep + whitelist management) |`, +- **InstitutionPositionToken**: ${INSTITUTION_POSITION_TOKEN}`, 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", @@ -158,12 +156,7 @@ If passed, this VIP will configure the Institutional Fixed Rate Vault system on return makeProposal( [ - // ────────────────────────────────────────────────────────────────────── - // Phase 1 — ACM permissions via Aggregator - // ────────────────────────────────────────────────────────────────────── - // Load the batch into the aggregator, grant admin role, execute batched - // permissions, then revoke admin role. - + // Phase 1 — Load and execute the ACM permission batch via the aggregator. { target: ACM_AGGREGATOR, signature: "addGrantPermissions((address,string,address)[])", @@ -185,29 +178,11 @@ If passed, this VIP will configure the Institutional Fixed Rate Vault system on params: [DEFAULT_ADMIN_ROLE, ACM_AGGREGATOR], }, - // ────────────────────────────────────────────────────────────────────── - // Phase 2 — Ownership acceptance - // ────────────────────────────────────────────────────────────────────── - // Deploy script called transferOwnership(NORMAL_TIMELOCK) on both contracts. - // acceptOwnership() completes the Ownable2Step transfer. - - { - target: INSTITUTIONAL_VAULT_CONTROLLER, - signature: "acceptOwnership()", - params: [], - }, - { - target: LIQUIDATION_ADAPTER, - signature: "acceptOwnership()", - params: [], - }, - - // ────────────────────────────────────────────────────────────────────── - // Phase 3 — System wiring - // ────────────────────────────────────────────────────────────────────── - // Note: NORMAL_TIMELOCK must have the ACM permission for both calls below, - // which is granted in Phase 1 above. Commands execute atomically in order. + // Phase 2 — Complete Ownable2Step transfers (initiated in deploy script). + { target: INSTITUTIONAL_VAULT_CONTROLLER, signature: "acceptOwnership()", params: [] }, + { target: LIQUIDATION_ADAPTER, signature: "acceptOwnership()", params: [] }, + // Phase 3 — Wire adapter into controller and accept position-token ownership. { target: INSTITUTIONAL_VAULT_CONTROLLER, signature: "setLiquidationAdapter(address)", @@ -219,10 +194,7 @@ If passed, this VIP will configure the Institutional Fixed Rate Vault system on params: [], }, - // ────────────────────────────────────────────────────────────────────── - // Phase 4 — Liquidator / settler whitelist configuration - // ────────────────────────────────────────────────────────────────────── - + // Phase 4 — Whitelist dedicated liquidator/settler addresses on the adapter. ...LIQUIDATOR_WHITELIST.map(account => ({ target: LIQUIDATION_ADAPTER, signature: "setLiquidatorWhitelist(address,bool)", From d642ac8512f1555fe48aefaf83941ebcdb8cd2e1 Mon Sep 17 00:00:00 2001 From: GitGuru7 Date: Tue, 26 May 2026 16:20:52 +0530 Subject: [PATCH 03/10] feat: finalize mainnet VIP and add testnet addendum --- .../vip-664/abi/LiquidationAdapter.json | 219 ++++++++++++++++- simulations/vip-664/bscmainnet.ts | 76 ++++-- simulations/vip-664/bsctestnet-addendum.ts | 226 +++++++++++++++++ vips/vip-664/bscmainnet.ts | 75 +++--- vips/vip-664/bsctestnet-addendum.ts | 230 ++++++++++++++++++ 5 files changed, 777 insertions(+), 49 deletions(-) create mode 100644 simulations/vip-664/bsctestnet-addendum.ts create mode 100644 vips/vip-664/bsctestnet-addendum.ts diff --git a/simulations/vip-664/abi/LiquidationAdapter.json b/simulations/vip-664/abi/LiquidationAdapter.json index c90cf602b..41a094a5b 100644 --- a/simulations/vip-664/abi/LiquidationAdapter.json +++ b/simulations/vip-664/abi/LiquidationAdapter.json @@ -20,6 +20,62 @@ "stateMutability": "view", "type": "function" }, + { + "inputs": [], + "name": "renounceOwnership", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [{ "internalType": "address", "name": "newOwner", "type": "address" }], + "name": "transferOwnership", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "accessControlManager", + "outputs": [{ "internalType": "contract IAccessControlManagerV8", "name": "", "type": "address" }], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [{ "internalType": "address", "name": "accessControlManager_", "type": "address" }], + "name": "setAccessControlManager", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "MANTISSA_ONE", + "outputs": [{ "internalType": "uint256", "name": "", "type": "uint256" }], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "vaultController", + "outputs": [{ "internalType": "address", "name": "", "type": "address" }], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [{ "internalType": "address", "name": "", "type": "address" }], + "name": "isWhitelistedLiquidator", + "outputs": [{ "internalType": "bool", "name": "", "type": "bool" }], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [{ "internalType": "address", "name": "", "type": "address" }], + "name": "isWhitelistedSettler", + "outputs": [{ "internalType": "bool", "name": "", "type": "bool" }], + "stateMutability": "view", + "type": "function" + }, { "inputs": [], "name": "protocolLiquidationShare", @@ -33,5 +89,166 @@ "outputs": [{ "internalType": "uint256", "name": "", "type": "uint256" }], "stateMutability": "view", "type": "function" - } + }, + { + "inputs": [{ "internalType": "address", "name": "", "type": "address" }], + "name": "protocolShareAccrued", + "outputs": [{ "internalType": "uint256", "name": "", "type": "uint256" }], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { "internalType": "address", "name": "vaultController_", "type": "address" }, + { "internalType": "uint256", "name": "protocolLiquidationShare_", "type": "uint256" }, + { "internalType": "uint256", "name": "closeFactor_", "type": "uint256" }, + { "internalType": "address", "name": "acm_", "type": "address" } + ], + "name": "initialize", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { "internalType": "address", "name": "liquidator", "type": "address" }, + { "internalType": "bool", "name": "approved", "type": "bool" } + ], + "name": "setLiquidatorWhitelist", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { "internalType": "address", "name": "settler", "type": "address" }, + { "internalType": "bool", "name": "approved", "type": "bool" } + ], + "name": "setSettlerWhitelist", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [{ "internalType": "uint256", "name": "share", "type": "uint256" }], + "name": "setProtocolLiquidationShare", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [{ "internalType": "uint256", "name": "newCF", "type": "uint256" }], + "name": "setCloseFactor", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [{ "internalType": "address", "name": "collateral", "type": "address" }], + "name": "sweepProtocolShareToReserve", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { "internalType": "address", "name": "vault", "type": "address" }, + { "internalType": "uint256", "name": "repayAmount", "type": "uint256" } + ], + "name": "liquidate", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { "internalType": "address", "name": "vault", "type": "address" }, + { "internalType": "uint256", "name": "repayAmount", "type": "uint256" } + ], + "name": "liquidateOverdueVault", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "anonymous": false, + "inputs": [ + { "indexed": true, "internalType": "address", "name": "liquidator", "type": "address" }, + { "indexed": false, "internalType": "bool", "name": "approved", "type": "bool" } + ], + "name": "LiquidatorWhitelistUpdated", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { "indexed": true, "internalType": "address", "name": "settler", "type": "address" }, + { "indexed": false, "internalType": "bool", "name": "approved", "type": "bool" } + ], + "name": "SettlerWhitelistUpdated", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { "indexed": false, "internalType": "uint256", "name": "oldShare", "type": "uint256" }, + { "indexed": false, "internalType": "uint256", "name": "newShare", "type": "uint256" } + ], + "name": "ProtocolLiquidationShareUpdated", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { "indexed": false, "internalType": "uint256", "name": "oldCloseFactor", "type": "uint256" }, + { "indexed": false, "internalType": "uint256", "name": "newCloseFactor", "type": "uint256" } + ], + "name": "CloseFactorUpdated", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { "indexed": false, "internalType": "uint256", "name": "totalSeized", "type": "uint256" }, + { "indexed": false, "internalType": "uint256", "name": "protocolAmount", "type": "uint256" }, + { "indexed": false, "internalType": "uint256", "name": "callerAmount", "type": "uint256" } + ], + "name": "LiquidationCollateralSplit", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { "indexed": true, "internalType": "address", "name": "collateral", "type": "address" }, + { "indexed": false, "internalType": "uint256", "name": "amount", "type": "uint256" } + ], + "name": "ProtocolShareSweptToReserve", + "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": "previousOwner", "type": "address" }, + { "indexed": true, "internalType": "address", "name": "newOwner", "type": "address" } + ], + "name": "OwnershipTransferStarted", + "type": "event" + }, + { "inputs": [], "name": "NotWhitelistedLiquidator", "type": "error" }, + { "inputs": [], "name": "NotWhitelistedSettler", "type": "error" }, + { "inputs": [], "name": "VaultNotRegistered", "type": "error" }, + { "inputs": [], "name": "InvalidShare", "type": "error" }, + { "inputs": [], "name": "InvalidCloseFactor", "type": "error" }, + { "inputs": [], "name": "InvalidAddress", "type": "error" }, + { "inputs": [], "name": "ZeroRepayAmount", "type": "error" }, + { "inputs": [], "name": "OwnershipCannotBeRenounced", "type": "error" } ] diff --git a/simulations/vip-664/bscmainnet.ts b/simulations/vip-664/bscmainnet.ts index 04cb283e3..540fe17a0 100644 --- a/simulations/vip-664/bscmainnet.ts +++ b/simulations/vip-664/bscmainnet.ts @@ -3,7 +3,7 @@ import { Contract } from "ethers"; import { parseEther } from "ethers/lib/utils"; import { ethers } from "hardhat"; import { NETWORK_ADDRESSES } from "src/networkAddresses"; -import { expectEvents } from "src/utils"; +import { expectEvents, initMainnetUser } from "src/utils"; import { forking, testVip } from "src/vip-framework"; import vip664, { @@ -27,20 +27,22 @@ const { NORMAL_TIMELOCK: NORMAL, FAST_TRACK_TIMELOCK: FAST_TRACK, CRITICAL_TIMELOCK: CRITICAL, - GUARDIAN, + CRITICAL_GUARDIAN, ACCESS_CONTROL_MANAGER, } = NETWORK_ADDRESSES.bscmainnet; -const FORK_BLOCK = 96701105; +const FORK_BLOCK = 100523019; // To make test names readable. const LABEL: Record = { [NORMAL]: "Normal", [FAST_TRACK]: "FastTrack", [CRITICAL]: "Critical", - [GUARDIAN]: "Guardian", + [CRITICAL_GUARDIAN]: "CriticalGuardian", }; +const SUPPORTER = "0xe5e62386933b74ea81bfd73a6a6591598e7f8ced"; + forking(FORK_BLOCK, async () => { let accessControlManager: Contract; let controller: Contract; @@ -102,19 +104,20 @@ forking(FORK_BLOCK, async () => { for (const { target, fn, callers } of PERMISSION_ENTRIES) { for (const account of callers) { it(`${LABEL[account]} should NOT yet have permission: ${fn} on ${target}`, async () => { - expect(await accessControlManager.hasPermission(account, target, fn)).to.be.false; + expect(await accessControlManager.isAllowedToCall(account, fn, { from: target })).to.be.false; }); } } }); testVip("VIP-664 [BNB Chain] Configure Institutional Fixed Rate Vault System", await vip664(), { + supporter: SUPPORTER, callbackAfterExecution: async txResponse => { await expectEvents( txResponse, [ACCESS_CONTROL_MANAGER_ABI], - ["PermissionGranted", "RoleGranted", "RoleRevoked"], - [EXPECTED_PERMISSION_GRANTED_EVENTS, EXPECTED_PERMISSION_GRANTED_EVENTS + 1, 1], + ["RoleGranted", "RoleRevoked"], + [EXPECTED_PERMISSION_GRANTED_EVENTS + 1, 1], ); await expectEvents( txResponse, @@ -130,7 +133,7 @@ forking(FORK_BLOCK, async () => { for (const { target, fn, callers } of PERMISSION_ENTRIES) { for (const account of callers) { it(`${LABEL[account]} should have permission: ${fn} on ${target}`, async () => { - expect(await accessControlManager.hasPermission(account, target, fn)).to.be.true; + expect(await accessControlManager.isAllowedToCall(account, fn, { from: target })).to.be.true; }); } } @@ -159,24 +162,65 @@ forking(FORK_BLOCK, async () => { }); }); - // Dedicated operator addresses whitelisted; Guardian explicitly is not. describe("Post-VIP: liquidator/settler whitelists", () => { for (const account of LIQUIDATOR_WHITELIST) { it(`${account} should be a whitelisted liquidator`, async () => { - expect(await liquidationAdapter.liquidatorWhitelist(account)).to.be.true; + expect(await liquidationAdapter.isWhitelistedLiquidator(account)).to.be.true; }); } - it("GUARDIAN should NOT be a whitelisted liquidator", async () => { - expect(await liquidationAdapter.liquidatorWhitelist(GUARDIAN)).to.be.false; - }); for (const account of SETTLER_WHITELIST) { it(`${account} should be a whitelisted settler`, async () => { - expect(await liquidationAdapter.settlerWhitelist(account)).to.be.true; + expect(await liquidationAdapter.isWhitelistedSettler(account)).to.be.true; }); } - it("GUARDIAN should NOT be a whitelisted settler", async () => { - expect(await liquidationAdapter.settlerWhitelist(GUARDIAN)).to.be.false; + }); + + describe("Post-VIP: createVault ACM gating", () => { + const USDT = "0x55d398326f99059fF775485246999027B3197955"; + const USDC = "0x8AC76a51cc950d9822D68b83fE1Ad97B32Cd580d"; + + const ONE_DAY = 24 * 60 * 60; + const vaultConfig = { + supplyAsset: USDT, + fixedAPY: parseEther("0.05"), // 5% APY + reserveFactor: parseEther("0.1"), // 10% + minBorrowCap: parseEther("1000"), // 1,000 USDT + maxBorrowCap: parseEther("10000000"), // 10M USDT + minSupplierDeposit: parseEther("100"), // 100 USDT + openDuration: 7 * ONE_DAY, + lockDuration: 30 * ONE_DAY, + settlementWindow: ONE_DAY, + }; + const instConfig = { + collateralAsset: USDC, + idealCollateralAmount: parseEther("100000"), + marginRate: parseEther("1.5"), + institutionOperator: CRITICAL_GUARDIAN, + positionTokenId: 1, + }; + const riskConfig = { + liquidationThreshold: parseEther("0.85"), + liquidationIncentive: parseEther("1.08"), + latePenaltyRate: parseEther("1.15"), + }; + + it("random address should revert with Unauthorized when trying to create vault", async () => { + const stranger = await initMainnetUser( + "0x000000000000000000000000000000000000dEaD", + ethers.utils.parseEther("1"), + ); + await expect( + controller.connect(stranger).createVault(vaultConfig, instConfig, riskConfig, "Test", "TEST"), + ).to.be.revertedWithCustomError(controller, "Unauthorized"); + }); + + it("CriticalGuardian should be able to create vault", async () => { + const criticalGuardian = await initMainnetUser(CRITICAL_GUARDIAN, ethers.utils.parseEther("1")); + const call = controller + .connect(criticalGuardian) + .createVault(vaultConfig, instConfig, riskConfig, "Test", "TEST"); + await expect(call).to.not.be.revertedWithCustomError(controller, "Unauthorized"); }); }); }); diff --git a/simulations/vip-664/bsctestnet-addendum.ts b/simulations/vip-664/bsctestnet-addendum.ts new file mode 100644 index 000000000..d13c6b770 --- /dev/null +++ b/simulations/vip-664/bsctestnet-addendum.ts @@ -0,0 +1,226 @@ +import { expect } from "chai"; +import { Contract } from "ethers"; +import { parseEther } from "ethers/lib/utils"; +import { ethers } from "hardhat"; +import { NETWORK_ADDRESSES } from "src/networkAddresses"; +import { expectEvents, initMainnetUser } from "src/utils"; +import { forking, testVip } from "src/vip-framework"; + +import vip664TestnetAddendum, { + ACM_AGGREGATOR, + DEFAULT_ADMIN_ROLE, + EXPECTED_PERMISSION_GRANTED_EVENTS, + INSTITUTIONAL_VAULT_CONTROLLER, + INSTITUTION_POSITION_TOKEN, + LIQUIDATION_ADAPTER, + LIQUIDATOR_WHITELIST, + PERMISSION_ENTRIES, + SETTLER_WHITELIST, +} from "../../vips/vip-664/bsctestnet-addendum"; +import ACM_AGGREGATOR_ABI from "./abi/ACMAggregator.json"; +import ACCESS_CONTROL_MANAGER_ABI from "./abi/AccessControlManager.json"; +import INSTITUTION_POSITION_TOKEN_ABI from "./abi/InstitutionPositionToken.json"; +import INSTITUTIONAL_VAULT_CONTROLLER_ABI from "./abi/InstitutionalVaultController.json"; +import LIQUIDATION_ADAPTER_ABI from "./abi/LiquidationAdapter.json"; + +const { + NORMAL_TIMELOCK: NORMAL, + FAST_TRACK_TIMELOCK: FAST_TRACK, + CRITICAL_TIMELOCK: CRITICAL, + GUARDIAN, + ACCESS_CONTROL_MANAGER, +} = NETWORK_ADDRESSES.bsctestnet; + +const FORK_BLOCK = 109652392; + +// To make test names readable. +const LABEL: Record = { + [NORMAL]: "Normal", + [FAST_TRACK]: "FastTrack", + [CRITICAL]: "Critical", + [GUARDIAN]: "Guardian", +}; + +forking(FORK_BLOCK, async () => { + let accessControlManager: Contract; + let controller: Contract; + let liquidationAdapter: Contract; + let positionToken: Contract; + + before(async () => { + accessControlManager = new ethers.Contract(ACCESS_CONTROL_MANAGER, ACCESS_CONTROL_MANAGER_ABI, ethers.provider); + controller = new ethers.Contract( + INSTITUTIONAL_VAULT_CONTROLLER, + INSTITUTIONAL_VAULT_CONTROLLER_ABI, + ethers.provider, + ); + liquidationAdapter = new ethers.Contract(LIQUIDATION_ADAPTER, LIQUIDATION_ADAPTER_ABI, ethers.provider); + positionToken = new ethers.Contract(INSTITUTION_POSITION_TOKEN, INSTITUTION_POSITION_TOKEN_ABI, ethers.provider); + }); + + // Contracts redeployed and deploy-script state is in place. + describe("Pre-VIP: verify redeployments", () => { + it("InstitutionalVaultController proxy should be deployed", async () => { + expect(await ethers.provider.getCode(INSTITUTIONAL_VAULT_CONTROLLER)).to.not.equal("0x"); + }); + + it("LiquidationAdapter proxy should be deployed", async () => { + expect(await ethers.provider.getCode(LIQUIDATION_ADAPTER)).to.not.equal("0x"); + }); + + it("InstitutionPositionToken should be deployed", async () => { + expect(await ethers.provider.getCode(INSTITUTION_POSITION_TOKEN)).to.not.equal("0x"); + }); + + it("InstitutionPositionToken pendingOwner should be the controller proxy", async () => { + expect(await positionToken.pendingOwner()).to.equal(INSTITUTIONAL_VAULT_CONTROLLER); + }); + + it("controller pendingOwner should be Normal timelock", async () => { + expect(await controller.pendingOwner()).to.equal(NORMAL); + }); + + it("liquidationAdapter pendingOwner should be Normal timelock", async () => { + expect(await liquidationAdapter.pendingOwner()).to.equal(NORMAL); + }); + + it("liquidationAdapter protocolLiquidationShare should be 0.5e18", async () => { + expect(await liquidationAdapter.protocolLiquidationShare()).to.equal(parseEther("0.5")); + }); + + it("liquidationAdapter closeFactor should be 0.5e18", async () => { + expect(await liquidationAdapter.closeFactor()).to.equal(parseEther("0.5")); + }); + + it("controller liquidationAdapter should be address(0) before VIP", async () => { + expect(await controller.liquidationAdapter()).to.equal(ethers.constants.AddressZero); + }); + }); + + // None of the planned grants exist yet against the redeployed contracts. + describe("Pre-VIP: ACM permissions not yet granted", () => { + for (const { target, fn, callers } of PERMISSION_ENTRIES) { + for (const account of callers) { + it(`${LABEL[account] ?? account} should NOT yet have permission: ${fn} on ${target}`, async () => { + expect(await accessControlManager.hasPermission(account, target, fn)).to.be.false; + }); + } + } + }); + + testVip( + "VIP-664 Addendum [BNB Chain Testnet] Configure redeployed Institutional Fixed Rate Vault System", + await vip664TestnetAddendum(), + { + callbackAfterExecution: async txResponse => { + await expectEvents( + txResponse, + [ACCESS_CONTROL_MANAGER_ABI], + ["PermissionGranted", "RoleGranted", "RoleRevoked"], + [EXPECTED_PERMISSION_GRANTED_EVENTS, EXPECTED_PERMISSION_GRANTED_EVENTS + 1, 1], + ); + await expectEvents( + txResponse, + [ACM_AGGREGATOR_ABI], + ["GrantPermissionsAdded", "GrantPermissionsExecuted"], + [1, 1], + ); + }, + }, + ); + + // Every planned grant is now active. + describe("Post-VIP: ACM permissions granted", () => { + for (const { target, fn, callers } of PERMISSION_ENTRIES) { + for (const account of callers) { + it(`${LABEL[account] ?? account} should have permission: ${fn} on ${target}`, async () => { + expect(await accessControlManager.hasPermission(account, target, fn)).to.be.true; + }); + } + } + }); + + // Ownership accepted, adapter wired, admin role revoked. + describe("Post-VIP: ownership and wiring", () => { + it("controller owner should be Normal timelock", async () => { + expect(await controller.owner()).to.equal(NORMAL); + }); + + it("liquidationAdapter owner should be Normal timelock", async () => { + expect(await liquidationAdapter.owner()).to.equal(NORMAL); + }); + + it("InstitutionPositionToken owner should be the controller proxy", async () => { + expect(await positionToken.owner()).to.equal(INSTITUTIONAL_VAULT_CONTROLLER); + }); + + it("controller liquidationAdapter should be set to the adapter proxy", async () => { + expect(await controller.liquidationAdapter()).to.equal(LIQUIDATION_ADAPTER); + }); + + it("DEFAULT_ADMIN_ROLE should be revoked from ACM Aggregator", async () => { + expect(await accessControlManager.hasRole(DEFAULT_ADMIN_ROLE, ACM_AGGREGATOR)).to.be.false; + }); + }); + + // Dedicated operator addresses whitelisted. + describe("Post-VIP: liquidator/settler whitelists", () => { + for (const account of LIQUIDATOR_WHITELIST) { + it(`${LABEL[account] ?? account} should be a whitelisted liquidator`, async () => { + expect(await liquidationAdapter.isWhitelistedLiquidator(account)).to.be.true; + }); + } + + for (const account of SETTLER_WHITELIST) { + it(`${LABEL[account] ?? account} should be a whitelisted settler`, async () => { + expect(await liquidationAdapter.isWhitelistedSettler(account)).to.be.true; + }); + } + }); + + describe("Post-VIP: createVault ACM gating", () => { + const USDT = "0xA11c8D9DC9b66E209Ef60F0C8D969D3CD988782c"; + const USDC = "0x16227D60f7a0e586C66B005219dfc887D13C9531"; + + const ONE_DAY = 24 * 60 * 60; + const vaultConfig = { + supplyAsset: USDT, + fixedAPY: parseEther("0.05"), // 5% APY + reserveFactor: parseEther("0.1"), // 10% + minBorrowCap: parseEther("1000"), // 1,000 USDT + maxBorrowCap: parseEther("10000000"), // 10M USDT + minSupplierDeposit: parseEther("100"), // 100 USDT + openDuration: 7 * ONE_DAY, // 7 days + lockDuration: 30 * ONE_DAY, // 30 days + settlementWindow: ONE_DAY, // 1 day + }; + const instConfig = { + collateralAsset: USDC, + idealCollateralAmount: parseEther("100000"), // 100k USDC + marginRate: parseEther("1.5"), + institutionOperator: GUARDIAN, + positionTokenId: 1, + }; + const riskConfig = { + liquidationThreshold: parseEther("0.85"), // 85% + liquidationIncentive: parseEther("1.08"), // 8% incentive + latePenaltyRate: parseEther("1.15"), // 15% late penalty + }; + + it("unauthorized caller should revert when trying to create vault", async () => { + const stranger = await initMainnetUser( + "0x000000000000000000000000000000000000dEaD", + ethers.utils.parseEther("1"), + ); + await expect( + controller.connect(stranger).createVault(vaultConfig, instConfig, riskConfig, "Test", "TEST"), + ).to.be.revertedWithCustomError(controller, "Unauthorized"); + }); + + it("Guardian should be able to create vault", async () => { + const guardian = await initMainnetUser(GUARDIAN, ethers.utils.parseEther("1")); + const call = controller.connect(guardian).createVault(vaultConfig, instConfig, riskConfig, "Test", "TEST"); + await expect(call).to.not.be.revertedWithCustomError(controller, "Unauthorized"); + }); + }); +}); diff --git a/vips/vip-664/bscmainnet.ts b/vips/vip-664/bscmainnet.ts index 4fcb426d4..0dc88c35b 100644 --- a/vips/vip-664/bscmainnet.ts +++ b/vips/vip-664/bscmainnet.ts @@ -6,7 +6,7 @@ const { NORMAL_TIMELOCK: NORMAL, FAST_TRACK_TIMELOCK: FAST_TRACK, CRITICAL_TIMELOCK: CRITICAL, - GUARDIAN, + CRITICAL_GUARDIAN, ACCESS_CONTROL_MANAGER, } = NETWORK_ADDRESSES.bscmainnet; @@ -16,18 +16,19 @@ export interface PermissionEntry { callers: string[]; } -// Deployed addresses (TODO: replace with mainnet addresses before proposing) -export const INSTITUTIONAL_VAULT_CONTROLLER = "0x0000000000000000000000000000000000000000"; -export const LIQUIDATION_ADAPTER = "0x0000000000000000000000000000000000000000"; -export const INSTITUTION_POSITION_TOKEN = "0x0000000000000000000000000000000000000000"; +// Deployed addresses +export const INSTITUTIONAL_VAULT_CONTROLLER = "0x6D9e91cB766259af42619c14c994E694E57e6E85"; +export const LIQUIDATION_ADAPTER = "0x17A6222fB8b4b6D852cA54f5bc376a6A2c6224Bd"; +export const INSTITUTION_POSITION_TOKEN = "0x3Ed56f6937fc8549f9325405d1e8E650739647Fa"; // ACM aggregator (mainnet) export const ACM_AGGREGATOR = "0x8b443Ea6726E56DF4C4F62f80F0556bB9B2a7c64"; export const DEFAULT_ADMIN_ROLE = "0x0000000000000000000000000000000000000000000000000000000000000000"; -export const ACM_AGGREGATOR_INDEX = 1; -export const LIQUIDATOR_WHITELIST: string[] = []; // TBD: populate before proposing. -export const SETTLER_WHITELIST: string[] = []; // TBD: populate before proposing. +export const ACM_AGGREGATOR_INDEX = 2; + +export const LIQUIDATOR_WHITELIST: string[] = [CRITICAL_GUARDIAN]; +export const SETTLER_WHITELIST: string[] = [CRITICAL_GUARDIAN]; export const PERMISSION_ENTRIES: PermissionEntry[] = [ // InstitutionalVaultController @@ -35,37 +36,37 @@ export const PERMISSION_ENTRIES: PermissionEntry[] = [ { target: INSTITUTIONAL_VAULT_CONTROLLER, fn: "createVault(VaultConfig,InstitutionalConfig,RiskConfig,string,string)", - callers: [NORMAL, FAST_TRACK, CRITICAL], + callers: [NORMAL, FAST_TRACK, CRITICAL, CRITICAL_GUARDIAN], }, { target: INSTITUTIONAL_VAULT_CONTROLLER, fn: "openVault(address)", - callers: [NORMAL, FAST_TRACK, CRITICAL, GUARDIAN], + callers: [NORMAL, FAST_TRACK, CRITICAL, CRITICAL_GUARDIAN], }, { target: INSTITUTIONAL_VAULT_CONTROLLER, fn: "partialPauseVault(address)", - callers: [NORMAL, FAST_TRACK, CRITICAL, GUARDIAN], + callers: [NORMAL, FAST_TRACK, CRITICAL, CRITICAL_GUARDIAN], }, { target: INSTITUTIONAL_VAULT_CONTROLLER, fn: "completePauseVault(address)", - callers: [NORMAL, FAST_TRACK, CRITICAL, GUARDIAN], + callers: [NORMAL, FAST_TRACK, CRITICAL, CRITICAL_GUARDIAN], }, { target: INSTITUTIONAL_VAULT_CONTROLLER, fn: "unpauseVault(address)", - callers: [NORMAL, FAST_TRACK, CRITICAL, GUARDIAN], + callers: [NORMAL, FAST_TRACK, CRITICAL, CRITICAL_GUARDIAN], }, { target: INSTITUTIONAL_VAULT_CONTROLLER, fn: "closeVault(address)", - callers: [NORMAL, FAST_TRACK, CRITICAL, GUARDIAN], + callers: [NORMAL, FAST_TRACK, CRITICAL, CRITICAL_GUARDIAN], }, { target: INSTITUTIONAL_VAULT_CONTROLLER, fn: "sweep(address,address)", - callers: [NORMAL, FAST_TRACK, CRITICAL, GUARDIAN], + callers: [NORMAL, FAST_TRACK, CRITICAL, CRITICAL_GUARDIAN], }, { target: INSTITUTIONAL_VAULT_CONTROLLER, @@ -75,7 +76,7 @@ export const PERMISSION_ENTRIES: PermissionEntry[] = [ { target: INSTITUTIONAL_VAULT_CONTROLLER, fn: "revokePositionTransfer(address)", - callers: [NORMAL, FAST_TRACK, CRITICAL, GUARDIAN], + callers: [NORMAL, FAST_TRACK, CRITICAL, CRITICAL_GUARDIAN], }, { target: INSTITUTIONAL_VAULT_CONTROLLER, @@ -97,29 +98,38 @@ export const PERMISSION_ENTRIES: PermissionEntry[] = [ fn: "setVaultImplementation(address)", callers: [NORMAL, FAST_TRACK, CRITICAL], }, - { target: INSTITUTIONAL_VAULT_CONTROLLER, fn: "setLiquidationAdapter(address)", callers: [NORMAL, GUARDIAN] }, - { target: INSTITUTIONAL_VAULT_CONTROLLER, fn: "setOracle(address)", callers: [NORMAL, GUARDIAN] }, - { target: INSTITUTIONAL_VAULT_CONTROLLER, fn: "setProtocolShareReserve(address)", callers: [NORMAL, GUARDIAN] }, - { target: INSTITUTIONAL_VAULT_CONTROLLER, fn: "setComptroller(address)", callers: [NORMAL, GUARDIAN] }, - { target: INSTITUTIONAL_VAULT_CONTROLLER, fn: "setTreasury(address)", callers: [NORMAL, GUARDIAN] }, + { + target: INSTITUTIONAL_VAULT_CONTROLLER, + fn: "setLiquidationAdapter(address)", + callers: [NORMAL, CRITICAL_GUARDIAN], + }, + { target: INSTITUTIONAL_VAULT_CONTROLLER, fn: "setOracle(address)", callers: [NORMAL, CRITICAL_GUARDIAN] }, + { + target: INSTITUTIONAL_VAULT_CONTROLLER, + fn: "setProtocolShareReserve(address)", + callers: [NORMAL, CRITICAL_GUARDIAN], + }, + { target: INSTITUTIONAL_VAULT_CONTROLLER, fn: "setComptroller(address)", callers: [NORMAL, CRITICAL_GUARDIAN] }, + { target: INSTITUTIONAL_VAULT_CONTROLLER, fn: "setTreasury(address)", callers: [NORMAL, CRITICAL_GUARDIAN] }, // LiquidationAdapter { target: LIQUIDATION_ADAPTER, fn: "setLiquidatorWhitelist(address,bool)", - callers: [NORMAL, FAST_TRACK, CRITICAL, GUARDIAN], + callers: [NORMAL, FAST_TRACK, CRITICAL, CRITICAL_GUARDIAN], }, { target: LIQUIDATION_ADAPTER, fn: "setSettlerWhitelist(address,bool)", - callers: [NORMAL, FAST_TRACK, CRITICAL, GUARDIAN], + callers: [NORMAL, FAST_TRACK, CRITICAL, CRITICAL_GUARDIAN], }, { target: LIQUIDATION_ADAPTER, fn: "setProtocolLiquidationShare(uint256)", callers: [NORMAL, FAST_TRACK, CRITICAL] }, - { target: LIQUIDATION_ADAPTER, fn: "setCloseFactor(uint256)", callers: [NORMAL, FAST_TRACK, CRITICAL] }, + // NORMAL already holds setCloseFactor globally on mainnet. + { target: LIQUIDATION_ADAPTER, fn: "setCloseFactor(uint256)", callers: [FAST_TRACK, CRITICAL] }, { target: LIQUIDATION_ADAPTER, fn: "sweepProtocolShareToReserve(address)", - callers: [NORMAL, FAST_TRACK, CRITICAL, GUARDIAN], + callers: [NORMAL, FAST_TRACK, CRITICAL, CRITICAL_GUARDIAN], }, ]; @@ -138,17 +148,18 @@ export const vip664 = () => { If passed, this VIP will configure the Institutional Fixed Rate Vault system on BNB Chain: -1. Grant ACM permissions (${EXPECTED_PERMISSION_GRANTED_EVENTS} total) via \`ACMCommandsAggregator\` to the appropriate set of timelocks (Normal, Fast-track, Critical) and the Guardian for each access-controlled function on \`InstitutionalVaultController\` and \`LiquidationAdapter\`. +1. Grant ACM permissions (${EXPECTED_PERMISSION_GRANTED_EVENTS} total) via \`ACMCommandsAggregator\` to the appropriate set of timelocks (Normal, Fast-track, Critical) and the Critical Guardian (which also holds the \`createVault\` permission) for each access-controlled function on \`InstitutionalVaultController\` and \`LiquidationAdapter\`. 2. Accept ownership of \`InstitutionalVaultController\` and \`LiquidationAdapter\` (two-step Ownable2Step transfer initiated in deploy script). 3. Set the \`LiquidationAdapter\` on the controller via \`setLiquidationAdapter()\`. 4. Complete the two-step position token ownership transfer via \`acceptPositionTokenOwnership()\`. -5. Whitelist a dedicated set of liquidator and settler addresses on the \`LiquidationAdapter\`. +5. Whitelist the Critical Guardian as a liquidator and settler on the \`LiquidationAdapter\`. #### Deployed Contracts - **InstitutionalVaultController** (proxy): ${INSTITUTIONAL_VAULT_CONTROLLER} - **LiquidationAdapter** (proxy): ${LIQUIDATION_ADAPTER} -- **InstitutionPositionToken**: ${INSTITUTION_POSITION_TOKEN}`, +- **InstitutionPositionToken**: ${INSTITUTION_POSITION_TOKEN} +- **Critical Guardian**: ${CRITICAL_GUARDIAN}`, 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", @@ -156,7 +167,7 @@ If passed, this VIP will configure the Institutional Fixed Rate Vault system on return makeProposal( [ - // Phase 1 — Load and execute the ACM permission batch via the aggregator. + // Step 1 — Load and execute the ACM permission batch via the aggregator. { target: ACM_AGGREGATOR, signature: "addGrantPermissions((address,string,address)[])", @@ -178,11 +189,11 @@ If passed, this VIP will configure the Institutional Fixed Rate Vault system on params: [DEFAULT_ADMIN_ROLE, ACM_AGGREGATOR], }, - // Phase 2 — Complete Ownable2Step transfers (initiated in deploy script). + // Step 2 — Complete Ownable2Step transfers. { target: INSTITUTIONAL_VAULT_CONTROLLER, signature: "acceptOwnership()", params: [] }, { target: LIQUIDATION_ADAPTER, signature: "acceptOwnership()", params: [] }, - // Phase 3 — Wire adapter into controller and accept position-token ownership. + // Step 3 — Wire adapter into controller and accept position-token ownership. { target: INSTITUTIONAL_VAULT_CONTROLLER, signature: "setLiquidationAdapter(address)", @@ -194,7 +205,7 @@ If passed, this VIP will configure the Institutional Fixed Rate Vault system on params: [], }, - // Phase 4 — Whitelist dedicated liquidator/settler addresses on the adapter. + // Step 4 — Whitelist dedicated liquidator/settler addresses on the adapter. ...LIQUIDATOR_WHITELIST.map(account => ({ target: LIQUIDATION_ADAPTER, signature: "setLiquidatorWhitelist(address,bool)", diff --git a/vips/vip-664/bsctestnet-addendum.ts b/vips/vip-664/bsctestnet-addendum.ts new file mode 100644 index 000000000..917f5156a --- /dev/null +++ b/vips/vip-664/bsctestnet-addendum.ts @@ -0,0 +1,230 @@ +import { NETWORK_ADDRESSES } from "src/networkAddresses"; +import { ProposalType } from "src/types"; +import { makeProposal } from "src/utils"; + +const { + NORMAL_TIMELOCK: NORMAL, + FAST_TRACK_TIMELOCK: FAST_TRACK, + CRITICAL_TIMELOCK: CRITICAL, + GUARDIAN, + ACCESS_CONTROL_MANAGER, +} = NETWORK_ADDRESSES.bsctestnet; + +export interface PermissionEntry { + target: string; + fn: string; + callers: string[]; +} + +// Redeployed addresses (TODO: replace once new testnet deployment is complete) +export const INSTITUTIONAL_VAULT_CONTROLLER = "0xf77dED2A00F94e33C392126238360D4642c16Ba2"; +export const LIQUIDATION_ADAPTER = "0x4b302b56315Ca16A0A4565108e62404496916491"; +export const INSTITUTION_POSITION_TOKEN = "0x71dA473257a96e975558C8edD8491AD0880EFCe5"; + +// ACM aggregator (existing testnet deployment). +// Index is incremented because index 1 was consumed by the original VIP-664 pre-load. +export const ACM_AGGREGATOR = "0xB59523628D92f914ec6624Be4281397E8aFD71EF"; +export const DEFAULT_ADMIN_ROLE = "0x0000000000000000000000000000000000000000000000000000000000000000"; +export const ACM_AGGREGATOR_INDEX = 2; + +// Liquidator/settler addresses whitelisted on the `LiquidationAdapter`. +// Guardian is whitelisted on testnet for operational convenience. +export const LIQUIDATOR_WHITELIST: string[] = [GUARDIAN]; +export const SETTLER_WHITELIST: string[] = [GUARDIAN]; + +export const PERMISSION_ENTRIES: PermissionEntry[] = [ + // InstitutionalVaultController + { target: INSTITUTIONAL_VAULT_CONTROLLER, fn: "acceptPositionTokenOwnership()", callers: [NORMAL] }, + { + target: INSTITUTIONAL_VAULT_CONTROLLER, + fn: "createVault(VaultConfig,InstitutionalConfig,RiskConfig,string,string)", + callers: [NORMAL, FAST_TRACK, CRITICAL, GUARDIAN], + }, + { + target: INSTITUTIONAL_VAULT_CONTROLLER, + fn: "openVault(address)", + callers: [NORMAL, FAST_TRACK, CRITICAL, GUARDIAN], + }, + { + target: INSTITUTIONAL_VAULT_CONTROLLER, + fn: "partialPauseVault(address)", + callers: [NORMAL, FAST_TRACK, CRITICAL, GUARDIAN], + }, + { + target: INSTITUTIONAL_VAULT_CONTROLLER, + fn: "completePauseVault(address)", + callers: [NORMAL, FAST_TRACK, CRITICAL, GUARDIAN], + }, + { + target: INSTITUTIONAL_VAULT_CONTROLLER, + fn: "unpauseVault(address)", + callers: [NORMAL, FAST_TRACK, CRITICAL, GUARDIAN], + }, + { + target: INSTITUTIONAL_VAULT_CONTROLLER, + fn: "closeVault(address)", + callers: [NORMAL, FAST_TRACK, CRITICAL, GUARDIAN], + }, + { + target: INSTITUTIONAL_VAULT_CONTROLLER, + fn: "sweep(address,address)", + callers: [NORMAL, FAST_TRACK, CRITICAL, GUARDIAN], + }, + { + target: INSTITUTIONAL_VAULT_CONTROLLER, + fn: "approvePositionTransfer(address)", + callers: [NORMAL, FAST_TRACK, CRITICAL, GUARDIAN], + }, + { + target: INSTITUTIONAL_VAULT_CONTROLLER, + fn: "revokePositionTransfer(address)", + callers: [NORMAL, FAST_TRACK, CRITICAL, GUARDIAN], + }, + { + target: INSTITUTIONAL_VAULT_CONTROLLER, + fn: "setLiquidationThreshold(address,uint256)", + callers: [NORMAL, FAST_TRACK, CRITICAL, GUARDIAN], + }, + { + target: INSTITUTIONAL_VAULT_CONTROLLER, + fn: "setLiquidationIncentive(address,uint256)", + callers: [NORMAL, FAST_TRACK, CRITICAL, GUARDIAN], + }, + { + target: INSTITUTIONAL_VAULT_CONTROLLER, + fn: "setLatePenaltyRate(address,uint256)", + callers: [NORMAL, FAST_TRACK, CRITICAL, GUARDIAN], + }, + { + target: INSTITUTIONAL_VAULT_CONTROLLER, + fn: "setVaultImplementation(address)", + callers: [NORMAL, FAST_TRACK, CRITICAL], + }, + { target: INSTITUTIONAL_VAULT_CONTROLLER, fn: "setLiquidationAdapter(address)", callers: [NORMAL, GUARDIAN] }, + { target: INSTITUTIONAL_VAULT_CONTROLLER, fn: "setOracle(address)", callers: [NORMAL, GUARDIAN] }, + { target: INSTITUTIONAL_VAULT_CONTROLLER, fn: "setProtocolShareReserve(address)", callers: [NORMAL, GUARDIAN] }, + { target: INSTITUTIONAL_VAULT_CONTROLLER, fn: "setComptroller(address)", callers: [NORMAL, GUARDIAN] }, + { target: INSTITUTIONAL_VAULT_CONTROLLER, fn: "setTreasury(address)", callers: [NORMAL, GUARDIAN] }, + + // LiquidationAdapter + { + target: LIQUIDATION_ADAPTER, + fn: "setLiquidatorWhitelist(address,bool)", + callers: [NORMAL, FAST_TRACK, CRITICAL, GUARDIAN], + }, + { + target: LIQUIDATION_ADAPTER, + fn: "setSettlerWhitelist(address,bool)", + callers: [NORMAL, FAST_TRACK, CRITICAL, GUARDIAN], + }, + { + target: LIQUIDATION_ADAPTER, + fn: "setProtocolLiquidationShare(uint256)", + callers: [NORMAL, FAST_TRACK, CRITICAL, GUARDIAN], + }, + { target: LIQUIDATION_ADAPTER, fn: "setCloseFactor(uint256)", callers: [NORMAL, FAST_TRACK, CRITICAL, GUARDIAN] }, + { + target: LIQUIDATION_ADAPTER, + fn: "sweepProtocolShareToReserve(address)", + callers: [NORMAL, FAST_TRACK, CRITICAL, GUARDIAN], + }, +]; + +export const buildPermissions = (table: PermissionEntry[]): [string, string, string][] => + table.flatMap(({ target, fn, callers }) => callers.map(c => [target, fn, c] as [string, string, string])); + +export const PERMISSIONS: [string, string, string][] = buildPermissions(PERMISSION_ENTRIES); + +export const EXPECTED_PERMISSION_GRANTED_EVENTS = PERMISSIONS.length; + +export const vip664TestnetAddendum = () => { + const meta = { + version: "v1", + title: "VIP-664 Addendum [BNB Chain Testnet] Configure redeployed Institutional Fixed Rate Vault System", + description: `#### Summary + +The Institutional Fixed Rate Vault contracts were redeployed on BNB Chain Testnet. This addendum +re-runs the configuration against the new contract addresses, following the same on-chain flow used +in the [VIP-664 BNB mainnet proposal](../bscmainnet.ts) where permissions are loaded into the ACM +aggregator inline via the VIP itself rather than via a pre-load script. + +If passed, this VIP will: + +1. Load and execute the ACM permission batch (${EXPECTED_PERMISSION_GRANTED_EVENTS} total grants) via \`ACMCommandsAggregator\`: + - \`addGrantPermissions\` to load the batch onto the aggregator + - \`grantRole(DEFAULT_ADMIN_ROLE, aggregator)\` so the aggregator can apply grants + - \`executeGrantPermissions\` to apply all permissions atomically + - \`revokeRole(DEFAULT_ADMIN_ROLE, aggregator)\` to remove the elevated role +2. Accept ownership of \`InstitutionalVaultController\` and \`LiquidationAdapter\` (two-step Ownable2Step transfer initiated in deploy script). +3. Set the \`LiquidationAdapter\` on the controller via \`setLiquidationAdapter()\`. +4. Complete the two-step position token ownership transfer via \`acceptPositionTokenOwnership()\`. +5. Whitelist the Guardian as a liquidator and settler on the \`LiquidationAdapter\`. + +#### Deployed Contracts (redeployed) + +- **InstitutionalVaultController** (proxy): ${INSTITUTIONAL_VAULT_CONTROLLER} +- **LiquidationAdapter** (proxy): ${LIQUIDATION_ADAPTER} +- **InstitutionPositionToken**: ${INSTITUTION_POSITION_TOKEN}`, + 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( + [ + // Step 1 — Load and execute the ACM permission batch via the aggregator. + { + target: ACM_AGGREGATOR, + signature: "addGrantPermissions((address,string,address)[])", + params: [PERMISSIONS], + }, + { + target: ACCESS_CONTROL_MANAGER, + signature: "grantRole(bytes32,address)", + params: [DEFAULT_ADMIN_ROLE, ACM_AGGREGATOR], + }, + { + target: ACM_AGGREGATOR, + signature: "executeGrantPermissions(uint256)", + params: [ACM_AGGREGATOR_INDEX], + }, + { + target: ACCESS_CONTROL_MANAGER, + signature: "revokeRole(bytes32,address)", + params: [DEFAULT_ADMIN_ROLE, ACM_AGGREGATOR], + }, + + // Step 2 — Complete Ownable2Step transfers (initiated in deploy script). + { target: INSTITUTIONAL_VAULT_CONTROLLER, signature: "acceptOwnership()", params: [] }, + { target: LIQUIDATION_ADAPTER, signature: "acceptOwnership()", params: [] }, + + // Step 3 — Wire adapter into controller and accept position-token ownership. + { + target: INSTITUTIONAL_VAULT_CONTROLLER, + signature: "setLiquidationAdapter(address)", + params: [LIQUIDATION_ADAPTER], + }, + { + target: INSTITUTIONAL_VAULT_CONTROLLER, + signature: "acceptPositionTokenOwnership()", + params: [], + }, + + // Step 4 — Whitelist dedicated liquidator/settler addresses on the adapter. + ...LIQUIDATOR_WHITELIST.map(account => ({ + target: LIQUIDATION_ADAPTER, + signature: "setLiquidatorWhitelist(address,bool)", + params: [account, true], + })), + ...SETTLER_WHITELIST.map(account => ({ + target: LIQUIDATION_ADAPTER, + signature: "setSettlerWhitelist(address,bool)", + params: [account, true], + })), + ], + meta, + ProposalType.REGULAR, + ); +}; + +export default vip664TestnetAddendum; From d9ff1ba8a6a60f08f25cd9ba8fcfb258486218ad Mon Sep 17 00:00:00 2001 From: GitGuru7 Date: Tue, 26 May 2026 17:47:00 +0530 Subject: [PATCH 04/10] chore: drop addGrantPermissions script --- vips/vip-664/addGrantPermissions.ts | 72 ----------------------------- vips/vip-664/bsctestnet.ts | 2 +- 2 files changed, 1 insertion(+), 73 deletions(-) delete mode 100644 vips/vip-664/addGrantPermissions.ts diff --git a/vips/vip-664/addGrantPermissions.ts b/vips/vip-664/addGrantPermissions.ts deleted file mode 100644 index 0d24b68c1..000000000 --- a/vips/vip-664/addGrantPermissions.ts +++ /dev/null @@ -1,72 +0,0 @@ -import hre, { ethers } from "hardhat"; - -import * as bscmainnet from "./bscmainnet"; -import * as bsctestnet from "./bsctestnet"; - -const NETWORK_CONFIG: Record = { - bsctestnet: { ACM_AGGREGATOR: bsctestnet.ACM_AGGREGATOR, PERMISSIONS: bsctestnet.PERMISSIONS }, - bscmainnet: { ACM_AGGREGATOR: bscmainnet.ACM_AGGREGATOR, PERMISSIONS: bscmainnet.PERMISSIONS }, -}; - -const ACM_AGGREGATOR_ABI = [ - { - inputs: [ - { - components: [ - { internalType: "address", name: "contractAddress", type: "address" }, - { internalType: "string", name: "functionSig", type: "string" }, - { internalType: "address", name: "account", type: "address" }, - ], - internalType: "struct ACMCommandsAggregator.Permission[]", - name: "_PERMISSIONS", - type: "tuple[]", - }, - ], - name: "addGrantPermissions", - outputs: [], - stateMutability: "nonpayable", - type: "function", - }, - { - anonymous: false, - inputs: [{ indexed: false, internalType: "uint256", name: "index", type: "uint256" }], - name: "GrantPermissionsAdded", - type: "event", - }, -]; - -async function main() { - const networkName = hre.network.name; - const config = NETWORK_CONFIG[networkName]; - if (!config) { - throw new Error( - `addGrantPermissions: unsupported network "${networkName}". Use --network bsctestnet or --network bscmainnet.`, - ); - } - const { ACM_AGGREGATOR, PERMISSIONS } = config; - - console.log(`Network: ${networkName}, ACM Aggregator: ${ACM_AGGREGATOR}`); - console.log(`Total PERMISSIONS to add: ${PERMISSIONS.length}`); - - const [signer] = await ethers.getSigners(); - const acmAggregator = new ethers.Contract(ACM_AGGREGATOR, ACM_AGGREGATOR_ABI, signer); - - const tx = await acmAggregator.addGrantPermissions(PERMISSIONS); - console.log(`Transaction hash: ${tx.hash}`); - - const receipt = await tx.wait(); - const event = receipt.events?.find((e: { event: string }) => e.event === "GrantPermissionsAdded"); - - if (event) { - console.log(`GrantPermissionsAdded index: ${event.args?.index.toString()}`); - } else { - console.log("GrantPermissionsAdded event not found in receipt"); - } -} - -main() - .then(() => process.exit(0)) - .catch(error => { - console.error(error); - process.exit(1); - }); diff --git a/vips/vip-664/bsctestnet.ts b/vips/vip-664/bsctestnet.ts index 8dc121e56..5e8d024fe 100644 --- a/vips/vip-664/bsctestnet.ts +++ b/vips/vip-664/bsctestnet.ts @@ -147,7 +147,7 @@ If passed, this VIP will configure the Institutional Fixed Rate Vault system on // ────────────────────────────────────────────────────────────────────── // Phase 1 — ACM permissions via Aggregator // ────────────────────────────────────────────────────────────────────── - // Permissions were pre-loaded into ACM Aggregator via addGrantPermissions.ts. + // Permissions were pre-loaded into ACM Aggregator. // Grant admin role, execute batched permissions, then revoke admin role. { From e4bdb5f747c38ff2cd8b08288d06538cfaf9b292 Mon Sep 17 00:00:00 2001 From: GitGuru7 Date: Wed, 27 May 2026 12:01:51 +0530 Subject: [PATCH 05/10] "fix: add addGrantPermissions script and preload permissions in acm aggregator" --- simulations/vip-664/bscmainnet.ts | 24 +++++--- simulations/vip-664/bsctestnet-addendum.ts | 24 +++++--- vips/vip-664/addGrantPermissions.ts | 72 ++++++++++++++++++++++ vips/vip-664/bscmainnet.ts | 8 +-- vips/vip-664/bsctestnet-addendum.ts | 17 ++--- 5 files changed, 112 insertions(+), 33 deletions(-) create mode 100644 vips/vip-664/addGrantPermissions.ts diff --git a/simulations/vip-664/bscmainnet.ts b/simulations/vip-664/bscmainnet.ts index 540fe17a0..f18fafd78 100644 --- a/simulations/vip-664/bscmainnet.ts +++ b/simulations/vip-664/bscmainnet.ts @@ -8,12 +8,14 @@ import { forking, testVip } from "src/vip-framework"; import vip664, { ACM_AGGREGATOR, + ACM_AGGREGATOR_INDEX, DEFAULT_ADMIN_ROLE, EXPECTED_PERMISSION_GRANTED_EVENTS, INSTITUTIONAL_VAULT_CONTROLLER, INSTITUTION_POSITION_TOKEN, LIQUIDATION_ADAPTER, LIQUIDATOR_WHITELIST, + PERMISSIONS, PERMISSION_ENTRIES, SETTLER_WHITELIST, } from "../../vips/vip-664/bscmainnet"; @@ -31,7 +33,7 @@ const { ACCESS_CONTROL_MANAGER, } = NETWORK_ADDRESSES.bscmainnet; -const FORK_BLOCK = 100523019; +const FORK_BLOCK = 100682646; // To make test names readable. const LABEL: Record = { @@ -48,6 +50,7 @@ forking(FORK_BLOCK, async () => { let controller: Contract; let liquidationAdapter: Contract; let positionToken: Contract; + let acmAggregator: Contract; before(async () => { accessControlManager = new ethers.Contract(ACCESS_CONTROL_MANAGER, ACCESS_CONTROL_MANAGER_ABI, ethers.provider); @@ -58,6 +61,7 @@ forking(FORK_BLOCK, async () => { ); liquidationAdapter = new ethers.Contract(LIQUIDATION_ADAPTER, LIQUIDATION_ADAPTER_ABI, ethers.provider); positionToken = new ethers.Contract(INSTITUTION_POSITION_TOKEN, INSTITUTION_POSITION_TOKEN_ABI, ethers.provider); + acmAggregator = new ethers.Contract(ACM_AGGREGATOR, ACM_AGGREGATOR_ABI, ethers.provider); }); // Contracts deployed and deploy-script state is in place. @@ -99,8 +103,17 @@ forking(FORK_BLOCK, async () => { }); }); - // None of the planned grants exist yet. + // Permissions are pre-loaded in the Aggregator but not yet applied to the ACM. describe("Pre-VIP: ACM permissions not yet granted", () => { + it("aggregator should hold the pre-loaded batch at ACM_AGGREGATOR_INDEX", async () => { + for (let i = 0; i < PERMISSIONS.length; i++) { + const stored = await acmAggregator.grantPermissions(ACM_AGGREGATOR_INDEX, i); + expect(stored.contractAddress.toLowerCase()).to.equal(PERMISSIONS[i][0].toLowerCase()); + expect(stored.functionSig).to.equal(PERMISSIONS[i][1]); + expect(stored.account.toLowerCase()).to.equal(PERMISSIONS[i][2].toLowerCase()); + } + }); + for (const { target, fn, callers } of PERMISSION_ENTRIES) { for (const account of callers) { it(`${LABEL[account]} should NOT yet have permission: ${fn} on ${target}`, async () => { @@ -119,12 +132,7 @@ forking(FORK_BLOCK, async () => { ["RoleGranted", "RoleRevoked"], [EXPECTED_PERMISSION_GRANTED_EVENTS + 1, 1], ); - await expectEvents( - txResponse, - [ACM_AGGREGATOR_ABI], - ["GrantPermissionsAdded", "GrantPermissionsExecuted"], - [1, 1], - ); + await expectEvents(txResponse, [ACM_AGGREGATOR_ABI], ["GrantPermissionsExecuted"], [1]); }, }); diff --git a/simulations/vip-664/bsctestnet-addendum.ts b/simulations/vip-664/bsctestnet-addendum.ts index d13c6b770..f619d63e3 100644 --- a/simulations/vip-664/bsctestnet-addendum.ts +++ b/simulations/vip-664/bsctestnet-addendum.ts @@ -8,12 +8,14 @@ import { forking, testVip } from "src/vip-framework"; import vip664TestnetAddendum, { ACM_AGGREGATOR, + ACM_AGGREGATOR_INDEX, DEFAULT_ADMIN_ROLE, EXPECTED_PERMISSION_GRANTED_EVENTS, INSTITUTIONAL_VAULT_CONTROLLER, INSTITUTION_POSITION_TOKEN, LIQUIDATION_ADAPTER, LIQUIDATOR_WHITELIST, + PERMISSIONS, PERMISSION_ENTRIES, SETTLER_WHITELIST, } from "../../vips/vip-664/bsctestnet-addendum"; @@ -31,7 +33,7 @@ const { ACCESS_CONTROL_MANAGER, } = NETWORK_ADDRESSES.bsctestnet; -const FORK_BLOCK = 109652392; +const FORK_BLOCK = 109821310; // To make test names readable. const LABEL: Record = { @@ -46,6 +48,7 @@ forking(FORK_BLOCK, async () => { let controller: Contract; let liquidationAdapter: Contract; let positionToken: Contract; + let acmAggregator: Contract; before(async () => { accessControlManager = new ethers.Contract(ACCESS_CONTROL_MANAGER, ACCESS_CONTROL_MANAGER_ABI, ethers.provider); @@ -56,6 +59,7 @@ forking(FORK_BLOCK, async () => { ); liquidationAdapter = new ethers.Contract(LIQUIDATION_ADAPTER, LIQUIDATION_ADAPTER_ABI, ethers.provider); positionToken = new ethers.Contract(INSTITUTION_POSITION_TOKEN, INSTITUTION_POSITION_TOKEN_ABI, ethers.provider); + acmAggregator = new ethers.Contract(ACM_AGGREGATOR, ACM_AGGREGATOR_ABI, ethers.provider); }); // Contracts redeployed and deploy-script state is in place. @@ -97,8 +101,17 @@ forking(FORK_BLOCK, async () => { }); }); - // None of the planned grants exist yet against the redeployed contracts. + // Permissions are pre-loaded but not yet applied to the ACM. describe("Pre-VIP: ACM permissions not yet granted", () => { + it("aggregator should hold the pre-loaded batch at ACM_AGGREGATOR_INDEX", async () => { + for (let i = 0; i < PERMISSIONS.length; i++) { + const stored = await acmAggregator.grantPermissions(ACM_AGGREGATOR_INDEX, i); + expect(stored.contractAddress.toLowerCase()).to.equal(PERMISSIONS[i][0].toLowerCase()); + expect(stored.functionSig).to.equal(PERMISSIONS[i][1]); + expect(stored.account.toLowerCase()).to.equal(PERMISSIONS[i][2].toLowerCase()); + } + }); + for (const { target, fn, callers } of PERMISSION_ENTRIES) { for (const account of callers) { it(`${LABEL[account] ?? account} should NOT yet have permission: ${fn} on ${target}`, async () => { @@ -119,12 +132,7 @@ forking(FORK_BLOCK, async () => { ["PermissionGranted", "RoleGranted", "RoleRevoked"], [EXPECTED_PERMISSION_GRANTED_EVENTS, EXPECTED_PERMISSION_GRANTED_EVENTS + 1, 1], ); - await expectEvents( - txResponse, - [ACM_AGGREGATOR_ABI], - ["GrantPermissionsAdded", "GrantPermissionsExecuted"], - [1, 1], - ); + await expectEvents(txResponse, [ACM_AGGREGATOR_ABI], ["GrantPermissionsExecuted"], [1]); }, }, ); diff --git a/vips/vip-664/addGrantPermissions.ts b/vips/vip-664/addGrantPermissions.ts new file mode 100644 index 000000000..9f72efe3e --- /dev/null +++ b/vips/vip-664/addGrantPermissions.ts @@ -0,0 +1,72 @@ +import hre, { ethers } from "hardhat"; + +import * as bscmainnet from "./bscmainnet"; +import * as bsctestnet from "./bsctestnet-addendum"; + +const NETWORK_CONFIG: Record = { + bsctestnet: { ACM_AGGREGATOR: bsctestnet.ACM_AGGREGATOR, PERMISSIONS: bsctestnet.PERMISSIONS }, + bscmainnet: { ACM_AGGREGATOR: bscmainnet.ACM_AGGREGATOR, PERMISSIONS: bscmainnet.PERMISSIONS }, +}; + +const ACM_AGGREGATOR_ABI = [ + { + inputs: [ + { + components: [ + { internalType: "address", name: "contractAddress", type: "address" }, + { internalType: "string", name: "functionSig", type: "string" }, + { internalType: "address", name: "account", type: "address" }, + ], + internalType: "struct ACMCommandsAggregator.Permission[]", + name: "_PERMISSIONS", + type: "tuple[]", + }, + ], + name: "addGrantPermissions", + outputs: [], + stateMutability: "nonpayable", + type: "function", + }, + { + anonymous: false, + inputs: [{ indexed: false, internalType: "uint256", name: "index", type: "uint256" }], + name: "GrantPermissionsAdded", + type: "event", + }, +]; + +async function main() { + const networkName = hre.network.name; + const config = NETWORK_CONFIG[networkName]; + if (!config) { + throw new Error( + `addGrantPermissions: unsupported network "${networkName}". Use --network bsctestnet or --network bscmainnet.`, + ); + } + const { ACM_AGGREGATOR, PERMISSIONS } = config; + + console.log(`Network: ${networkName}, ACM Aggregator: ${ACM_AGGREGATOR}`); + console.log(`Total PERMISSIONS to add: ${PERMISSIONS.length}`); + + const [signer] = await ethers.getSigners(); + const acmAggregator = new ethers.Contract(ACM_AGGREGATOR, ACM_AGGREGATOR_ABI, signer); + + const tx = await acmAggregator.addGrantPermissions(PERMISSIONS); + console.log(`Transaction hash: ${tx.hash}`); + + const receipt = await tx.wait(); + const event = receipt.events?.find((e: { event: string }) => e.event === "GrantPermissionsAdded"); + + if (event) { + console.log(`GrantPermissionsAdded index: ${event.args?.index.toString()}`); + } else { + console.log("GrantPermissionsAdded event not found in receipt"); + } +} + +main() + .then(() => process.exit(0)) + .catch(error => { + console.error(error); + process.exit(1); + }); diff --git a/vips/vip-664/bscmainnet.ts b/vips/vip-664/bscmainnet.ts index 0dc88c35b..40e69fc15 100644 --- a/vips/vip-664/bscmainnet.ts +++ b/vips/vip-664/bscmainnet.ts @@ -167,12 +167,8 @@ If passed, this VIP will configure the Institutional Fixed Rate Vault system on return makeProposal( [ - // Step 1 — Load and execute the ACM permission batch via the aggregator. - { - target: ACM_AGGREGATOR, - signature: "addGrantPermissions((address,string,address)[])", - params: [PERMISSIONS], - }, + // Step 1 — Execute the ACM permission batch via the aggregator. + // Permissions are pre-loaded into the aggregator off-chain via addGrantPermissions.ts. { target: ACCESS_CONTROL_MANAGER, signature: "grantRole(bytes32,address)", diff --git a/vips/vip-664/bsctestnet-addendum.ts b/vips/vip-664/bsctestnet-addendum.ts index 917f5156a..98a5d4f7a 100644 --- a/vips/vip-664/bsctestnet-addendum.ts +++ b/vips/vip-664/bsctestnet-addendum.ts @@ -144,14 +144,13 @@ export const vip664TestnetAddendum = () => { description: `#### Summary The Institutional Fixed Rate Vault contracts were redeployed on BNB Chain Testnet. This addendum -re-runs the configuration against the new contract addresses, following the same on-chain flow used -in the [VIP-664 BNB mainnet proposal](../bscmainnet.ts) where permissions are loaded into the ACM -aggregator inline via the VIP itself rather than via a pre-load script. +re-runs the configuration against the new contract addresses, following the same flow used in the +[VIP-664 BNB mainnet proposal](../bscmainnet.ts). Permissions are pre-loaded into the +\`ACMCommandsAggregator\` off-chain via \`addGrantPermissions.ts\`; the VIP only executes the batch. If passed, this VIP will: -1. Load and execute the ACM permission batch (${EXPECTED_PERMISSION_GRANTED_EVENTS} total grants) via \`ACMCommandsAggregator\`: - - \`addGrantPermissions\` to load the batch onto the aggregator +1. Execute the pre-loaded ACM permission batch (${EXPECTED_PERMISSION_GRANTED_EVENTS} total grants) via \`ACMCommandsAggregator\`: - \`grantRole(DEFAULT_ADMIN_ROLE, aggregator)\` so the aggregator can apply grants - \`executeGrantPermissions\` to apply all permissions atomically - \`revokeRole(DEFAULT_ADMIN_ROLE, aggregator)\` to remove the elevated role @@ -172,12 +171,8 @@ If passed, this VIP will: return makeProposal( [ - // Step 1 — Load and execute the ACM permission batch via the aggregator. - { - target: ACM_AGGREGATOR, - signature: "addGrantPermissions((address,string,address)[])", - params: [PERMISSIONS], - }, + // Step 1 — Execute the ACM permission batch via the aggregator. + // Permissions are pre-loaded into the aggregator off-chain via addGrantPermissions.ts. { target: ACCESS_CONTROL_MANAGER, signature: "grantRole(bytes32,address)", From 44bb1d9514f77f7cc644b57e3ce78aa9ceed57c1 Mon Sep 17 00:00:00 2001 From: GitGuru7 Date: Wed, 27 May 2026 12:56:16 +0530 Subject: [PATCH 06/10] feat: upgrade psr implementation --- simulations/vip-664/abi/ProxyAdmin.json | 151 +++++++++++++++++++++ simulations/vip-664/bscmainnet.ts | 16 ++- simulations/vip-664/bsctestnet-addendum.ts | 16 ++- vips/vip-664/bscmainnet.ts | 17 ++- vips/vip-664/bsctestnet-addendum.ts | 17 ++- 5 files changed, 213 insertions(+), 4 deletions(-) create mode 100644 simulations/vip-664/abi/ProxyAdmin.json diff --git a/simulations/vip-664/abi/ProxyAdmin.json b/simulations/vip-664/abi/ProxyAdmin.json new file mode 100644 index 000000000..b4c51d8df --- /dev/null +++ b/simulations/vip-664/abi/ProxyAdmin.json @@ -0,0 +1,151 @@ +[ + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "previousOwner", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "newOwner", + "type": "address" + } + ], + "name": "OwnershipTransferred", + "type": "event" + }, + { + "inputs": [ + { + "internalType": "contract TransparentUpgradeableProxy", + "name": "proxy", + "type": "address" + }, + { + "internalType": "address", + "name": "newAdmin", + "type": "address" + } + ], + "name": "changeProxyAdmin", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "contract TransparentUpgradeableProxy", + "name": "proxy", + "type": "address" + } + ], + "name": "getProxyAdmin", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "contract TransparentUpgradeableProxy", + "name": "proxy", + "type": "address" + } + ], + "name": "getProxyImplementation", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "owner", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "renounceOwnership", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "newOwner", + "type": "address" + } + ], + "name": "transferOwnership", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "contract TransparentUpgradeableProxy", + "name": "proxy", + "type": "address" + }, + { + "internalType": "address", + "name": "implementation", + "type": "address" + } + ], + "name": "upgrade", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "contract TransparentUpgradeableProxy", + "name": "proxy", + "type": "address" + }, + { + "internalType": "address", + "name": "implementation", + "type": "address" + }, + { + "internalType": "bytes", + "name": "data", + "type": "bytes" + } + ], + "name": "upgradeAndCall", + "outputs": [], + "stateMutability": "payable", + "type": "function" + } +] diff --git a/simulations/vip-664/bscmainnet.ts b/simulations/vip-664/bscmainnet.ts index f18fafd78..9185ff0d0 100644 --- a/simulations/vip-664/bscmainnet.ts +++ b/simulations/vip-664/bscmainnet.ts @@ -15,8 +15,11 @@ import vip664, { INSTITUTION_POSITION_TOKEN, LIQUIDATION_ADAPTER, LIQUIDATOR_WHITELIST, + NEW_PSR_IMPLEMENTATION, PERMISSIONS, PERMISSION_ENTRIES, + PROTOCOL_SHARE_RESERVE, + PROXY_ADMIN, SETTLER_WHITELIST, } from "../../vips/vip-664/bscmainnet"; import ACM_AGGREGATOR_ABI from "./abi/ACMAggregator.json"; @@ -24,6 +27,7 @@ import ACCESS_CONTROL_MANAGER_ABI from "./abi/AccessControlManager.json"; import INSTITUTION_POSITION_TOKEN_ABI from "./abi/InstitutionPositionToken.json"; import INSTITUTIONAL_VAULT_CONTROLLER_ABI from "./abi/InstitutionalVaultController.json"; import LIQUIDATION_ADAPTER_ABI from "./abi/LiquidationAdapter.json"; +import PROXY_ADMIN_ABI from "./abi/ProxyAdmin.json"; const { NORMAL_TIMELOCK: NORMAL, @@ -33,7 +37,7 @@ const { ACCESS_CONTROL_MANAGER, } = NETWORK_ADDRESSES.bscmainnet; -const FORK_BLOCK = 100682646; +const FORK_BLOCK = 100689867; // To make test names readable. const LABEL: Record = { @@ -51,6 +55,7 @@ forking(FORK_BLOCK, async () => { let liquidationAdapter: Contract; let positionToken: Contract; let acmAggregator: Contract; + let proxyAdmin: Contract; before(async () => { accessControlManager = new ethers.Contract(ACCESS_CONTROL_MANAGER, ACCESS_CONTROL_MANAGER_ABI, ethers.provider); @@ -62,6 +67,7 @@ forking(FORK_BLOCK, async () => { liquidationAdapter = new ethers.Contract(LIQUIDATION_ADAPTER, LIQUIDATION_ADAPTER_ABI, ethers.provider); positionToken = new ethers.Contract(INSTITUTION_POSITION_TOKEN, INSTITUTION_POSITION_TOKEN_ABI, ethers.provider); acmAggregator = new ethers.Contract(ACM_AGGREGATOR, ACM_AGGREGATOR_ABI, ethers.provider); + proxyAdmin = new ethers.Contract(PROXY_ADMIN, PROXY_ADMIN_ABI, ethers.provider); }); // Contracts deployed and deploy-script state is in place. @@ -101,6 +107,10 @@ forking(FORK_BLOCK, async () => { it("controller liquidationAdapter should be address(0) before VIP", async () => { expect(await controller.liquidationAdapter()).to.equal(ethers.constants.AddressZero); }); + + it("ProtocolShareReserve should not yet point to the new implementation", async () => { + expect(await proxyAdmin.getProxyImplementation(PROTOCOL_SHARE_RESERVE)).to.not.equal(NEW_PSR_IMPLEMENTATION); + }); }); // Permissions are pre-loaded in the Aggregator but not yet applied to the ACM. @@ -168,6 +178,10 @@ forking(FORK_BLOCK, async () => { it("DEFAULT_ADMIN_ROLE should be revoked from ACM Aggregator", async () => { expect(await accessControlManager.hasRole(DEFAULT_ADMIN_ROLE, ACM_AGGREGATOR)).to.be.false; }); + + it("ProtocolShareReserve should point to the new implementation", async () => { + expect(await proxyAdmin.getProxyImplementation(PROTOCOL_SHARE_RESERVE)).to.equal(NEW_PSR_IMPLEMENTATION); + }); }); describe("Post-VIP: liquidator/settler whitelists", () => { diff --git a/simulations/vip-664/bsctestnet-addendum.ts b/simulations/vip-664/bsctestnet-addendum.ts index f619d63e3..c85fbea44 100644 --- a/simulations/vip-664/bsctestnet-addendum.ts +++ b/simulations/vip-664/bsctestnet-addendum.ts @@ -15,8 +15,11 @@ import vip664TestnetAddendum, { INSTITUTION_POSITION_TOKEN, LIQUIDATION_ADAPTER, LIQUIDATOR_WHITELIST, + NEW_PSR_IMPLEMENTATION, PERMISSIONS, PERMISSION_ENTRIES, + PROTOCOL_SHARE_RESERVE, + PROXY_ADMIN, SETTLER_WHITELIST, } from "../../vips/vip-664/bsctestnet-addendum"; import ACM_AGGREGATOR_ABI from "./abi/ACMAggregator.json"; @@ -24,6 +27,7 @@ import ACCESS_CONTROL_MANAGER_ABI from "./abi/AccessControlManager.json"; import INSTITUTION_POSITION_TOKEN_ABI from "./abi/InstitutionPositionToken.json"; import INSTITUTIONAL_VAULT_CONTROLLER_ABI from "./abi/InstitutionalVaultController.json"; import LIQUIDATION_ADAPTER_ABI from "./abi/LiquidationAdapter.json"; +import PROXY_ADMIN_ABI from "./abi/ProxyAdmin.json"; const { NORMAL_TIMELOCK: NORMAL, @@ -33,7 +37,7 @@ const { ACCESS_CONTROL_MANAGER, } = NETWORK_ADDRESSES.bsctestnet; -const FORK_BLOCK = 109821310; +const FORK_BLOCK = 109829264; // To make test names readable. const LABEL: Record = { @@ -49,6 +53,7 @@ forking(FORK_BLOCK, async () => { let liquidationAdapter: Contract; let positionToken: Contract; let acmAggregator: Contract; + let proxyAdmin: Contract; before(async () => { accessControlManager = new ethers.Contract(ACCESS_CONTROL_MANAGER, ACCESS_CONTROL_MANAGER_ABI, ethers.provider); @@ -60,6 +65,7 @@ forking(FORK_BLOCK, async () => { liquidationAdapter = new ethers.Contract(LIQUIDATION_ADAPTER, LIQUIDATION_ADAPTER_ABI, ethers.provider); positionToken = new ethers.Contract(INSTITUTION_POSITION_TOKEN, INSTITUTION_POSITION_TOKEN_ABI, ethers.provider); acmAggregator = new ethers.Contract(ACM_AGGREGATOR, ACM_AGGREGATOR_ABI, ethers.provider); + proxyAdmin = new ethers.Contract(PROXY_ADMIN, PROXY_ADMIN_ABI, ethers.provider); }); // Contracts redeployed and deploy-script state is in place. @@ -99,6 +105,10 @@ forking(FORK_BLOCK, async () => { it("controller liquidationAdapter should be address(0) before VIP", async () => { expect(await controller.liquidationAdapter()).to.equal(ethers.constants.AddressZero); }); + + it("ProtocolShareReserve should not yet point to the new implementation", async () => { + expect(await proxyAdmin.getProxyImplementation(PROTOCOL_SHARE_RESERVE)).to.not.equal(NEW_PSR_IMPLEMENTATION); + }); }); // Permissions are pre-loaded but not yet applied to the ACM. @@ -169,6 +179,10 @@ forking(FORK_BLOCK, async () => { it("DEFAULT_ADMIN_ROLE should be revoked from ACM Aggregator", async () => { expect(await accessControlManager.hasRole(DEFAULT_ADMIN_ROLE, ACM_AGGREGATOR)).to.be.false; }); + + it("ProtocolShareReserve should point to the new implementation", async () => { + expect(await proxyAdmin.getProxyImplementation(PROTOCOL_SHARE_RESERVE)).to.equal(NEW_PSR_IMPLEMENTATION); + }); }); // Dedicated operator addresses whitelisted. diff --git a/vips/vip-664/bscmainnet.ts b/vips/vip-664/bscmainnet.ts index 40e69fc15..741b57adb 100644 --- a/vips/vip-664/bscmainnet.ts +++ b/vips/vip-664/bscmainnet.ts @@ -21,6 +21,11 @@ export const INSTITUTIONAL_VAULT_CONTROLLER = "0x6D9e91cB766259af42619c14c994E69 export const LIQUIDATION_ADAPTER = "0x17A6222fB8b4b6D852cA54f5bc376a6A2c6224Bd"; export const INSTITUTION_POSITION_TOKEN = "0x3Ed56f6937fc8549f9325405d1e8E650739647Fa"; +// ProtocolShareReserve upgrade +export const PROTOCOL_SHARE_RESERVE = "0xCa01D5A9A248a830E9D93231e791B1afFed7c446"; +export const PROXY_ADMIN = "0x6beb6d2695b67feb73ad4f172e8e2975497187e4"; +export const NEW_PSR_IMPLEMENTATION = "0x4eC6D748a2647000895b455c408f85602A144Ed6"; + // ACM aggregator (mainnet) export const ACM_AGGREGATOR = "0x8b443Ea6726E56DF4C4F62f80F0556bB9B2a7c64"; export const DEFAULT_ADMIN_ROLE = "0x0000000000000000000000000000000000000000000000000000000000000000"; @@ -153,13 +158,16 @@ If passed, this VIP will configure the Institutional Fixed Rate Vault system on 3. Set the \`LiquidationAdapter\` on the controller via \`setLiquidationAdapter()\`. 4. Complete the two-step position token ownership transfer via \`acceptPositionTokenOwnership()\`. 5. Whitelist the Critical Guardian as a liquidator and settler on the \`LiquidationAdapter\`. +6. Upgrade the \`ProtocolShareReserve\` proxy to a new implementation that supports the institutional-vault liquidation income type. #### Deployed Contracts - **InstitutionalVaultController** (proxy): ${INSTITUTIONAL_VAULT_CONTROLLER} - **LiquidationAdapter** (proxy): ${LIQUIDATION_ADAPTER} - **InstitutionPositionToken**: ${INSTITUTION_POSITION_TOKEN} -- **Critical Guardian**: ${CRITICAL_GUARDIAN}`, +- **Critical Guardian**: ${CRITICAL_GUARDIAN} +- **ProtocolShareReserve** (proxy): ${PROTOCOL_SHARE_RESERVE} +- **New ProtocolShareReserve implementation**: ${NEW_PSR_IMPLEMENTATION}`, 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", @@ -212,6 +220,13 @@ If passed, this VIP will configure the Institutional Fixed Rate Vault system on signature: "setSettlerWhitelist(address,bool)", params: [account, true], })), + + // Step 5 — Upgrade the ProtocolShareReserve + { + target: PROXY_ADMIN, + signature: "upgrade(address,address)", + params: [PROTOCOL_SHARE_RESERVE, NEW_PSR_IMPLEMENTATION], + }, ], meta, ProposalType.REGULAR, diff --git a/vips/vip-664/bsctestnet-addendum.ts b/vips/vip-664/bsctestnet-addendum.ts index 98a5d4f7a..66c543b7d 100644 --- a/vips/vip-664/bsctestnet-addendum.ts +++ b/vips/vip-664/bsctestnet-addendum.ts @@ -21,6 +21,11 @@ export const INSTITUTIONAL_VAULT_CONTROLLER = "0xf77dED2A00F94e33C392126238360D4 export const LIQUIDATION_ADAPTER = "0x4b302b56315Ca16A0A4565108e62404496916491"; export const INSTITUTION_POSITION_TOKEN = "0x71dA473257a96e975558C8edD8491AD0880EFCe5"; +// ProtocolShareReserve upgrade (adds support for the institutional-vault liquidation income type). +export const PROTOCOL_SHARE_RESERVE = "0x25c7c7D6Bf710949fD7f03364E9BA19a1b3c10E3"; +export const PROXY_ADMIN = "0x7877ffd62649b6a1557b55d4c20fcbab17344c91"; +export const NEW_PSR_IMPLEMENTATION = "0x6eFa596c53E6A753DdA643e3e3FEcA1570879b7C"; + // ACM aggregator (existing testnet deployment). // Index is incremented because index 1 was consumed by the original VIP-664 pre-load. export const ACM_AGGREGATOR = "0xB59523628D92f914ec6624Be4281397E8aFD71EF"; @@ -158,12 +163,15 @@ If passed, this VIP will: 3. Set the \`LiquidationAdapter\` on the controller via \`setLiquidationAdapter()\`. 4. Complete the two-step position token ownership transfer via \`acceptPositionTokenOwnership()\`. 5. Whitelist the Guardian as a liquidator and settler on the \`LiquidationAdapter\`. +6. Upgrade the \`ProtocolShareReserve\` proxy to a new implementation that supports the institutional-vault liquidation income type. #### Deployed Contracts (redeployed) - **InstitutionalVaultController** (proxy): ${INSTITUTIONAL_VAULT_CONTROLLER} - **LiquidationAdapter** (proxy): ${LIQUIDATION_ADAPTER} -- **InstitutionPositionToken**: ${INSTITUTION_POSITION_TOKEN}`, +- **InstitutionPositionToken**: ${INSTITUTION_POSITION_TOKEN} +- **ProtocolShareReserve** (proxy): ${PROTOCOL_SHARE_RESERVE} +- **New ProtocolShareReserve implementation**: ${NEW_PSR_IMPLEMENTATION}`, 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", @@ -216,6 +224,13 @@ If passed, this VIP will: signature: "setSettlerWhitelist(address,bool)", params: [account, true], })), + + // Step 5 — Upgrade the ProtocolShareReserve + { + target: PROXY_ADMIN, + signature: "upgrade(address,address)", + params: [PROTOCOL_SHARE_RESERVE, NEW_PSR_IMPLEMENTATION], + }, ], meta, ProposalType.REGULAR, From 82848734fc2a2932ebde5e950d9f39d4e4c83132 Mon Sep 17 00:00:00 2001 From: GitGuru7 Date: Wed, 27 May 2026 14:23:08 +0530 Subject: [PATCH 07/10] add cancle vault permission --- simulations/vip-664/bscmainnet.ts | 2 +- simulations/vip-664/bsctestnet-addendum.ts | 2 +- vips/vip-664/bscmainnet.ts | 11 ++++++++--- vips/vip-664/bsctestnet-addendum.ts | 10 +++++++--- 4 files changed, 17 insertions(+), 8 deletions(-) diff --git a/simulations/vip-664/bscmainnet.ts b/simulations/vip-664/bscmainnet.ts index 9185ff0d0..a731a6d3c 100644 --- a/simulations/vip-664/bscmainnet.ts +++ b/simulations/vip-664/bscmainnet.ts @@ -37,7 +37,7 @@ const { ACCESS_CONTROL_MANAGER, } = NETWORK_ADDRESSES.bscmainnet; -const FORK_BLOCK = 100689867; +const FORK_BLOCK = 100701625; // To make test names readable. const LABEL: Record = { diff --git a/simulations/vip-664/bsctestnet-addendum.ts b/simulations/vip-664/bsctestnet-addendum.ts index c85fbea44..28b4c30a1 100644 --- a/simulations/vip-664/bsctestnet-addendum.ts +++ b/simulations/vip-664/bsctestnet-addendum.ts @@ -37,7 +37,7 @@ const { ACCESS_CONTROL_MANAGER, } = NETWORK_ADDRESSES.bsctestnet; -const FORK_BLOCK = 109829264; +const FORK_BLOCK = 109840621; // To make test names readable. const LABEL: Record = { diff --git a/vips/vip-664/bscmainnet.ts b/vips/vip-664/bscmainnet.ts index 741b57adb..50f5aa92e 100644 --- a/vips/vip-664/bscmainnet.ts +++ b/vips/vip-664/bscmainnet.ts @@ -30,7 +30,7 @@ export const NEW_PSR_IMPLEMENTATION = "0x4eC6D748a2647000895b455c408f85602A144Ed export const ACM_AGGREGATOR = "0x8b443Ea6726E56DF4C4F62f80F0556bB9B2a7c64"; export const DEFAULT_ADMIN_ROLE = "0x0000000000000000000000000000000000000000000000000000000000000000"; -export const ACM_AGGREGATOR_INDEX = 2; +export const ACM_AGGREGATOR_INDEX = 3; export const LIQUIDATOR_WHITELIST: string[] = [CRITICAL_GUARDIAN]; export const SETTLER_WHITELIST: string[] = [CRITICAL_GUARDIAN]; @@ -48,6 +48,11 @@ export const PERMISSION_ENTRIES: PermissionEntry[] = [ fn: "openVault(address)", callers: [NORMAL, FAST_TRACK, CRITICAL, CRITICAL_GUARDIAN], }, + { + target: INSTITUTIONAL_VAULT_CONTROLLER, + fn: "cancelVault(address)", + callers: [NORMAL, FAST_TRACK, CRITICAL, CRITICAL_GUARDIAN], + }, { target: INSTITUTIONAL_VAULT_CONTROLLER, fn: "partialPauseVault(address)", @@ -75,8 +80,8 @@ export const PERMISSION_ENTRIES: PermissionEntry[] = [ }, { target: INSTITUTIONAL_VAULT_CONTROLLER, - fn: "approvePositionTransfer(address)", - callers: [NORMAL, FAST_TRACK, CRITICAL], + fn: "approvePositionTransfer(address,address)", + callers: [NORMAL, FAST_TRACK, CRITICAL, CRITICAL_GUARDIAN], }, { target: INSTITUTIONAL_VAULT_CONTROLLER, diff --git a/vips/vip-664/bsctestnet-addendum.ts b/vips/vip-664/bsctestnet-addendum.ts index 66c543b7d..363bb4760 100644 --- a/vips/vip-664/bsctestnet-addendum.ts +++ b/vips/vip-664/bsctestnet-addendum.ts @@ -27,10 +27,9 @@ export const PROXY_ADMIN = "0x7877ffd62649b6a1557b55d4c20fcbab17344c91"; export const NEW_PSR_IMPLEMENTATION = "0x6eFa596c53E6A753DdA643e3e3FEcA1570879b7C"; // ACM aggregator (existing testnet deployment). -// Index is incremented because index 1 was consumed by the original VIP-664 pre-load. export const ACM_AGGREGATOR = "0xB59523628D92f914ec6624Be4281397E8aFD71EF"; export const DEFAULT_ADMIN_ROLE = "0x0000000000000000000000000000000000000000000000000000000000000000"; -export const ACM_AGGREGATOR_INDEX = 2; +export const ACM_AGGREGATOR_INDEX = 3; // Liquidator/settler addresses whitelisted on the `LiquidationAdapter`. // Guardian is whitelisted on testnet for operational convenience. @@ -50,6 +49,11 @@ export const PERMISSION_ENTRIES: PermissionEntry[] = [ fn: "openVault(address)", callers: [NORMAL, FAST_TRACK, CRITICAL, GUARDIAN], }, + { + target: INSTITUTIONAL_VAULT_CONTROLLER, + fn: "cancelVault(address)", + callers: [NORMAL, FAST_TRACK, CRITICAL, GUARDIAN], + }, { target: INSTITUTIONAL_VAULT_CONTROLLER, fn: "partialPauseVault(address)", @@ -77,7 +81,7 @@ export const PERMISSION_ENTRIES: PermissionEntry[] = [ }, { target: INSTITUTIONAL_VAULT_CONTROLLER, - fn: "approvePositionTransfer(address)", + fn: "approvePositionTransfer(address,address)", callers: [NORMAL, FAST_TRACK, CRITICAL, GUARDIAN], }, { From 82cb7c0d0b51e3b9d742f5b5703e606323c4b2ee Mon Sep 17 00:00:00 2001 From: GitGuru7 Date: Wed, 27 May 2026 20:31:11 +0530 Subject: [PATCH 08/10] chore: update abi --- .../vip-664/abi/InstitutionPositionToken.json | 710 +++++++--- .../vip-664/abi/InstitutionalLoanVault.json | 41 +- .../abi/InstitutionalVaultController.json | 1255 ++++++++++++----- .../vip-664/abi/LiquidationAdapter.json | 633 +++++++-- 4 files changed, 1970 insertions(+), 669 deletions(-) diff --git a/simulations/vip-664/abi/InstitutionPositionToken.json b/simulations/vip-664/abi/InstitutionPositionToken.json index 8933a45ce..a77897421 100644 --- a/simulations/vip-664/abi/InstitutionPositionToken.json +++ b/simulations/vip-664/abi/InstitutionPositionToken.json @@ -1,267 +1,631 @@ [ - { "inputs": [], "stateMutability": "nonpayable", "type": "constructor" }, - { "inputs": [], "name": "OwnershipCannotBeRenounced", "type": "error" }, { - "inputs": [{ "internalType": "uint256", "name": "tokenId", "type": "uint256" }], - "name": "TransferNotApproved", - "type": "error" + "type": "constructor", + "inputs": [], + "stateMutability": "nonpayable" }, { - "anonymous": false, + "type": "function", + "name": "acceptOwnership", + "inputs": [], + "outputs": [], + "stateMutability": "nonpayable" + }, + { + "type": "function", + "name": "approve", "inputs": [ - { "indexed": true, "internalType": "address", "name": "owner", "type": "address" }, - { "indexed": true, "internalType": "address", "name": "approved", "type": "address" }, - { "indexed": true, "internalType": "uint256", "name": "tokenId", "type": "uint256" } + { + "name": "to", + "type": "address", + "internalType": "address" + }, + { + "name": "tokenId", + "type": "uint256", + "internalType": "uint256" + } ], - "name": "Approval", - "type": "event" + "outputs": [], + "stateMutability": "nonpayable" }, { - "anonymous": false, + "type": "function", + "name": "approveTransfer", "inputs": [ - { "indexed": true, "internalType": "address", "name": "owner", "type": "address" }, - { "indexed": true, "internalType": "address", "name": "operator", "type": "address" }, - { "indexed": false, "internalType": "bool", "name": "approved", "type": "bool" } + { + "name": "tokenId", + "type": "uint256", + "internalType": "uint256" + }, + { + "name": "recipient", + "type": "address", + "internalType": "address" + } ], - "name": "ApprovalForAll", - "type": "event" + "outputs": [], + "stateMutability": "nonpayable" }, { - "anonymous": false, + "type": "function", + "name": "approvedRecipient", "inputs": [ - { "indexed": true, "internalType": "address", "name": "previousOwner", "type": "address" }, - { "indexed": true, "internalType": "address", "name": "newOwner", "type": "address" } + { + "name": "", + "type": "uint256", + "internalType": "uint256" + } ], - "name": "OwnershipTransferStarted", - "type": "event" + "outputs": [ + { + "name": "", + "type": "address", + "internalType": "address" + } + ], + "stateMutability": "view" }, { - "anonymous": false, + "type": "function", + "name": "balanceOf", "inputs": [ - { "indexed": true, "internalType": "address", "name": "previousOwner", "type": "address" }, - { "indexed": true, "internalType": "address", "name": "newOwner", "type": "address" } + { + "name": "owner", + "type": "address", + "internalType": "address" + } ], - "name": "OwnershipTransferred", - "type": "event" + "outputs": [ + { + "name": "", + "type": "uint256", + "internalType": "uint256" + } + ], + "stateMutability": "view" }, { - "anonymous": false, + "type": "function", + "name": "getApproved", "inputs": [ - { "indexed": true, "internalType": "address", "name": "vault", "type": "address" }, - { "indexed": true, "internalType": "uint256", "name": "tokenId", "type": "uint256" }, - { "indexed": true, "internalType": "address", "name": "institution", "type": "address" } + { + "name": "tokenId", + "type": "uint256", + "internalType": "uint256" + } ], - "name": "PositionTokenMinted", - "type": "event" + "outputs": [ + { + "name": "", + "type": "address", + "internalType": "address" + } + ], + "stateMutability": "view" }, { - "anonymous": false, - "inputs": [{ "indexed": true, "internalType": "uint256", "name": "tokenId", "type": "uint256" }], - "name": "PositionTransferApproved", - "type": "event" + "type": "function", + "name": "isApprovedForAll", + "inputs": [ + { + "name": "owner", + "type": "address", + "internalType": "address" + }, + { + "name": "operator", + "type": "address", + "internalType": "address" + } + ], + "outputs": [ + { + "name": "", + "type": "bool", + "internalType": "bool" + } + ], + "stateMutability": "view" }, { - "anonymous": false, - "inputs": [{ "indexed": true, "internalType": "uint256", "name": "tokenId", "type": "uint256" }], - "name": "PositionTransferRevoked", - "type": "event" + "type": "function", + "name": "mint", + "inputs": [ + { + "name": "to", + "type": "address", + "internalType": "address" + }, + { + "name": "vault", + "type": "address", + "internalType": "address" + } + ], + "outputs": [ + { + "name": "tokenId", + "type": "uint256", + "internalType": "uint256" + } + ], + "stateMutability": "nonpayable" }, { - "anonymous": false, - "inputs": [ - { "indexed": true, "internalType": "address", "name": "from", "type": "address" }, - { "indexed": true, "internalType": "address", "name": "to", "type": "address" }, - { "indexed": true, "internalType": "uint256", "name": "tokenId", "type": "uint256" } + "type": "function", + "name": "name", + "inputs": [], + "outputs": [ + { + "name": "", + "type": "string", + "internalType": "string" + } ], - "name": "Transfer", - "type": "event" + "stateMutability": "view" }, - { "inputs": [], "name": "acceptOwnership", "outputs": [], "stateMutability": "nonpayable", "type": "function" }, { + "type": "function", + "name": "nextTokenId", + "inputs": [], + "outputs": [ + { + "name": "", + "type": "uint256", + "internalType": "uint256" + } + ], + "stateMutability": "view" + }, + { + "type": "function", + "name": "owner", + "inputs": [], + "outputs": [ + { + "name": "", + "type": "address", + "internalType": "address" + } + ], + "stateMutability": "view" + }, + { + "type": "function", + "name": "ownerOf", "inputs": [ - { "internalType": "address", "name": "to", "type": "address" }, - { "internalType": "uint256", "name": "tokenId", "type": "uint256" } + { + "name": "tokenId", + "type": "uint256", + "internalType": "uint256" + } ], - "name": "approve", - "outputs": [], - "stateMutability": "nonpayable", - "type": "function" + "outputs": [ + { + "name": "", + "type": "address", + "internalType": "address" + } + ], + "stateMutability": "view" }, { - "inputs": [{ "internalType": "uint256", "name": "tokenId", "type": "uint256" }], - "name": "approveTransfer", - "outputs": [], - "stateMutability": "nonpayable", - "type": "function" + "type": "function", + "name": "pendingOwner", + "inputs": [], + "outputs": [ + { + "name": "", + "type": "address", + "internalType": "address" + } + ], + "stateMutability": "view" }, { - "inputs": [{ "internalType": "address", "name": "owner", "type": "address" }], - "name": "balanceOf", - "outputs": [{ "internalType": "uint256", "name": "", "type": "uint256" }], - "stateMutability": "view", - "type": "function" + "type": "function", + "name": "renounceOwnership", + "inputs": [], + "outputs": [], + "stateMutability": "pure" }, { - "inputs": [{ "internalType": "uint256", "name": "tokenId", "type": "uint256" }], - "name": "getApproved", - "outputs": [{ "internalType": "address", "name": "", "type": "address" }], - "stateMutability": "view", - "type": "function" + "type": "function", + "name": "revokeTransferApproval", + "inputs": [ + { + "name": "tokenId", + "type": "uint256", + "internalType": "uint256" + } + ], + "outputs": [], + "stateMutability": "nonpayable" }, { + "type": "function", + "name": "safeTransferFrom", "inputs": [ - { "internalType": "address", "name": "owner", "type": "address" }, - { "internalType": "address", "name": "operator", "type": "address" } + { + "name": "from", + "type": "address", + "internalType": "address" + }, + { + "name": "to", + "type": "address", + "internalType": "address" + }, + { + "name": "tokenId", + "type": "uint256", + "internalType": "uint256" + } ], - "name": "isApprovedForAll", - "outputs": [{ "internalType": "bool", "name": "", "type": "bool" }], - "stateMutability": "view", - "type": "function" + "outputs": [], + "stateMutability": "nonpayable" }, { + "type": "function", + "name": "safeTransferFrom", "inputs": [ - { "internalType": "address", "name": "to", "type": "address" }, - { "internalType": "address", "name": "vault", "type": "address" } + { + "name": "from", + "type": "address", + "internalType": "address" + }, + { + "name": "to", + "type": "address", + "internalType": "address" + }, + { + "name": "tokenId", + "type": "uint256", + "internalType": "uint256" + }, + { + "name": "data", + "type": "bytes", + "internalType": "bytes" + } ], - "name": "mint", - "outputs": [{ "internalType": "uint256", "name": "tokenId", "type": "uint256" }], - "stateMutability": "nonpayable", - "type": "function" + "outputs": [], + "stateMutability": "nonpayable" }, { - "inputs": [], - "name": "name", - "outputs": [{ "internalType": "string", "name": "", "type": "string" }], - "stateMutability": "view", - "type": "function" + "type": "function", + "name": "setApprovalForAll", + "inputs": [ + { + "name": "operator", + "type": "address", + "internalType": "address" + }, + { + "name": "approved", + "type": "bool", + "internalType": "bool" + } + ], + "outputs": [], + "stateMutability": "nonpayable" }, { - "inputs": [], - "name": "nextTokenId", - "outputs": [{ "internalType": "uint256", "name": "", "type": "uint256" }], - "stateMutability": "view", - "type": "function" + "type": "function", + "name": "supportsInterface", + "inputs": [ + { + "name": "interfaceId", + "type": "bytes4", + "internalType": "bytes4" + } + ], + "outputs": [ + { + "name": "", + "type": "bool", + "internalType": "bool" + } + ], + "stateMutability": "view" }, { + "type": "function", + "name": "symbol", "inputs": [], - "name": "owner", - "outputs": [{ "internalType": "address", "name": "", "type": "address" }], - "stateMutability": "view", - "type": "function" + "outputs": [ + { + "name": "", + "type": "string", + "internalType": "string" + } + ], + "stateMutability": "view" }, { - "inputs": [{ "internalType": "uint256", "name": "tokenId", "type": "uint256" }], - "name": "ownerOf", - "outputs": [{ "internalType": "address", "name": "", "type": "address" }], - "stateMutability": "view", - "type": "function" + "type": "function", + "name": "tokenIdToVault", + "inputs": [ + { + "name": "", + "type": "uint256", + "internalType": "uint256" + } + ], + "outputs": [ + { + "name": "", + "type": "address", + "internalType": "address" + } + ], + "stateMutability": "view" }, { - "inputs": [], - "name": "pendingOwner", - "outputs": [{ "internalType": "address", "name": "", "type": "address" }], - "stateMutability": "view", - "type": "function" + "type": "function", + "name": "tokenURI", + "inputs": [ + { + "name": "tokenId", + "type": "uint256", + "internalType": "uint256" + } + ], + "outputs": [ + { + "name": "", + "type": "string", + "internalType": "string" + } + ], + "stateMutability": "view" }, - { "inputs": [], "name": "renounceOwnership", "outputs": [], "stateMutability": "nonpayable", "type": "function" }, { - "inputs": [{ "internalType": "uint256", "name": "tokenId", "type": "uint256" }], - "name": "revokeTransferApproval", + "type": "function", + "name": "transferFrom", + "inputs": [ + { + "name": "from", + "type": "address", + "internalType": "address" + }, + { + "name": "to", + "type": "address", + "internalType": "address" + }, + { + "name": "tokenId", + "type": "uint256", + "internalType": "uint256" + } + ], "outputs": [], - "stateMutability": "nonpayable", - "type": "function" + "stateMutability": "nonpayable" }, { + "type": "function", + "name": "transferOwnership", "inputs": [ - { "internalType": "address", "name": "from", "type": "address" }, - { "internalType": "address", "name": "to", "type": "address" }, - { "internalType": "uint256", "name": "tokenId", "type": "uint256" } + { + "name": "newOwner", + "type": "address", + "internalType": "address" + } ], - "name": "safeTransferFrom", "outputs": [], - "stateMutability": "nonpayable", - "type": "function" + "stateMutability": "nonpayable" }, { + "type": "function", + "name": "vaultToTokenId", "inputs": [ - { "internalType": "address", "name": "from", "type": "address" }, - { "internalType": "address", "name": "to", "type": "address" }, - { "internalType": "uint256", "name": "tokenId", "type": "uint256" }, - { "internalType": "bytes", "name": "data", "type": "bytes" } + { + "name": "", + "type": "address", + "internalType": "address" + } ], - "name": "safeTransferFrom", - "outputs": [], - "stateMutability": "nonpayable", - "type": "function" + "outputs": [ + { + "name": "", + "type": "uint256", + "internalType": "uint256" + } + ], + "stateMutability": "view" }, { + "type": "event", + "name": "Approval", "inputs": [ - { "internalType": "address", "name": "operator", "type": "address" }, - { "internalType": "bool", "name": "approved", "type": "bool" } + { + "name": "owner", + "type": "address", + "indexed": true, + "internalType": "address" + }, + { + "name": "approved", + "type": "address", + "indexed": true, + "internalType": "address" + }, + { + "name": "tokenId", + "type": "uint256", + "indexed": true, + "internalType": "uint256" + } ], - "name": "setApprovalForAll", - "outputs": [], - "stateMutability": "nonpayable", - "type": "function" + "anonymous": false }, { - "inputs": [{ "internalType": "bytes4", "name": "interfaceId", "type": "bytes4" }], - "name": "supportsInterface", - "outputs": [{ "internalType": "bool", "name": "", "type": "bool" }], - "stateMutability": "view", - "type": "function" + "type": "event", + "name": "ApprovalForAll", + "inputs": [ + { + "name": "owner", + "type": "address", + "indexed": true, + "internalType": "address" + }, + { + "name": "operator", + "type": "address", + "indexed": true, + "internalType": "address" + }, + { + "name": "approved", + "type": "bool", + "indexed": false, + "internalType": "bool" + } + ], + "anonymous": false }, { - "inputs": [], - "name": "symbol", - "outputs": [{ "internalType": "string", "name": "", "type": "string" }], - "stateMutability": "view", - "type": "function" + "type": "event", + "name": "OwnershipTransferStarted", + "inputs": [ + { + "name": "previousOwner", + "type": "address", + "indexed": true, + "internalType": "address" + }, + { + "name": "newOwner", + "type": "address", + "indexed": true, + "internalType": "address" + } + ], + "anonymous": false }, { - "inputs": [{ "internalType": "uint256", "name": "", "type": "uint256" }], - "name": "tokenIdToVault", - "outputs": [{ "internalType": "address", "name": "", "type": "address" }], - "stateMutability": "view", - "type": "function" + "type": "event", + "name": "OwnershipTransferred", + "inputs": [ + { + "name": "previousOwner", + "type": "address", + "indexed": true, + "internalType": "address" + }, + { + "name": "newOwner", + "type": "address", + "indexed": true, + "internalType": "address" + } + ], + "anonymous": false }, { - "inputs": [{ "internalType": "uint256", "name": "tokenId", "type": "uint256" }], - "name": "tokenURI", - "outputs": [{ "internalType": "string", "name": "", "type": "string" }], - "stateMutability": "view", - "type": "function" + "type": "event", + "name": "PositionTokenMinted", + "inputs": [ + { + "name": "vault", + "type": "address", + "indexed": true, + "internalType": "address" + }, + { + "name": "tokenId", + "type": "uint256", + "indexed": true, + "internalType": "uint256" + }, + { + "name": "institution", + "type": "address", + "indexed": true, + "internalType": "address" + } + ], + "anonymous": false }, { - "inputs": [{ "internalType": "uint256", "name": "", "type": "uint256" }], - "name": "transferApproved", - "outputs": [{ "internalType": "bool", "name": "", "type": "bool" }], - "stateMutability": "view", - "type": "function" + "type": "event", + "name": "PositionTransferApproved", + "inputs": [ + { + "name": "tokenId", + "type": "uint256", + "indexed": true, + "internalType": "uint256" + }, + { + "name": "recipient", + "type": "address", + "indexed": true, + "internalType": "address" + } + ], + "anonymous": false }, { + "type": "event", + "name": "PositionTransferRevoked", "inputs": [ - { "internalType": "address", "name": "from", "type": "address" }, - { "internalType": "address", "name": "to", "type": "address" }, - { "internalType": "uint256", "name": "tokenId", "type": "uint256" } + { + "name": "tokenId", + "type": "uint256", + "indexed": true, + "internalType": "uint256" + } ], - "name": "transferFrom", - "outputs": [], - "stateMutability": "nonpayable", - "type": "function" + "anonymous": false }, { - "inputs": [{ "internalType": "address", "name": "newOwner", "type": "address" }], - "name": "transferOwnership", - "outputs": [], - "stateMutability": "nonpayable", - "type": "function" + "type": "event", + "name": "Transfer", + "inputs": [ + { + "name": "from", + "type": "address", + "indexed": true, + "internalType": "address" + }, + { + "name": "to", + "type": "address", + "indexed": true, + "internalType": "address" + }, + { + "name": "tokenId", + "type": "uint256", + "indexed": true, + "internalType": "uint256" + } + ], + "anonymous": false }, { - "inputs": [{ "internalType": "address", "name": "", "type": "address" }], - "name": "vaultToTokenId", - "outputs": [{ "internalType": "uint256", "name": "", "type": "uint256" }], - "stateMutability": "view", - "type": "function" + "type": "error", + "name": "OwnershipCannotBeRenounced", + "inputs": [] + }, + { + "type": "error", + "name": "TransferNotApproved", + "inputs": [ + { + "name": "tokenId", + "type": "uint256", + "internalType": "uint256" + } + ] + }, + { + "type": "error", + "name": "ZeroAddress", + "inputs": [] } ] diff --git a/simulations/vip-664/abi/InstitutionalLoanVault.json b/simulations/vip-664/abi/InstitutionalLoanVault.json index efe35a44d..21b72547f 100644 --- a/simulations/vip-664/abi/InstitutionalLoanVault.json +++ b/simulations/vip-664/abi/InstitutionalLoanVault.json @@ -147,6 +147,13 @@ ], "stateMutability": "view" }, + { + "type": "function", + "name": "cancelVault", + "inputs": [], + "outputs": [], + "stateMutability": "nonpayable" + }, { "type": "function", "name": "claimRaisedFunds", @@ -626,11 +633,6 @@ "type": "uint256", "internalType": "uint256" }, - { - "name": "idealCollateralValuation", - "type": "uint256", - "internalType": "uint256" - }, { "name": "confiscatedMarginRemaining", "type": "uint256", @@ -1748,6 +1750,25 @@ ], "anonymous": false }, + { + "type": "event", + "name": "VaultCancelled", + "inputs": [ + { + "name": "recipient", + "type": "address", + "indexed": true, + "internalType": "address" + }, + { + "name": "collateralAmount", + "type": "uint256", + "indexed": false, + "internalType": "uint256" + } + ], + "anonymous": false + }, { "type": "event", "name": "VaultClosed", @@ -1963,11 +1984,6 @@ "name": "PartiallyPaused", "inputs": [] }, - { - "type": "error", - "name": "PositionTokenIdNotSet", - "inputs": [] - }, { "type": "error", "name": "SameStateTransition", @@ -1988,11 +2004,6 @@ "name": "WithdrawalWouldBreachLT", "inputs": [] }, - { - "type": "error", - "name": "ZeroAddress", - "inputs": [] - }, { "type": "error", "name": "ZeroRepayAmount", diff --git a/simulations/vip-664/abi/InstitutionalVaultController.json b/simulations/vip-664/abi/InstitutionalVaultController.json index 0bf203dbb..3622b4807 100644 --- a/simulations/vip-664/abi/InstitutionalVaultController.json +++ b/simulations/vip-664/abi/InstitutionalVaultController.json @@ -1,505 +1,1100 @@ [ - { "inputs": [], "stateMutability": "nonpayable", "type": "constructor" }, - { "inputs": [], "name": "InvalidAddress", "type": "error" }, - { "inputs": [], "name": "InvalidConfig", "type": "error" }, - { "inputs": [], "name": "InvalidLatePenaltyRate", "type": "error" }, - { "inputs": [], "name": "InvalidLiquidationIncentive", "type": "error" }, - { "inputs": [], "name": "InvalidLiquidationThreshold", "type": "error" }, - { "inputs": [], "name": "OwnershipCannotBeRenounced", "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": "VaultNotRegistered", "type": "error" }, - { - "anonymous": false, - "inputs": [ - { "indexed": true, "internalType": "address", "name": "oldComptroller", "type": "address" }, - { "indexed": true, "internalType": "address", "name": "newComptroller", "type": "address" } - ], - "name": "ComptrollerUpdated", - "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": "vault", "type": "address" }, - { "indexed": false, "internalType": "uint256", "name": "newRate", "type": "uint256" } - ], - "name": "LatePenaltyRateUpdated", - "type": "event" + "type": "constructor", + "inputs": [], + "stateMutability": "nonpayable" }, { - "anonymous": false, - "inputs": [ - { "indexed": true, "internalType": "address", "name": "oldAdapter", "type": "address" }, - { "indexed": true, "internalType": "address", "name": "newAdapter", "type": "address" } + "type": "function", + "name": "MANTISSA_ONE", + "inputs": [], + "outputs": [ + { + "name": "", + "type": "uint256", + "internalType": "uint256" + } ], - "name": "LiquidationAdapterUpdated", - "type": "event" + "stateMutability": "view" }, { - "anonymous": false, - "inputs": [ - { "indexed": true, "internalType": "address", "name": "vault", "type": "address" }, - { "indexed": false, "internalType": "uint256", "name": "newLI", "type": "uint256" } + "type": "function", + "name": "MANTISSA_ONE_AND_HALF", + "inputs": [], + "outputs": [ + { + "name": "", + "type": "uint256", + "internalType": "uint256" + } ], - "name": "LiquidationIncentiveUpdated", - "type": "event" + "stateMutability": "view" }, { - "anonymous": false, - "inputs": [ - { "indexed": true, "internalType": "address", "name": "vault", "type": "address" }, - { "indexed": false, "internalType": "uint256", "name": "newLT", "type": "uint256" } + "type": "function", + "name": "MAX_APY_BPS", + "inputs": [], + "outputs": [ + { + "name": "", + "type": "uint256", + "internalType": "uint256" + } ], - "name": "LiquidationThresholdUpdated", - "type": "event" + "stateMutability": "view" }, { - "anonymous": false, - "inputs": [ - { "indexed": false, "internalType": "address", "name": "oldAccessControlManager", "type": "address" }, - { "indexed": false, "internalType": "address", "name": "newAccessControlManager", "type": "address" } - ], - "name": "NewAccessControlManager", - "type": "event" + "type": "function", + "name": "acceptOwnership", + "inputs": [], + "outputs": [], + "stateMutability": "nonpayable" }, { - "anonymous": false, - "inputs": [ - { "indexed": true, "internalType": "address", "name": "oldOracle", "type": "address" }, - { "indexed": true, "internalType": "address", "name": "newOracle", "type": "address" } - ], - "name": "OracleUpdated", - "type": "event" + "type": "function", + "name": "acceptPositionTokenOwnership", + "inputs": [], + "outputs": [], + "stateMutability": "nonpayable" }, { - "anonymous": false, - "inputs": [ - { "indexed": true, "internalType": "address", "name": "previousOwner", "type": "address" }, - { "indexed": true, "internalType": "address", "name": "newOwner", "type": "address" } + "type": "function", + "name": "accessControlManager", + "inputs": [], + "outputs": [ + { + "name": "", + "type": "address", + "internalType": "contract IAccessControlManagerV8" + } ], - "name": "OwnershipTransferStarted", - "type": "event" + "stateMutability": "view" }, { - "anonymous": false, + "type": "function", + "name": "allVaults", "inputs": [ - { "indexed": true, "internalType": "address", "name": "previousOwner", "type": "address" }, - { "indexed": true, "internalType": "address", "name": "newOwner", "type": "address" } + { + "name": "", + "type": "uint256", + "internalType": "uint256" + } ], - "name": "OwnershipTransferred", - "type": "event" - }, - { - "anonymous": false, - "inputs": [ - { "indexed": true, "internalType": "address", "name": "oldPSR", "type": "address" }, - { "indexed": true, "internalType": "address", "name": "newPSR", "type": "address" } + "outputs": [ + { + "name": "", + "type": "address", + "internalType": "address" + } ], - "name": "ProtocolShareReserveUpdated", - "type": "event" + "stateMutability": "view" }, { - "anonymous": false, - "inputs": [ - { "indexed": true, "internalType": "address", "name": "oldTreasury", "type": "address" }, - { "indexed": true, "internalType": "address", "name": "newTreasury", "type": "address" } + "type": "function", + "name": "allVaultsLength", + "inputs": [], + "outputs": [ + { + "name": "", + "type": "uint256", + "internalType": "uint256" + } ], - "name": "TreasuryUpdated", - "type": "event" + "stateMutability": "view" }, { - "anonymous": false, + "type": "function", + "name": "approvePositionTransfer", "inputs": [ - { "indexed": true, "internalType": "address", "name": "vault", "type": "address" }, - { "indexed": true, "internalType": "address", "name": "institution", "type": "address" } + { + "name": "vault", + "type": "address", + "internalType": "address" + }, + { + "name": "recipient", + "type": "address", + "internalType": "address" + } ], - "name": "VaultCreated", - "type": "event" + "outputs": [], + "stateMutability": "nonpayable" }, { - "anonymous": false, + "type": "function", + "name": "cancelVault", "inputs": [ - { "indexed": true, "internalType": "address", "name": "oldImpl", "type": "address" }, - { "indexed": true, "internalType": "address", "name": "newImpl", "type": "address" } + { + "name": "vault", + "type": "address", + "internalType": "address" + } ], - "name": "VaultImplementationUpdated", - "type": "event" - }, - { - "inputs": [], - "name": "MANTISSA_ONE", - "outputs": [{ "internalType": "uint256", "name": "", "type": "uint256" }], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [], - "name": "MANTISSA_ONE_AND_HALF", - "outputs": [{ "internalType": "uint256", "name": "", "type": "uint256" }], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [], - "name": "MAX_APY_BPS", - "outputs": [{ "internalType": "uint256", "name": "", "type": "uint256" }], - "stateMutability": "view", - "type": "function" - }, - { "inputs": [], "name": "acceptOwnership", "outputs": [], "stateMutability": "nonpayable", "type": "function" }, - { - "inputs": [], - "name": "acceptPositionTokenOwnership", - "outputs": [], - "stateMutability": "nonpayable", - "type": "function" - }, - { - "inputs": [], - "name": "accessControlManager", - "outputs": [{ "internalType": "contract IAccessControlManagerV8", "name": "", "type": "address" }], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [{ "internalType": "uint256", "name": "", "type": "uint256" }], - "name": "allVaults", - "outputs": [{ "internalType": "address", "name": "", "type": "address" }], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [], - "name": "allVaultsLength", - "outputs": [{ "internalType": "uint256", "name": "", "type": "uint256" }], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [{ "internalType": "address", "name": "vault", "type": "address" }], - "name": "approvePositionTransfer", "outputs": [], - "stateMutability": "nonpayable", - "type": "function" + "stateMutability": "nonpayable" }, { - "inputs": [{ "internalType": "address", "name": "vault", "type": "address" }], + "type": "function", "name": "closeVault", + "inputs": [ + { + "name": "vault", + "type": "address", + "internalType": "address" + } + ], "outputs": [], - "stateMutability": "nonpayable", - "type": "function" + "stateMutability": "nonpayable" }, { - "inputs": [{ "internalType": "address", "name": "vault", "type": "address" }], + "type": "function", "name": "completePauseVault", + "inputs": [ + { + "name": "vault", + "type": "address", + "internalType": "address" + } + ], "outputs": [], - "stateMutability": "nonpayable", - "type": "function" + "stateMutability": "nonpayable" }, { - "inputs": [], + "type": "function", "name": "comptroller", - "outputs": [{ "internalType": "address", "name": "", "type": "address" }], - "stateMutability": "view", - "type": "function" + "inputs": [], + "outputs": [ + { + "name": "", + "type": "address", + "internalType": "address" + } + ], + "stateMutability": "view" }, { + "type": "function", + "name": "createVault", "inputs": [ { - "components": [ - { "internalType": "contract IERC20", "name": "supplyAsset", "type": "address" }, - { "internalType": "uint256", "name": "fixedAPY", "type": "uint256" }, - { "internalType": "uint256", "name": "reserveFactor", "type": "uint256" }, - { "internalType": "uint256", "name": "minBorrowCap", "type": "uint256" }, - { "internalType": "uint256", "name": "maxBorrowCap", "type": "uint256" }, - { "internalType": "uint256", "name": "minSupplierDeposit", "type": "uint256" }, - { "internalType": "uint40", "name": "openDuration", "type": "uint40" }, - { "internalType": "uint40", "name": "lockDuration", "type": "uint40" }, - { "internalType": "uint40", "name": "settlementWindow", "type": "uint40" } - ], - "internalType": "struct VaultConfig", "name": "_vaultConfig", - "type": "tuple" + "type": "tuple", + "internalType": "struct VaultConfig", + "components": [ + { + "name": "supplyAsset", + "type": "address", + "internalType": "contract IERC20" + }, + { + "name": "fixedAPY", + "type": "uint256", + "internalType": "uint256" + }, + { + "name": "reserveFactor", + "type": "uint256", + "internalType": "uint256" + }, + { + "name": "minBorrowCap", + "type": "uint256", + "internalType": "uint256" + }, + { + "name": "maxBorrowCap", + "type": "uint256", + "internalType": "uint256" + }, + { + "name": "minSupplierDeposit", + "type": "uint256", + "internalType": "uint256" + }, + { + "name": "openDuration", + "type": "uint40", + "internalType": "uint40" + }, + { + "name": "lockDuration", + "type": "uint40", + "internalType": "uint40" + }, + { + "name": "settlementWindow", + "type": "uint40", + "internalType": "uint40" + } + ] }, { - "components": [ - { "internalType": "contract IERC20", "name": "collateralAsset", "type": "address" }, - { "internalType": "uint256", "name": "idealCollateralAmount", "type": "uint256" }, - { "internalType": "uint256", "name": "marginRate", "type": "uint256" }, - { "internalType": "address", "name": "institutionOperator", "type": "address" }, - { "internalType": "uint256", "name": "positionTokenId", "type": "uint256" } - ], - "internalType": "struct InstitutionalConfig", "name": "_instConfig", - "type": "tuple" + "type": "tuple", + "internalType": "struct InstitutionalConfig", + "components": [ + { + "name": "collateralAsset", + "type": "address", + "internalType": "contract IERC20" + }, + { + "name": "idealCollateralAmount", + "type": "uint256", + "internalType": "uint256" + }, + { + "name": "marginRate", + "type": "uint256", + "internalType": "uint256" + }, + { + "name": "institutionOperator", + "type": "address", + "internalType": "address" + }, + { + "name": "positionTokenId", + "type": "uint256", + "internalType": "uint256" + } + ] }, { - "components": [ - { "internalType": "uint256", "name": "liquidationThreshold", "type": "uint256" }, - { "internalType": "uint256", "name": "liquidationIncentive", "type": "uint256" }, - { "internalType": "uint256", "name": "latePenaltyRate", "type": "uint256" } - ], - "internalType": "struct RiskConfig", "name": "_riskConfig", - "type": "tuple" + "type": "tuple", + "internalType": "struct RiskConfig", + "components": [ + { + "name": "liquidationThreshold", + "type": "uint256", + "internalType": "uint256" + }, + { + "name": "liquidationIncentive", + "type": "uint256", + "internalType": "uint256" + }, + { + "name": "latePenaltyRate", + "type": "uint256", + "internalType": "uint256" + } + ] }, - { "internalType": "string", "name": "_name", "type": "string" }, - { "internalType": "string", "name": "_symbol", "type": "string" } + { + "name": "_name", + "type": "string", + "internalType": "string" + }, + { + "name": "_symbol", + "type": "string", + "internalType": "string" + } ], - "name": "createVault", - "outputs": [{ "internalType": "address", "name": "vault", "type": "address" }], - "stateMutability": "nonpayable", - "type": "function" + "outputs": [ + { + "name": "vault", + "type": "address", + "internalType": "address" + } + ], + "stateMutability": "nonpayable" }, { - "inputs": [], + "type": "function", "name": "getAggregatedVaultStates", + "inputs": [], "outputs": [ { - "components": [ - { "internalType": "address", "name": "vault", "type": "address" }, - { "internalType": "enum VaultState", "name": "state", "type": "uint8" }, - { "internalType": "address", "name": "institutionOperator", "type": "address" }, - { "internalType": "uint256", "name": "totalRaised", "type": "uint256" }, - { "internalType": "uint256", "name": "outstandingDebt", "type": "uint256" } - ], - "internalType": "struct VaultStateInfo[]", "name": "", - "type": "tuple[]" + "type": "tuple[]", + "internalType": "struct VaultStateInfo[]", + "components": [ + { + "name": "vault", + "type": "address", + "internalType": "address" + }, + { + "name": "state", + "type": "uint8", + "internalType": "enum VaultState" + }, + { + "name": "institutionOperator", + "type": "address", + "internalType": "address" + }, + { + "name": "totalRaised", + "type": "uint256", + "internalType": "uint256" + }, + { + "name": "outstandingDebt", + "type": "uint256", + "internalType": "uint256" + } + ] } ], - "stateMutability": "view", - "type": "function" + "stateMutability": "view" }, { + "type": "function", + "name": "initialize", "inputs": [ - { "internalType": "address", "name": "vaultImplementation_", "type": "address" }, - { "internalType": "address", "name": "oracle_", "type": "address" }, - { "internalType": "address", "name": "protocolShareReserve_", "type": "address" }, - { "internalType": "address", "name": "comptroller_", "type": "address" }, - { "internalType": "address", "name": "treasury_", "type": "address" }, - { "internalType": "address", "name": "positionToken_", "type": "address" }, - { "internalType": "address", "name": "acm_", "type": "address" } + { + "name": "vaultImplementation_", + "type": "address", + "internalType": "address" + }, + { + "name": "oracle_", + "type": "address", + "internalType": "address" + }, + { + "name": "protocolShareReserve_", + "type": "address", + "internalType": "address" + }, + { + "name": "comptroller_", + "type": "address", + "internalType": "address" + }, + { + "name": "treasury_", + "type": "address", + "internalType": "address" + }, + { + "name": "positionToken_", + "type": "address", + "internalType": "address" + }, + { + "name": "acm_", + "type": "address", + "internalType": "address" + } ], - "name": "initialize", "outputs": [], - "stateMutability": "nonpayable", - "type": "function" + "stateMutability": "nonpayable" }, { - "inputs": [{ "internalType": "address", "name": "", "type": "address" }], + "type": "function", "name": "institutionNonce", - "outputs": [{ "internalType": "uint256", "name": "", "type": "uint256" }], - "stateMutability": "view", - "type": "function" + "inputs": [ + { + "name": "", + "type": "address", + "internalType": "address" + } + ], + "outputs": [ + { + "name": "", + "type": "uint256", + "internalType": "uint256" + } + ], + "stateMutability": "view" }, { - "inputs": [{ "internalType": "address", "name": "", "type": "address" }], + "type": "function", "name": "isRegistered", - "outputs": [{ "internalType": "bool", "name": "", "type": "bool" }], - "stateMutability": "view", - "type": "function" + "inputs": [ + { + "name": "", + "type": "address", + "internalType": "address" + } + ], + "outputs": [ + { + "name": "", + "type": "bool", + "internalType": "bool" + } + ], + "stateMutability": "view" }, { - "inputs": [], + "type": "function", "name": "liquidationAdapter", - "outputs": [{ "internalType": "address", "name": "", "type": "address" }], - "stateMutability": "view", - "type": "function" + "inputs": [], + "outputs": [ + { + "name": "", + "type": "address", + "internalType": "address" + } + ], + "stateMutability": "view" }, { - "inputs": [{ "internalType": "address", "name": "vault", "type": "address" }], + "type": "function", "name": "openVault", + "inputs": [ + { + "name": "vault", + "type": "address", + "internalType": "address" + } + ], "outputs": [], - "stateMutability": "nonpayable", - "type": "function" + "stateMutability": "nonpayable" }, { - "inputs": [], + "type": "function", "name": "oracle", - "outputs": [{ "internalType": "address", "name": "", "type": "address" }], - "stateMutability": "view", - "type": "function" + "inputs": [], + "outputs": [ + { + "name": "", + "type": "address", + "internalType": "address" + } + ], + "stateMutability": "view" }, { - "inputs": [], + "type": "function", "name": "owner", - "outputs": [{ "internalType": "address", "name": "", "type": "address" }], - "stateMutability": "view", - "type": "function" + "inputs": [], + "outputs": [ + { + "name": "", + "type": "address", + "internalType": "address" + } + ], + "stateMutability": "view" }, { - "inputs": [{ "internalType": "address", "name": "vault", "type": "address" }], + "type": "function", "name": "partialPauseVault", + "inputs": [ + { + "name": "vault", + "type": "address", + "internalType": "address" + } + ], "outputs": [], - "stateMutability": "nonpayable", - "type": "function" + "stateMutability": "nonpayable" }, { - "inputs": [], + "type": "function", "name": "pendingOwner", - "outputs": [{ "internalType": "address", "name": "", "type": "address" }], - "stateMutability": "view", - "type": "function" + "inputs": [], + "outputs": [ + { + "name": "", + "type": "address", + "internalType": "address" + } + ], + "stateMutability": "view" }, { - "inputs": [], + "type": "function", "name": "positionToken", - "outputs": [{ "internalType": "contract IInstitutionPositionToken", "name": "", "type": "address" }], - "stateMutability": "view", - "type": "function" + "inputs": [], + "outputs": [ + { + "name": "", + "type": "address", + "internalType": "contract IInstitutionPositionToken" + } + ], + "stateMutability": "view" }, { - "inputs": [{ "internalType": "address", "name": "institution", "type": "address" }], + "type": "function", "name": "predictVaultAddress", - "outputs": [{ "internalType": "address", "name": "", "type": "address" }], - "stateMutability": "view", - "type": "function" + "inputs": [ + { + "name": "institution", + "type": "address", + "internalType": "address" + } + ], + "outputs": [ + { + "name": "", + "type": "address", + "internalType": "address" + } + ], + "stateMutability": "view" }, { - "inputs": [], + "type": "function", "name": "protocolShareReserve", - "outputs": [{ "internalType": "address", "name": "", "type": "address" }], - "stateMutability": "view", - "type": "function" + "inputs": [], + "outputs": [ + { + "name": "", + "type": "address", + "internalType": "address" + } + ], + "stateMutability": "view" }, - { "inputs": [], "name": "renounceOwnership", "outputs": [], "stateMutability": "nonpayable", "type": "function" }, { - "inputs": [{ "internalType": "address", "name": "vault", "type": "address" }], + "type": "function", + "name": "renounceOwnership", + "inputs": [], + "outputs": [], + "stateMutability": "pure" + }, + { + "type": "function", "name": "revokePositionTransfer", + "inputs": [ + { + "name": "vault", + "type": "address", + "internalType": "address" + } + ], "outputs": [], - "stateMutability": "nonpayable", - "type": "function" + "stateMutability": "nonpayable" }, { - "inputs": [{ "internalType": "address", "name": "accessControlManager_", "type": "address" }], + "type": "function", "name": "setAccessControlManager", + "inputs": [ + { + "name": "accessControlManager_", + "type": "address", + "internalType": "address" + } + ], "outputs": [], - "stateMutability": "nonpayable", - "type": "function" + "stateMutability": "nonpayable" }, { - "inputs": [{ "internalType": "address", "name": "comptroller_", "type": "address" }], + "type": "function", "name": "setComptroller", + "inputs": [ + { + "name": "comptroller_", + "type": "address", + "internalType": "address" + } + ], "outputs": [], - "stateMutability": "nonpayable", - "type": "function" + "stateMutability": "nonpayable" }, { + "type": "function", + "name": "setLatePenaltyRate", "inputs": [ - { "internalType": "address", "name": "vault", "type": "address" }, - { "internalType": "uint256", "name": "newRate", "type": "uint256" } + { + "name": "vault", + "type": "address", + "internalType": "address" + }, + { + "name": "newRate", + "type": "uint256", + "internalType": "uint256" + } ], - "name": "setLatePenaltyRate", "outputs": [], - "stateMutability": "nonpayable", - "type": "function" + "stateMutability": "nonpayable" }, { - "inputs": [{ "internalType": "address", "name": "adapter", "type": "address" }], + "type": "function", "name": "setLiquidationAdapter", + "inputs": [ + { + "name": "adapter", + "type": "address", + "internalType": "address" + } + ], "outputs": [], - "stateMutability": "nonpayable", - "type": "function" + "stateMutability": "nonpayable" }, { + "type": "function", + "name": "setLiquidationIncentive", "inputs": [ - { "internalType": "address", "name": "vault", "type": "address" }, - { "internalType": "uint256", "name": "newLI", "type": "uint256" } + { + "name": "vault", + "type": "address", + "internalType": "address" + }, + { + "name": "newLI", + "type": "uint256", + "internalType": "uint256" + } ], - "name": "setLiquidationIncentive", "outputs": [], - "stateMutability": "nonpayable", - "type": "function" + "stateMutability": "nonpayable" }, { + "type": "function", + "name": "setLiquidationThreshold", "inputs": [ - { "internalType": "address", "name": "vault", "type": "address" }, - { "internalType": "uint256", "name": "newLT", "type": "uint256" } + { + "name": "vault", + "type": "address", + "internalType": "address" + }, + { + "name": "newLT", + "type": "uint256", + "internalType": "uint256" + } ], - "name": "setLiquidationThreshold", "outputs": [], - "stateMutability": "nonpayable", - "type": "function" + "stateMutability": "nonpayable" }, { - "inputs": [{ "internalType": "address", "name": "oracle_", "type": "address" }], + "type": "function", "name": "setOracle", + "inputs": [ + { + "name": "oracle_", + "type": "address", + "internalType": "address" + } + ], "outputs": [], - "stateMutability": "nonpayable", - "type": "function" + "stateMutability": "nonpayable" }, { - "inputs": [{ "internalType": "address", "name": "psr", "type": "address" }], + "type": "function", "name": "setProtocolShareReserve", + "inputs": [ + { + "name": "psr", + "type": "address", + "internalType": "address" + } + ], "outputs": [], - "stateMutability": "nonpayable", - "type": "function" + "stateMutability": "nonpayable" }, { - "inputs": [{ "internalType": "address", "name": "treasury_", "type": "address" }], + "type": "function", "name": "setTreasury", + "inputs": [ + { + "name": "treasury_", + "type": "address", + "internalType": "address" + } + ], "outputs": [], - "stateMutability": "nonpayable", - "type": "function" + "stateMutability": "nonpayable" }, { - "inputs": [{ "internalType": "address", "name": "impl", "type": "address" }], + "type": "function", "name": "setVaultImplementation", + "inputs": [ + { + "name": "impl", + "type": "address", + "internalType": "address" + } + ], "outputs": [], - "stateMutability": "nonpayable", - "type": "function" + "stateMutability": "nonpayable" }, { + "type": "function", + "name": "sweep", "inputs": [ - { "internalType": "address", "name": "vault", "type": "address" }, - { "internalType": "address", "name": "token", "type": "address" } + { + "name": "vault", + "type": "address", + "internalType": "address" + }, + { + "name": "token", + "type": "address", + "internalType": "address" + } ], - "name": "sweep", "outputs": [], - "stateMutability": "nonpayable", - "type": "function" + "stateMutability": "nonpayable" }, { - "inputs": [{ "internalType": "address", "name": "newOwner", "type": "address" }], + "type": "function", "name": "transferOwnership", + "inputs": [ + { + "name": "newOwner", + "type": "address", + "internalType": "address" + } + ], "outputs": [], - "stateMutability": "nonpayable", - "type": "function" + "stateMutability": "nonpayable" }, { - "inputs": [], + "type": "function", "name": "treasury", - "outputs": [{ "internalType": "address", "name": "", "type": "address" }], - "stateMutability": "view", - "type": "function" + "inputs": [], + "outputs": [ + { + "name": "", + "type": "address", + "internalType": "address" + } + ], + "stateMutability": "view" }, { - "inputs": [{ "internalType": "address", "name": "vault", "type": "address" }], + "type": "function", "name": "unpauseVault", + "inputs": [ + { + "name": "vault", + "type": "address", + "internalType": "address" + } + ], "outputs": [], - "stateMutability": "nonpayable", - "type": "function" + "stateMutability": "nonpayable" }, { - "inputs": [], + "type": "function", "name": "vaultImplementation", - "outputs": [{ "internalType": "address", "name": "", "type": "address" }], - "stateMutability": "view", - "type": "function" + "inputs": [], + "outputs": [ + { + "name": "", + "type": "address", + "internalType": "address" + } + ], + "stateMutability": "view" + }, + { + "type": "event", + "name": "ComptrollerUpdated", + "inputs": [ + { + "name": "oldComptroller", + "type": "address", + "indexed": true, + "internalType": "address" + }, + { + "name": "newComptroller", + "type": "address", + "indexed": true, + "internalType": "address" + } + ], + "anonymous": false + }, + { + "type": "event", + "name": "Initialized", + "inputs": [ + { + "name": "version", + "type": "uint8", + "indexed": false, + "internalType": "uint8" + } + ], + "anonymous": false + }, + { + "type": "event", + "name": "LatePenaltyRateUpdated", + "inputs": [ + { + "name": "vault", + "type": "address", + "indexed": true, + "internalType": "address" + }, + { + "name": "newRate", + "type": "uint256", + "indexed": false, + "internalType": "uint256" + } + ], + "anonymous": false + }, + { + "type": "event", + "name": "LiquidationAdapterUpdated", + "inputs": [ + { + "name": "oldAdapter", + "type": "address", + "indexed": true, + "internalType": "address" + }, + { + "name": "newAdapter", + "type": "address", + "indexed": true, + "internalType": "address" + } + ], + "anonymous": false + }, + { + "type": "event", + "name": "LiquidationIncentiveUpdated", + "inputs": [ + { + "name": "vault", + "type": "address", + "indexed": true, + "internalType": "address" + }, + { + "name": "newLI", + "type": "uint256", + "indexed": false, + "internalType": "uint256" + } + ], + "anonymous": false + }, + { + "type": "event", + "name": "LiquidationThresholdUpdated", + "inputs": [ + { + "name": "vault", + "type": "address", + "indexed": true, + "internalType": "address" + }, + { + "name": "newLT", + "type": "uint256", + "indexed": false, + "internalType": "uint256" + } + ], + "anonymous": false + }, + { + "type": "event", + "name": "NewAccessControlManager", + "inputs": [ + { + "name": "oldAccessControlManager", + "type": "address", + "indexed": false, + "internalType": "address" + }, + { + "name": "newAccessControlManager", + "type": "address", + "indexed": false, + "internalType": "address" + } + ], + "anonymous": false + }, + { + "type": "event", + "name": "OracleUpdated", + "inputs": [ + { + "name": "oldOracle", + "type": "address", + "indexed": true, + "internalType": "address" + }, + { + "name": "newOracle", + "type": "address", + "indexed": true, + "internalType": "address" + } + ], + "anonymous": false + }, + { + "type": "event", + "name": "OwnershipTransferStarted", + "inputs": [ + { + "name": "previousOwner", + "type": "address", + "indexed": true, + "internalType": "address" + }, + { + "name": "newOwner", + "type": "address", + "indexed": true, + "internalType": "address" + } + ], + "anonymous": false + }, + { + "type": "event", + "name": "OwnershipTransferred", + "inputs": [ + { + "name": "previousOwner", + "type": "address", + "indexed": true, + "internalType": "address" + }, + { + "name": "newOwner", + "type": "address", + "indexed": true, + "internalType": "address" + } + ], + "anonymous": false + }, + { + "type": "event", + "name": "ProtocolShareReserveUpdated", + "inputs": [ + { + "name": "oldPSR", + "type": "address", + "indexed": true, + "internalType": "address" + }, + { + "name": "newPSR", + "type": "address", + "indexed": true, + "internalType": "address" + } + ], + "anonymous": false + }, + { + "type": "event", + "name": "TreasuryUpdated", + "inputs": [ + { + "name": "oldTreasury", + "type": "address", + "indexed": true, + "internalType": "address" + }, + { + "name": "newTreasury", + "type": "address", + "indexed": true, + "internalType": "address" + } + ], + "anonymous": false + }, + { + "type": "event", + "name": "VaultCreated", + "inputs": [ + { + "name": "vault", + "type": "address", + "indexed": true, + "internalType": "address" + }, + { + "name": "institution", + "type": "address", + "indexed": true, + "internalType": "address" + } + ], + "anonymous": false + }, + { + "type": "event", + "name": "VaultImplementationUpdated", + "inputs": [ + { + "name": "oldImpl", + "type": "address", + "indexed": true, + "internalType": "address" + }, + { + "name": "newImpl", + "type": "address", + "indexed": true, + "internalType": "address" + } + ], + "anonymous": false + }, + { + "type": "error", + "name": "InvalidAddress", + "inputs": [] + }, + { + "type": "error", + "name": "InvalidConfig", + "inputs": [] + }, + { + "type": "error", + "name": "InvalidLatePenaltyRate", + "inputs": [] + }, + { + "type": "error", + "name": "InvalidLiquidationIncentive", + "inputs": [] + }, + { + "type": "error", + "name": "InvalidLiquidationThreshold", + "inputs": [] + }, + { + "type": "error", + "name": "OwnershipCannotBeRenounced", + "inputs": [] + }, + { + "type": "error", + "name": "Unauthorized", + "inputs": [ + { + "name": "sender", + "type": "address", + "internalType": "address" + }, + { + "name": "calledContract", + "type": "address", + "internalType": "address" + }, + { + "name": "methodSignature", + "type": "string", + "internalType": "string" + } + ] + }, + { + "type": "error", + "name": "VaultNotRegistered", + "inputs": [] } ] diff --git a/simulations/vip-664/abi/LiquidationAdapter.json b/simulations/vip-664/abi/LiquidationAdapter.json index 41a094a5b..c57002d5e 100644 --- a/simulations/vip-664/abi/LiquidationAdapter.json +++ b/simulations/vip-664/abi/LiquidationAdapter.json @@ -1,254 +1,585 @@ [ { + "type": "constructor", "inputs": [], - "name": "acceptOwnership", - "outputs": [], - "stateMutability": "nonpayable", - "type": "function" - }, - { - "inputs": [], - "name": "owner", - "outputs": [{ "internalType": "address", "name": "", "type": "address" }], - "stateMutability": "view", - "type": "function" + "stateMutability": "nonpayable" }, { + "type": "function", + "name": "MANTISSA_ONE", "inputs": [], - "name": "pendingOwner", - "outputs": [{ "internalType": "address", "name": "", "type": "address" }], - "stateMutability": "view", - "type": "function" + "outputs": [ + { + "name": "", + "type": "uint256", + "internalType": "uint256" + } + ], + "stateMutability": "view" }, { + "type": "function", + "name": "acceptOwnership", "inputs": [], - "name": "renounceOwnership", "outputs": [], - "stateMutability": "nonpayable", - "type": "function" + "stateMutability": "nonpayable" }, { - "inputs": [{ "internalType": "address", "name": "newOwner", "type": "address" }], - "name": "transferOwnership", - "outputs": [], - "stateMutability": "nonpayable", - "type": "function" + "type": "function", + "name": "accessControlManager", + "inputs": [], + "outputs": [ + { + "name": "", + "type": "address", + "internalType": "contract IAccessControlManagerV8" + } + ], + "stateMutability": "view" }, { + "type": "function", + "name": "closeFactor", "inputs": [], - "name": "accessControlManager", - "outputs": [{ "internalType": "contract IAccessControlManagerV8", "name": "", "type": "address" }], - "stateMutability": "view", - "type": "function" + "outputs": [ + { + "name": "", + "type": "uint256", + "internalType": "uint256" + } + ], + "stateMutability": "view" }, { - "inputs": [{ "internalType": "address", "name": "accessControlManager_", "type": "address" }], - "name": "setAccessControlManager", + "type": "function", + "name": "initialize", + "inputs": [ + { + "name": "vaultController_", + "type": "address", + "internalType": "address" + }, + { + "name": "protocolLiquidationShare_", + "type": "uint256", + "internalType": "uint256" + }, + { + "name": "closeFactor_", + "type": "uint256", + "internalType": "uint256" + }, + { + "name": "acm_", + "type": "address", + "internalType": "address" + } + ], "outputs": [], - "stateMutability": "nonpayable", - "type": "function" + "stateMutability": "nonpayable" }, { - "inputs": [], - "name": "MANTISSA_ONE", - "outputs": [{ "internalType": "uint256", "name": "", "type": "uint256" }], - "stateMutability": "view", - "type": "function" + "type": "function", + "name": "isWhitelistedLiquidator", + "inputs": [ + { + "name": "", + "type": "address", + "internalType": "address" + } + ], + "outputs": [ + { + "name": "", + "type": "bool", + "internalType": "bool" + } + ], + "stateMutability": "view" }, { - "inputs": [], - "name": "vaultController", - "outputs": [{ "internalType": "address", "name": "", "type": "address" }], - "stateMutability": "view", - "type": "function" + "type": "function", + "name": "isWhitelistedSettler", + "inputs": [ + { + "name": "", + "type": "address", + "internalType": "address" + } + ], + "outputs": [ + { + "name": "", + "type": "bool", + "internalType": "bool" + } + ], + "stateMutability": "view" }, { - "inputs": [{ "internalType": "address", "name": "", "type": "address" }], - "name": "isWhitelistedLiquidator", - "outputs": [{ "internalType": "bool", "name": "", "type": "bool" }], - "stateMutability": "view", - "type": "function" + "type": "function", + "name": "liquidate", + "inputs": [ + { + "name": "vault", + "type": "address", + "internalType": "address" + }, + { + "name": "repayAmount", + "type": "uint256", + "internalType": "uint256" + } + ], + "outputs": [], + "stateMutability": "nonpayable" }, { - "inputs": [{ "internalType": "address", "name": "", "type": "address" }], - "name": "isWhitelistedSettler", - "outputs": [{ "internalType": "bool", "name": "", "type": "bool" }], - "stateMutability": "view", - "type": "function" + "type": "function", + "name": "liquidateOverdueVault", + "inputs": [ + { + "name": "vault", + "type": "address", + "internalType": "address" + }, + { + "name": "repayAmount", + "type": "uint256", + "internalType": "uint256" + } + ], + "outputs": [], + "stateMutability": "nonpayable" }, { + "type": "function", + "name": "owner", "inputs": [], - "name": "protocolLiquidationShare", - "outputs": [{ "internalType": "uint256", "name": "", "type": "uint256" }], - "stateMutability": "view", - "type": "function" + "outputs": [ + { + "name": "", + "type": "address", + "internalType": "address" + } + ], + "stateMutability": "view" }, { + "type": "function", + "name": "pendingOwner", "inputs": [], - "name": "closeFactor", - "outputs": [{ "internalType": "uint256", "name": "", "type": "uint256" }], - "stateMutability": "view", - "type": "function" + "outputs": [ + { + "name": "", + "type": "address", + "internalType": "address" + } + ], + "stateMutability": "view" + }, + { + "type": "function", + "name": "protocolLiquidationShare", + "inputs": [], + "outputs": [ + { + "name": "", + "type": "uint256", + "internalType": "uint256" + } + ], + "stateMutability": "view" }, { - "inputs": [{ "internalType": "address", "name": "", "type": "address" }], + "type": "function", "name": "protocolShareAccrued", - "outputs": [{ "internalType": "uint256", "name": "", "type": "uint256" }], - "stateMutability": "view", - "type": "function" + "inputs": [ + { + "name": "", + "type": "address", + "internalType": "address" + } + ], + "outputs": [ + { + "name": "", + "type": "uint256", + "internalType": "uint256" + } + ], + "stateMutability": "view" + }, + { + "type": "function", + "name": "renounceOwnership", + "inputs": [], + "outputs": [], + "stateMutability": "pure" }, { + "type": "function", + "name": "setAccessControlManager", "inputs": [ - { "internalType": "address", "name": "vaultController_", "type": "address" }, - { "internalType": "uint256", "name": "protocolLiquidationShare_", "type": "uint256" }, - { "internalType": "uint256", "name": "closeFactor_", "type": "uint256" }, - { "internalType": "address", "name": "acm_", "type": "address" } + { + "name": "accessControlManager_", + "type": "address", + "internalType": "address" + } ], - "name": "initialize", "outputs": [], - "stateMutability": "nonpayable", - "type": "function" + "stateMutability": "nonpayable" }, { + "type": "function", + "name": "setCloseFactor", "inputs": [ - { "internalType": "address", "name": "liquidator", "type": "address" }, - { "internalType": "bool", "name": "approved", "type": "bool" } + { + "name": "newCF", + "type": "uint256", + "internalType": "uint256" + } ], - "name": "setLiquidatorWhitelist", "outputs": [], - "stateMutability": "nonpayable", - "type": "function" + "stateMutability": "nonpayable" }, { + "type": "function", + "name": "setLiquidatorWhitelist", "inputs": [ - { "internalType": "address", "name": "settler", "type": "address" }, - { "internalType": "bool", "name": "approved", "type": "bool" } + { + "name": "liquidator", + "type": "address", + "internalType": "address" + }, + { + "name": "approved", + "type": "bool", + "internalType": "bool" + } ], - "name": "setSettlerWhitelist", "outputs": [], - "stateMutability": "nonpayable", - "type": "function" + "stateMutability": "nonpayable" }, { - "inputs": [{ "internalType": "uint256", "name": "share", "type": "uint256" }], + "type": "function", "name": "setProtocolLiquidationShare", + "inputs": [ + { + "name": "share", + "type": "uint256", + "internalType": "uint256" + } + ], "outputs": [], - "stateMutability": "nonpayable", - "type": "function" + "stateMutability": "nonpayable" }, { - "inputs": [{ "internalType": "uint256", "name": "newCF", "type": "uint256" }], - "name": "setCloseFactor", + "type": "function", + "name": "setSettlerWhitelist", + "inputs": [ + { + "name": "settler", + "type": "address", + "internalType": "address" + }, + { + "name": "approved", + "type": "bool", + "internalType": "bool" + } + ], "outputs": [], - "stateMutability": "nonpayable", - "type": "function" + "stateMutability": "nonpayable" }, { - "inputs": [{ "internalType": "address", "name": "collateral", "type": "address" }], + "type": "function", "name": "sweepProtocolShareToReserve", + "inputs": [ + { + "name": "collateral", + "type": "address", + "internalType": "address" + } + ], "outputs": [], - "stateMutability": "nonpayable", - "type": "function" + "stateMutability": "nonpayable" }, { + "type": "function", + "name": "transferOwnership", "inputs": [ - { "internalType": "address", "name": "vault", "type": "address" }, - { "internalType": "uint256", "name": "repayAmount", "type": "uint256" } + { + "name": "newOwner", + "type": "address", + "internalType": "address" + } ], - "name": "liquidate", "outputs": [], - "stateMutability": "nonpayable", - "type": "function" + "stateMutability": "nonpayable" + }, + { + "type": "function", + "name": "vaultController", + "inputs": [], + "outputs": [ + { + "name": "", + "type": "address", + "internalType": "address" + } + ], + "stateMutability": "view" }, { + "type": "event", + "name": "CloseFactorUpdated", "inputs": [ - { "internalType": "address", "name": "vault", "type": "address" }, - { "internalType": "uint256", "name": "repayAmount", "type": "uint256" } + { + "name": "oldCloseFactor", + "type": "uint256", + "indexed": false, + "internalType": "uint256" + }, + { + "name": "newCloseFactor", + "type": "uint256", + "indexed": false, + "internalType": "uint256" + } ], - "name": "liquidateOverdueVault", - "outputs": [], - "stateMutability": "nonpayable", - "type": "function" + "anonymous": false }, { - "anonymous": false, + "type": "event", + "name": "Initialized", "inputs": [ - { "indexed": true, "internalType": "address", "name": "liquidator", "type": "address" }, - { "indexed": false, "internalType": "bool", "name": "approved", "type": "bool" } + { + "name": "version", + "type": "uint8", + "indexed": false, + "internalType": "uint8" + } ], - "name": "LiquidatorWhitelistUpdated", - "type": "event" + "anonymous": false }, { - "anonymous": false, + "type": "event", + "name": "LiquidationCollateralSplit", "inputs": [ - { "indexed": true, "internalType": "address", "name": "settler", "type": "address" }, - { "indexed": false, "internalType": "bool", "name": "approved", "type": "bool" } + { + "name": "totalSeized", + "type": "uint256", + "indexed": false, + "internalType": "uint256" + }, + { + "name": "protocolAmount", + "type": "uint256", + "indexed": false, + "internalType": "uint256" + }, + { + "name": "callerAmount", + "type": "uint256", + "indexed": false, + "internalType": "uint256" + } ], - "name": "SettlerWhitelistUpdated", - "type": "event" + "anonymous": false }, { - "anonymous": false, + "type": "event", + "name": "LiquidatorWhitelistUpdated", "inputs": [ - { "indexed": false, "internalType": "uint256", "name": "oldShare", "type": "uint256" }, - { "indexed": false, "internalType": "uint256", "name": "newShare", "type": "uint256" } + { + "name": "liquidator", + "type": "address", + "indexed": true, + "internalType": "address" + }, + { + "name": "approved", + "type": "bool", + "indexed": false, + "internalType": "bool" + } ], - "name": "ProtocolLiquidationShareUpdated", - "type": "event" + "anonymous": false }, { - "anonymous": false, + "type": "event", + "name": "NewAccessControlManager", "inputs": [ - { "indexed": false, "internalType": "uint256", "name": "oldCloseFactor", "type": "uint256" }, - { "indexed": false, "internalType": "uint256", "name": "newCloseFactor", "type": "uint256" } + { + "name": "oldAccessControlManager", + "type": "address", + "indexed": false, + "internalType": "address" + }, + { + "name": "newAccessControlManager", + "type": "address", + "indexed": false, + "internalType": "address" + } ], - "name": "CloseFactorUpdated", - "type": "event" + "anonymous": false }, { - "anonymous": false, + "type": "event", + "name": "OwnershipTransferStarted", "inputs": [ - { "indexed": false, "internalType": "uint256", "name": "totalSeized", "type": "uint256" }, - { "indexed": false, "internalType": "uint256", "name": "protocolAmount", "type": "uint256" }, - { "indexed": false, "internalType": "uint256", "name": "callerAmount", "type": "uint256" } + { + "name": "previousOwner", + "type": "address", + "indexed": true, + "internalType": "address" + }, + { + "name": "newOwner", + "type": "address", + "indexed": true, + "internalType": "address" + } ], - "name": "LiquidationCollateralSplit", - "type": "event" + "anonymous": false }, { - "anonymous": false, + "type": "event", + "name": "OwnershipTransferred", "inputs": [ - { "indexed": true, "internalType": "address", "name": "collateral", "type": "address" }, - { "indexed": false, "internalType": "uint256", "name": "amount", "type": "uint256" } + { + "name": "previousOwner", + "type": "address", + "indexed": true, + "internalType": "address" + }, + { + "name": "newOwner", + "type": "address", + "indexed": true, + "internalType": "address" + } ], - "name": "ProtocolShareSweptToReserve", - "type": "event" + "anonymous": false }, { - "anonymous": false, + "type": "event", + "name": "ProtocolLiquidationShareUpdated", "inputs": [ - { "indexed": true, "internalType": "address", "name": "previousOwner", "type": "address" }, - { "indexed": true, "internalType": "address", "name": "newOwner", "type": "address" } + { + "name": "oldShare", + "type": "uint256", + "indexed": false, + "internalType": "uint256" + }, + { + "name": "newShare", + "type": "uint256", + "indexed": false, + "internalType": "uint256" + } ], - "name": "OwnershipTransferred", - "type": "event" + "anonymous": false }, { - "anonymous": false, + "type": "event", + "name": "ProtocolShareSweptToReserve", "inputs": [ - { "indexed": true, "internalType": "address", "name": "previousOwner", "type": "address" }, - { "indexed": true, "internalType": "address", "name": "newOwner", "type": "address" } + { + "name": "collateral", + "type": "address", + "indexed": true, + "internalType": "address" + }, + { + "name": "amount", + "type": "uint256", + "indexed": false, + "internalType": "uint256" + } ], - "name": "OwnershipTransferStarted", - "type": "event" - }, - { "inputs": [], "name": "NotWhitelistedLiquidator", "type": "error" }, - { "inputs": [], "name": "NotWhitelistedSettler", "type": "error" }, - { "inputs": [], "name": "VaultNotRegistered", "type": "error" }, - { "inputs": [], "name": "InvalidShare", "type": "error" }, - { "inputs": [], "name": "InvalidCloseFactor", "type": "error" }, - { "inputs": [], "name": "InvalidAddress", "type": "error" }, - { "inputs": [], "name": "ZeroRepayAmount", "type": "error" }, - { "inputs": [], "name": "OwnershipCannotBeRenounced", "type": "error" } + "anonymous": false + }, + { + "type": "event", + "name": "SettlerWhitelistUpdated", + "inputs": [ + { + "name": "settler", + "type": "address", + "indexed": true, + "internalType": "address" + }, + { + "name": "approved", + "type": "bool", + "indexed": false, + "internalType": "bool" + } + ], + "anonymous": false + }, + { + "type": "error", + "name": "InvalidAddress", + "inputs": [] + }, + { + "type": "error", + "name": "InvalidCloseFactor", + "inputs": [] + }, + { + "type": "error", + "name": "InvalidShare", + "inputs": [] + }, + { + "type": "error", + "name": "NotWhitelistedLiquidator", + "inputs": [] + }, + { + "type": "error", + "name": "NotWhitelistedSettler", + "inputs": [] + }, + { + "type": "error", + "name": "OwnershipCannotBeRenounced", + "inputs": [] + }, + { + "type": "error", + "name": "Unauthorized", + "inputs": [ + { + "name": "sender", + "type": "address", + "internalType": "address" + }, + { + "name": "calledContract", + "type": "address", + "internalType": "address" + }, + { + "name": "methodSignature", + "type": "string", + "internalType": "string" + } + ] + }, + { + "type": "error", + "name": "VaultNotRegistered", + "inputs": [] + }, + { + "type": "error", + "name": "ZeroRepayAmount", + "inputs": [] + } ] From d8afcb71c8a80ca6e46197913c3015707c8ee7dc Mon Sep 17 00:00:00 2001 From: Fred Date: Thu, 28 May 2026 02:42:37 +0000 Subject: [PATCH 09/10] test(vip-664): verify createVault success path MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Existing 'CriticalGuardian should be able to create vault' test only asserts that the call doesn't revert with Unauthorized — but the test config has marginRate=1.5e18 and fixedAPY=5e16, both of which violate _validateVaultConfig invariants, so the call always reverts with InvalidConfig and the success path is never exercised. Add a parallel test that: - uses valid config (marginRate=0.5e18, fixedAPY=500 bps) - deploys a stub oracle returning 1e18 and swaps it in via NT (since the forked ResilientOracle's chainlink feeds go stale after testVip advances time past the timelock delay) - asserts VaultCreated is emitted, allVaultsLength increments, and the new vault is registered --- simulations/vip-664/bscmainnet.ts | 62 +++++++++++++++++++++++++++++++ 1 file changed, 62 insertions(+) diff --git a/simulations/vip-664/bscmainnet.ts b/simulations/vip-664/bscmainnet.ts index a731a6d3c..721114d3e 100644 --- a/simulations/vip-664/bscmainnet.ts +++ b/simulations/vip-664/bscmainnet.ts @@ -244,5 +244,67 @@ forking(FORK_BLOCK, async () => { .createVault(vaultConfig, instConfig, riskConfig, "Test", "TEST"); await expect(call).to.not.be.revertedWithCustomError(controller, "Unauthorized"); }); + + it("CriticalGuardian should successfully create a vault and register it", async () => { + // testVip advances time past the timelock delay, which makes the real ResilientOracle's + // underlying chainlink feeds stale. Deploy a minimal stub oracle that always returns 1e18 + // and swap it in via NT (which now holds setOracle on the controller). + // + // Stub source (compiled with solc 0.8.25, optimizer off): + // contract StubOracle { + // function getPrice(address) external pure returns (uint256) { return 1e18; } + // } + const STUB_ORACLE_BYTECODE = + "0x6080604052348015600e575f80fd5b5061015e8061001c5f395ff3fe608060405234801561000f575f80fd5b5060043610610029575f3560e01c806341976e091461002d575b5f80fd5b610047600480360381019061004291906100cc565b61005d565b604051610054919061010f565b60405180910390f35b5f670de0b6b3a76400009050919050565b5f80fd5b5f73ffffffffffffffffffffffffffffffffffffffff82169050919050565b5f61009b82610072565b9050919050565b6100ab81610091565b81146100b5575f80fd5b50565b5f813590506100c6816100a2565b92915050565b5f602082840312156100e1576100e061006e565b5b5f6100ee848285016100b8565b91505092915050565b5f819050919050565b610109816100f7565b82525050565b5f6020820190506101225f830184610100565b9291505056fea26469706673582212209282f7f2d85233912d0088d6dc45ce2459097d2866597e41f0a286059758c12c64736f6c63430008190033"; + const STUB_ORACLE_ABI = ["function getPrice(address) external pure returns (uint256)"]; + const [deployer] = await ethers.getSigners(); + const stubFactory = new ethers.ContractFactory(STUB_ORACLE_ABI, STUB_ORACLE_BYTECODE, deployer); + const stubOracle = await stubFactory.deploy(); + await stubOracle.deployed(); + + const normalTimelock = await initMainnetUser(NORMAL, ethers.utils.parseEther("1")); + await controller.connect(normalTimelock).setOracle(stubOracle.address); + + const criticalGuardian = await initMainnetUser(CRITICAL_GUARDIAN, ethers.utils.parseEther("1")); + + // Valid config — passes every check in InstitutionalVaultController._validateVaultConfig. + const validVaultConfig = { + supplyAsset: USDT, + fixedAPY: 500, // 5% in basis points (cap is MAX_APY_BPS = 10_000) + reserveFactor: parseEther("0.1"), + minBorrowCap: parseEther("1000"), + maxBorrowCap: parseEther("10000000"), + minSupplierDeposit: parseEther("100"), + openDuration: 7 * ONE_DAY, + lockDuration: 30 * ONE_DAY, + settlementWindow: ONE_DAY, + }; + const validInstConfig = { + collateralAsset: USDC, + idealCollateralAmount: parseEther("100000"), + marginRate: parseEther("0.5"), // 50% — must be ≤ MANTISSA_ONE (1e18) + institutionOperator: CRITICAL_GUARDIAN, + positionTokenId: 0, // overridden by controller with the freshly-minted tokenId + }; + const validRiskConfig = { + liquidationThreshold: parseEther("0.85"), + liquidationIncentive: parseEther("1.08"), + latePenaltyRate: parseEther("1.15"), + }; + + const vaultsBefore = await controller.allVaultsLength(); + + await expect( + controller + .connect(criticalGuardian) + .createVault(validVaultConfig, validInstConfig, validRiskConfig, "Test Vault", "TVAULT"), + ).to.emit(controller, "VaultCreated"); + + const vaultsAfter = await controller.allVaultsLength(); + expect(vaultsAfter).to.equal(vaultsBefore.add(1)); + + const newVault = await controller.allVaults(vaultsAfter.sub(1)); + expect(await controller.isRegistered(newVault)).to.be.true; + }); }); }); From 224e970adf932535f2102368d05dfaf44b51bf8f Mon Sep 17 00:00:00 2001 From: Fred Date: Thu, 28 May 2026 03:39:49 +0000 Subject: [PATCH 10/10] chore: prepare VIP-627 for proposal --- .../abi/ACMAggregator.json | 0 .../abi/AccessControlManager.json | 0 .../abi/InstitutionPositionToken.json | 0 .../abi/InstitutionalLoanVault.json | 0 .../abi/InstitutionalVaultController.json | 0 .../abi/LiquidationAdapter.json | 0 .../{vip-664 => vip-627}/abi/ProxyAdmin.json | 0 .../{vip-664 => vip-627}/bscmainnet.ts | 6 +- .../bsctestnet-addendum.ts | 2 +- .../{vip-664 => vip-627}/bsctestnet.ts | 6 +- .../addGrantPermissions.ts | 0 vips/{vip-664 => vip-627}/bscmainnet.ts | 62 ++++++++++++++----- .../bsctestnet-addendum.ts | 0 vips/{vip-664 => vip-627}/bsctestnet.ts | 6 +- 14 files changed, 55 insertions(+), 27 deletions(-) rename simulations/{vip-664 => vip-627}/abi/ACMAggregator.json (100%) rename simulations/{vip-664 => vip-627}/abi/AccessControlManager.json (100%) rename simulations/{vip-664 => vip-627}/abi/InstitutionPositionToken.json (100%) rename simulations/{vip-664 => vip-627}/abi/InstitutionalLoanVault.json (100%) rename simulations/{vip-664 => vip-627}/abi/InstitutionalVaultController.json (100%) rename simulations/{vip-664 => vip-627}/abi/LiquidationAdapter.json (100%) rename simulations/{vip-664 => vip-627}/abi/ProxyAdmin.json (100%) rename simulations/{vip-664 => vip-627}/bscmainnet.ts (98%) rename simulations/{vip-664 => vip-627}/bsctestnet-addendum.ts (99%) rename simulations/{vip-664 => vip-627}/bsctestnet.ts (98%) rename vips/{vip-664 => vip-627}/addGrantPermissions.ts (100%) rename vips/{vip-664 => vip-627}/bscmainnet.ts (64%) rename vips/{vip-664 => vip-627}/bsctestnet-addendum.ts (100%) rename vips/{vip-664 => vip-627}/bsctestnet.ts (99%) diff --git a/simulations/vip-664/abi/ACMAggregator.json b/simulations/vip-627/abi/ACMAggregator.json similarity index 100% rename from simulations/vip-664/abi/ACMAggregator.json rename to simulations/vip-627/abi/ACMAggregator.json diff --git a/simulations/vip-664/abi/AccessControlManager.json b/simulations/vip-627/abi/AccessControlManager.json similarity index 100% rename from simulations/vip-664/abi/AccessControlManager.json rename to simulations/vip-627/abi/AccessControlManager.json diff --git a/simulations/vip-664/abi/InstitutionPositionToken.json b/simulations/vip-627/abi/InstitutionPositionToken.json similarity index 100% rename from simulations/vip-664/abi/InstitutionPositionToken.json rename to simulations/vip-627/abi/InstitutionPositionToken.json diff --git a/simulations/vip-664/abi/InstitutionalLoanVault.json b/simulations/vip-627/abi/InstitutionalLoanVault.json similarity index 100% rename from simulations/vip-664/abi/InstitutionalLoanVault.json rename to simulations/vip-627/abi/InstitutionalLoanVault.json diff --git a/simulations/vip-664/abi/InstitutionalVaultController.json b/simulations/vip-627/abi/InstitutionalVaultController.json similarity index 100% rename from simulations/vip-664/abi/InstitutionalVaultController.json rename to simulations/vip-627/abi/InstitutionalVaultController.json diff --git a/simulations/vip-664/abi/LiquidationAdapter.json b/simulations/vip-627/abi/LiquidationAdapter.json similarity index 100% rename from simulations/vip-664/abi/LiquidationAdapter.json rename to simulations/vip-627/abi/LiquidationAdapter.json diff --git a/simulations/vip-664/abi/ProxyAdmin.json b/simulations/vip-627/abi/ProxyAdmin.json similarity index 100% rename from simulations/vip-664/abi/ProxyAdmin.json rename to simulations/vip-627/abi/ProxyAdmin.json diff --git a/simulations/vip-664/bscmainnet.ts b/simulations/vip-627/bscmainnet.ts similarity index 98% rename from simulations/vip-664/bscmainnet.ts rename to simulations/vip-627/bscmainnet.ts index 721114d3e..d996cc59e 100644 --- a/simulations/vip-664/bscmainnet.ts +++ b/simulations/vip-627/bscmainnet.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 vip664, { +import vip627, { ACM_AGGREGATOR, ACM_AGGREGATOR_INDEX, DEFAULT_ADMIN_ROLE, @@ -21,7 +21,7 @@ import vip664, { PROTOCOL_SHARE_RESERVE, PROXY_ADMIN, SETTLER_WHITELIST, -} from "../../vips/vip-664/bscmainnet"; +} from "../../vips/vip-627/bscmainnet"; import ACM_AGGREGATOR_ABI from "./abi/ACMAggregator.json"; import ACCESS_CONTROL_MANAGER_ABI from "./abi/AccessControlManager.json"; import INSTITUTION_POSITION_TOKEN_ABI from "./abi/InstitutionPositionToken.json"; @@ -133,7 +133,7 @@ forking(FORK_BLOCK, async () => { } }); - testVip("VIP-664 [BNB Chain] Configure Institutional Fixed Rate Vault System", await vip664(), { + testVip("VIP-627 [BNB Chain] Configure Institutional Fixed Rate Vault System", await vip627(), { supporter: SUPPORTER, callbackAfterExecution: async txResponse => { await expectEvents( diff --git a/simulations/vip-664/bsctestnet-addendum.ts b/simulations/vip-627/bsctestnet-addendum.ts similarity index 99% rename from simulations/vip-664/bsctestnet-addendum.ts rename to simulations/vip-627/bsctestnet-addendum.ts index 28b4c30a1..e03458d7d 100644 --- a/simulations/vip-664/bsctestnet-addendum.ts +++ b/simulations/vip-627/bsctestnet-addendum.ts @@ -21,7 +21,7 @@ import vip664TestnetAddendum, { PROTOCOL_SHARE_RESERVE, PROXY_ADMIN, SETTLER_WHITELIST, -} from "../../vips/vip-664/bsctestnet-addendum"; +} from "../../vips/vip-627/bsctestnet-addendum"; import ACM_AGGREGATOR_ABI from "./abi/ACMAggregator.json"; import ACCESS_CONTROL_MANAGER_ABI from "./abi/AccessControlManager.json"; import INSTITUTION_POSITION_TOKEN_ABI from "./abi/InstitutionPositionToken.json"; diff --git a/simulations/vip-664/bsctestnet.ts b/simulations/vip-627/bsctestnet.ts similarity index 98% rename from simulations/vip-664/bsctestnet.ts rename to simulations/vip-627/bsctestnet.ts index 9eb5d5841..4dfea73a9 100644 --- a/simulations/vip-664/bsctestnet.ts +++ b/simulations/vip-627/bsctestnet.ts @@ -6,7 +6,7 @@ import { NETWORK_ADDRESSES } from "src/networkAddresses"; import { expectEvents } from "src/utils"; import { forking, testVip } from "src/vip-framework"; -import vip664, { +import vip627, { ACM_AGGREGATOR, ACM_AGGREGATOR_INDEX, ADAPTER_FUNCTIONS, @@ -19,7 +19,7 @@ import vip664, { INSTITUTION_POSITION_TOKEN, LIQUIDATION_ADAPTER, PERMISSIONS, -} from "../../vips/vip-664/bsctestnet"; +} from "../../vips/vip-627/bsctestnet"; import ACM_AGGREGATOR_ABI from "./abi/ACMAggregator.json"; import ACCESS_CONTROL_MANAGER_ABI from "./abi/AccessControlManager.json"; import INSTITUTION_POSITION_TOKEN_ABI from "./abi/InstitutionPositionToken.json"; @@ -139,7 +139,7 @@ forking(FORK_BLOCK, async () => { // ────────────────────────────────────────────────────────────────────── // VIP execution // ────────────────────────────────────────────────────────────────────── - testVip("VIP-664 [BNB Chain Testnet] Configure Institutional Fixed Rate Vault System", await vip664(), { + testVip("VIP-627 [BNB Chain Testnet] Configure Institutional Fixed Rate Vault System", await vip627(), { callbackAfterExecution: async txResponse => { await expectEvents( txResponse, diff --git a/vips/vip-664/addGrantPermissions.ts b/vips/vip-627/addGrantPermissions.ts similarity index 100% rename from vips/vip-664/addGrantPermissions.ts rename to vips/vip-627/addGrantPermissions.ts diff --git a/vips/vip-664/bscmainnet.ts b/vips/vip-627/bscmainnet.ts similarity index 64% rename from vips/vip-664/bscmainnet.ts rename to vips/vip-627/bscmainnet.ts index 50f5aa92e..bbd3cedfb 100644 --- a/vips/vip-664/bscmainnet.ts +++ b/vips/vip-627/bscmainnet.ts @@ -150,29 +150,57 @@ export const PERMISSIONS: [string, string, string][] = buildPermissions(PERMISSI export const EXPECTED_PERMISSION_GRANTED_EVENTS = PERMISSIONS.length; -export const vip664 = () => { +export const vip627 = () => { const meta = { version: "v1", - title: "VIP-664 [BNB Chain] Configure Institutional Fixed Rate Vault System", + title: "VIP-627 [BNB Chain] Activate Institutional Fixed Rate Vault System", description: `#### Summary -If passed, this VIP will configure the Institutional Fixed Rate Vault system on BNB Chain: +If passed, this VIP activates the Institutional Fixed Rate Vault system on BNB Chain and upgrades the Protocol Share Reserve to support it. The new system introduces fixed-rate, time-bound vaults intended for whitelisted institutional borrowers, with a dedicated liquidation flow separate from the existing core pool. -1. Grant ACM permissions (${EXPECTED_PERMISSION_GRANTED_EVENTS} total) via \`ACMCommandsAggregator\` to the appropriate set of timelocks (Normal, Fast-track, Critical) and the Critical Guardian (which also holds the \`createVault\` permission) for each access-controlled function on \`InstitutionalVaultController\` and \`LiquidationAdapter\`. -2. Accept ownership of \`InstitutionalVaultController\` and \`LiquidationAdapter\` (two-step Ownable2Step transfer initiated in deploy script). -3. Set the \`LiquidationAdapter\` on the controller via \`setLiquidationAdapter()\`. -4. Complete the two-step position token ownership transfer via \`acceptPositionTokenOwnership()\`. -5. Whitelist the Critical Guardian as a liquidator and settler on the \`LiquidationAdapter\`. -6. Upgrade the \`ProtocolShareReserve\` proxy to a new implementation that supports the institutional-vault liquidation income type. +#### Description -#### Deployed Contracts +The proposal performs the following on-chain actions: -- **InstitutionalVaultController** (proxy): ${INSTITUTIONAL_VAULT_CONTROLLER} -- **LiquidationAdapter** (proxy): ${LIQUIDATION_ADAPTER} -- **InstitutionPositionToken**: ${INSTITUTION_POSITION_TOKEN} -- **Critical Guardian**: ${CRITICAL_GUARDIAN} -- **ProtocolShareReserve** (proxy): ${PROTOCOL_SHARE_RESERVE} -- **New ProtocolShareReserve implementation**: ${NEW_PSR_IMPLEMENTATION}`, +**1. Grant ACM permissions via the ACMCommandsAggregator** + +Permissions for the new InstitutionalVaultController and LiquidationAdapter contracts have been pre-loaded off-chain into the ACMCommandsAggregator (index ${ACM_AGGREGATOR_INDEX}). The VIP temporarily grants the aggregator the DEFAULT_ADMIN_ROLE on the AccessControlManager, executes the batch, and revokes the role in the same proposal. This grants role-based access (${EXPECTED_PERMISSION_GRANTED_EVENTS} permissions in total) over all administrative functions of both contracts to the Normal / Fast-track / Critical timelocks and, where appropriate, the Critical Guardian. + +- Vault lifecycle and operations (createVault, openVault, cancelVault, partialPauseVault, completePauseVault, unpauseVault, closeVault, sweep, approvePositionTransfer, revokePositionTransfer) — granted to all three timelocks and the Critical Guardian. +- Risk parameter setters (setLiquidationThreshold, setLiquidationIncentive, setLatePenaltyRate, setVaultImplementation) — granted to the three timelocks only. +- Plumbing setters (setLiquidationAdapter, setOracle, setProtocolShareReserve, setComptroller, setTreasury) — granted to the Normal Timelock and Critical Guardian. +- LiquidationAdapter controls (setLiquidatorWhitelist, setSettlerWhitelist, setProtocolLiquidationShare, setCloseFactor, sweepProtocolShareToReserve) — granted to the timelocks, with the whitelist and sweep functions also granted to the Critical Guardian. + +**2. Complete two-step ownership transfers** + +The InstitutionalVaultController, the LiquidationAdapter, and the InstitutionPositionToken were deployed with their Ownable2Step pending owner set to the Normal Timelock. The VIP calls acceptOwnership() on the controller and adapter, and acceptPositionTokenOwnership() on the controller (which in turn accepts ownership of the position-token NFT). + +**3. Wire the LiquidationAdapter into the controller** + +Call setLiquidationAdapter on the InstitutionalVaultController, pointing it at the newly deployed LiquidationAdapter. + +**4. Whitelist the Critical Guardian as the initial liquidator and settler** + +Whitelist the Critical Guardian on the LiquidationAdapter as both an authorized liquidator and an authorized settler, so that the dedicated liquidation flow can run under operational control while remaining governance-bounded. + +**5. Upgrade the ProtocolShareReserve implementation** + +Upgrade the ProtocolShareReserve proxy to a new implementation that recognises an additional income type produced by institutional-vault liquidations, so that protocol-share income flowing in from the LiquidationAdapter is routed and accounted for correctly. + +#### Security and additional considerations + +- The DEFAULT_ADMIN_ROLE grant to the ACMCommandsAggregator is scoped to this proposal: it is granted, used once to apply a pre-loaded permission batch, and revoked in the same transaction sequence. +- All sensitive risk-parameter changes (liquidation threshold, liquidation incentive, late-penalty rate, vault implementation) are restricted to timelock callers only and are not exposed to the Critical Guardian. +- The Critical Guardian initially fills the liquidator and settler roles on the LiquidationAdapter; additional whitelisted parties can be added later via subsequent VIPs. + +#### Deployed contracts (BNB Chain) + +- InstitutionalVaultController (proxy): ${INSTITUTIONAL_VAULT_CONTROLLER} +- LiquidationAdapter (proxy): ${LIQUIDATION_ADAPTER} +- InstitutionPositionToken: ${INSTITUTION_POSITION_TOKEN} +- ProtocolShareReserve (proxy): ${PROTOCOL_SHARE_RESERVE} +- New ProtocolShareReserve implementation: ${NEW_PSR_IMPLEMENTATION} +- ACMCommandsAggregator: ${ACM_AGGREGATOR} (batch index ${ACM_AGGREGATOR_INDEX})`, 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", @@ -238,4 +266,4 @@ If passed, this VIP will configure the Institutional Fixed Rate Vault system on ); }; -export default vip664; +export default vip627; diff --git a/vips/vip-664/bsctestnet-addendum.ts b/vips/vip-627/bsctestnet-addendum.ts similarity index 100% rename from vips/vip-664/bsctestnet-addendum.ts rename to vips/vip-627/bsctestnet-addendum.ts diff --git a/vips/vip-664/bsctestnet.ts b/vips/vip-627/bsctestnet.ts similarity index 99% rename from vips/vip-664/bsctestnet.ts rename to vips/vip-627/bsctestnet.ts index 5e8d024fe..ac8eabb42 100644 --- a/vips/vip-664/bsctestnet.ts +++ b/vips/vip-627/bsctestnet.ts @@ -105,10 +105,10 @@ export const PERMISSIONS: [string, string, string][] = [ ...ADAPTER_GUARDIAN_FUNCTIONS.map(sig => [LIQUIDATION_ADAPTER, sig, GUARDIAN] as [string, string, string]), ]; -export const vip664 = () => { +export const vip627 = () => { const meta = { version: "v1", - title: "VIP-664 [BNB Chain Testnet] Configure Institutional Fixed Rate Vault System", + title: "VIP-627 [BNB Chain Testnet] Configure Institutional Fixed Rate Vault System", description: `#### Summary If passed, this VIP will configure the Institutional Fixed Rate Vault system on BNB Chain Testnet: @@ -220,4 +220,4 @@ If passed, this VIP will configure the Institutional Fixed Rate Vault system on ); }; -export default vip664; +export default vip627;