From 5c29e549654beefe948691d9a5ba13ddf727f290 Mon Sep 17 00:00:00 2001 From: Debugger022 Date: Thu, 4 Dec 2025 13:13:42 +0530 Subject: [PATCH 1/2] feat: add vip and simulation for liquidation and repay improvements in bsctestnet --- simulations/vip-580/abi/ACM.json | 81 +++ simulations/vip-580/abi/Comptroller.json | 209 ++++++++ .../vip-580/abi/LiquidationManager.json | 466 ++++++++++++++++++ simulations/vip-580/abi/Liquidator.json | 15 + simulations/vip-580/abi/VAIController.json | 53 ++ simulations/vip-580/abi/VBep20Delegate.json | 34 ++ simulations/vip-580/bsctestnet.ts | 346 +++++++++++++ .../vip-580/utils/cut-params-bsctestnet.json | 130 +++++ simulations/vip-580/utils/market.json | 154 ++++++ src/networkAddresses.ts | 1 + vips/vip-580/bsctestnet.ts | 198 ++++++++ 11 files changed, 1687 insertions(+) create mode 100644 simulations/vip-580/abi/ACM.json create mode 100644 simulations/vip-580/abi/Comptroller.json create mode 100644 simulations/vip-580/abi/LiquidationManager.json create mode 100644 simulations/vip-580/abi/Liquidator.json create mode 100644 simulations/vip-580/abi/VAIController.json create mode 100644 simulations/vip-580/abi/VBep20Delegate.json create mode 100644 simulations/vip-580/bsctestnet.ts create mode 100644 simulations/vip-580/utils/cut-params-bsctestnet.json create mode 100644 simulations/vip-580/utils/market.json create mode 100644 vips/vip-580/bsctestnet.ts diff --git a/simulations/vip-580/abi/ACM.json b/simulations/vip-580/abi/ACM.json new file mode 100644 index 000000000..2092ee121 --- /dev/null +++ b/simulations/vip-580/abi/ACM.json @@ -0,0 +1,81 @@ +[ + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "address", + "name": "account", + "type": "address" + }, + { + "indexed": false, + "internalType": "address", + "name": "contractAddress", + "type": "address" + }, + { + "indexed": false, + "internalType": "string", + "name": "functionSig", + "type": "string" + } + ], + "name": "PermissionGranted", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "address", + "name": "account", + "type": "address" + }, + { + "indexed": false, + "internalType": "address", + "name": "contractAddress", + "type": "address" + }, + { + "indexed": false, + "internalType": "string", + "name": "functionSig", + "type": "string" + } + ], + "name": "PermissionRevoked", + "type": "event" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "account", + "type": "address" + }, + { + "internalType": "address", + "name": "contractAddress", + "type": "address" + }, + { + "internalType": "string", + "name": "functionSig", + "type": "string" + } + ], + "name": "hasPermission", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "view", + "type": "function" + } +] diff --git a/simulations/vip-580/abi/Comptroller.json b/simulations/vip-580/abi/Comptroller.json new file mode 100644 index 000000000..e55f5b415 --- /dev/null +++ b/simulations/vip-580/abi/Comptroller.json @@ -0,0 +1,209 @@ +[ + { + "inputs": [], + "name": "comptrollerImplementation", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "comptrollerLens", + "outputs": [ + { + "internalType": "contract ComptrollerLensInterface", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "liquidationManager", + "outputs": [ + { + "internalType": "contract LiquidationManager", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "facetAddresses", + "outputs": [ + { + "internalType": "address[]", + "name": "", + "type": "address[]" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "facet", + "type": "address" + } + ], + "name": "facetFunctionSelectors", + "outputs": [ + { + "internalType": "bytes4[]", + "name": "", + "type": "bytes4[]" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "name": "markets", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + }, + { + "internalType": "uint256", + "name": "", + "type": "uint256" + }, + { + "internalType": "bool", + "name": "", + "type": "bool" + }, + { + "internalType": "uint256", + "name": "", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "", + "type": "uint256" + }, + { + "internalType": "uint96", + "name": "", + "type": "uint96" + }, + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "anonymous": false, + "inputs": [ + { + "components": [ + { + "internalType": "address", + "name": "facetAddress", + "type": "address" + }, + { + "internalType": "enum IDiamondCut.FacetCutAction", + "name": "action", + "type": "uint8" + }, + { + "internalType": "bytes4[]", + "name": "functionSelectors", + "type": "bytes4[]" + } + ], + "indexed": false, + "internalType": "struct IDiamondCut.FacetCut[]", + "name": "_diamondCut", + "type": "tuple[]" + } + ], + "name": "DiamondCut", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "address", + "name": "oldImplementation", + "type": "address" + }, + { + "indexed": false, + "internalType": "address", + "name": "newImplementation", + "type": "address" + } + ], + "name": "NewImplementation", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "address", + "name": "oldPendingImplementation", + "type": "address" + }, + { + "indexed": false, + "internalType": "address", + "name": "newPendingImplementation", + "type": "address" + } + ], + "name": "NewPendingImplementation", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "address", + "name": "oldLiquidationManager", + "type": "address" + }, + { + "indexed": false, + "internalType": "address", + "name": "newLiquidationManager", + "type": "address" + } + ], + "name": "NewLiquidationManager", + "type": "event" + } +] diff --git a/simulations/vip-580/abi/LiquidationManager.json b/simulations/vip-580/abi/LiquidationManager.json new file mode 100644 index 000000000..5ffb01dcc --- /dev/null +++ b/simulations/vip-580/abi/LiquidationManager.json @@ -0,0 +1,466 @@ +[ + { + "inputs": [ + { + "internalType": "uint256", + "name": "baseCloseFactorMantissa_", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "defaultCloseFactorMantissa_", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "targetHealthFactor_", + "type": "uint256" + } + ], + "stateMutability": "nonpayable", + "type": "constructor" + }, + { + "inputs": [], + "name": "CollateralExceedsBorrowCapacity", + "type": "error" + }, + { + "inputs": [], + "name": "InvalidBaseCloseFactor", + "type": "error" + }, + { + "inputs": [], + "name": "InvalidDefaultCloseFactor", + "type": "error" + }, + { + "inputs": [], + "name": "InvalidTargetHealthFactor", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "market", + "type": "address" + } + ], + "name": "MarketNotListed", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "sender", + "type": "address" + }, + { + "internalType": "address", + "name": "calledContract", + "type": "address" + }, + { + "internalType": "string", + "name": "methodSignature", + "type": "string" + } + ], + "name": "Unauthorized", + "type": "error" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "market", + "type": "address" + }, + { + "indexed": false, + "internalType": "bool", + "name": "enabled", + "type": "bool" + } + ], + "name": "DynamicCloseFactorEnabledSet", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "market", + "type": "address" + }, + { + "indexed": false, + "internalType": "bool", + "name": "enabled", + "type": "bool" + } + ], + "name": "DynamicLiquidationIncentiveEnabledSet", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "uint8", + "name": "version", + "type": "uint8" + } + ], + "name": "Initialized", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "address", + "name": "oldAccessControlManager", + "type": "address" + }, + { + "indexed": false, + "internalType": "address", + "name": "newAccessControlManager", + "type": "address" + } + ], + "name": "NewAccessControlManager", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "previousOwner", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "newOwner", + "type": "address" + } + ], + "name": "OwnershipTransferStarted", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "previousOwner", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "newOwner", + "type": "address" + } + ], + "name": "OwnershipTransferred", + "type": "event" + }, + { + "inputs": [], + "name": "acceptOwnership", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "accessControlManager", + "outputs": [ + { + "internalType": "contract IAccessControlManagerV8", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "baseCloseFactorMantissa", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "market", + "type": "address" + }, + { + "internalType": "uint256", + "name": "borrowBalance", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "wtAvgMantissa", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "totalCollateral", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "dynamicLiquidationIncentive", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "maxLiquidationIncentive", + "type": "uint256" + } + ], + "name": "calculateDynamicCloseFactor", + "outputs": [ + { + "internalType": "uint256", + "name": "closeFactor", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "market", + "type": "address" + }, + { + "internalType": "uint256", + "name": "healthFactor", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "liquidationThresholdAvg", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "maxLiquidationIncentiveMantissa", + "type": "uint256" + } + ], + "name": "calculateDynamicLiquidationIncentive", + "outputs": [ + { + "internalType": "uint256", + "name": "incentive", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "defaultCloseFactorMantissa", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "name": "dynamicCloseFactorEnabled", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "name": "dynamicLiquidationIncentiveEnabled", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "accessControlManager_", + "type": "address" + } + ], + "name": "initialize", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "owner", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "pendingOwner", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "renounceOwnership", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "accessControlManager_", + "type": "address" + } + ], + "name": "setAccessControlManager", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "market", + "type": "address" + }, + { + "internalType": "bool", + "name": "enabled", + "type": "bool" + } + ], + "name": "setDynamicCloseFactorEnabled", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "market", + "type": "address" + }, + { + "internalType": "bool", + "name": "enabled", + "type": "bool" + } + ], + "name": "setDynamicLiquidationIncentiveEnabled", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "targetHealthFactor", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "newOwner", + "type": "address" + } + ], + "name": "transferOwnership", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + } +] diff --git a/simulations/vip-580/abi/Liquidator.json b/simulations/vip-580/abi/Liquidator.json new file mode 100644 index 000000000..00dd8a8ad --- /dev/null +++ b/simulations/vip-580/abi/Liquidator.json @@ -0,0 +1,15 @@ +[ + { + "inputs": [], + "name": "implementation", + "outputs": [ + { + "internalType": "address", + "name": "implementation_", + "type": "address" + } + ], + "stateMutability": "nonpayable", + "type": "function" + } +] diff --git a/simulations/vip-580/abi/VAIController.json b/simulations/vip-580/abi/VAIController.json new file mode 100644 index 000000000..e3665dfb7 --- /dev/null +++ b/simulations/vip-580/abi/VAIController.json @@ -0,0 +1,53 @@ +[ + { + "inputs": [], + "name": "vaiControllerImplementation", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "address", + "name": "oldImplementation", + "type": "address" + }, + { + "indexed": false, + "internalType": "address", + "name": "newImplementation", + "type": "address" + } + ], + "name": "NewImplementation", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "address", + "name": "oldPendingImplementation", + "type": "address" + }, + { + "indexed": false, + "internalType": "address", + "name": "newPendingImplementation", + "type": "address" + } + ], + "name": "NewPendingImplementation", + "type": "event" + } +] diff --git a/simulations/vip-580/abi/VBep20Delegate.json b/simulations/vip-580/abi/VBep20Delegate.json new file mode 100644 index 000000000..f5795fb5b --- /dev/null +++ b/simulations/vip-580/abi/VBep20Delegate.json @@ -0,0 +1,34 @@ +[ + { + "inputs": [], + "name": "implementation", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "address", + "name": "oldImplementation", + "type": "address" + }, + { + "indexed": false, + "internalType": "address", + "name": "newImplementation", + "type": "address" + } + ], + "name": "NewImplementation", + "type": "event" + } +] diff --git a/simulations/vip-580/bsctestnet.ts b/simulations/vip-580/bsctestnet.ts new file mode 100644 index 000000000..115a7e2e8 --- /dev/null +++ b/simulations/vip-580/bsctestnet.ts @@ -0,0 +1,346 @@ +import { TransactionResponse } from "@ethersproject/providers"; +import { SignerWithAddress } from "@nomiclabs/hardhat-ethers/signers"; +import { expect } from "chai"; +import { Contract } from "ethers"; +import { parseUnits } 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 { + LIQUIDATION_MANAGER, + LIQUIDATOR_PROXY_ADMIN, + NEW_COMPTROLLER_LENS, + NEW_DIAMOND, + NEW_LIQUIDATOR_IMPL, + NEW_VAI_CONTROLLER, + NEW_VTOKEN_IMPLEMENTATION, + vip580, +} from "../../vips/vip-580/bsctestnet"; +import ACM_ABI from "./abi/ACM.json"; +import COMPTROLLER_ABI from "./abi/Comptroller.json"; +import LIQUIDATOR_ABI from "./abi/Liquidator.json"; +import VAI_CONTROLLER_ABI from "./abi/VAIController.json"; +import VBEP20_DELEGATOR_ABI from "./abi/VBep20Delegate.json"; +import { cutParams as params } from "./utils/cut-params-bsctestnet.json"; +import CORE_POOL_VTOKENS from "./utils/market.json"; + +type CutParam = [string, number, string[]]; +const cutParams = params as unknown as CutParam[]; + +const { bsctestnet } = NETWORK_ADDRESSES; +const UNITROLLER = bsctestnet.UNITROLLER; +const ACM = bsctestnet.ACCESS_CONTROL_MANAGER; +const VAI_UNITROLLER = bsctestnet.VAI_UNITROLLER; +const LIQUIDATOR = bsctestnet.LIQUIDATOR; + +// Previous implementations (update these with actual addresses if known) +const OLD_DIAMOND = "0x1774f993861B14B7C3963F3e09f67cfBd2B32198"; +const OLD_COMPTROLLER_LENS = "0x72dCB93F8c3fB00D31076e93b6E87C342A3eCC9c"; +const OLD_VAI_CONTROLLER_IMPL = "0xA8122Fe0F9db39E266DE7A5BF953Cd72a87fe345"; +const OLD_LIQUIDATOR_IMPL = "0x91070E5b5Ff60a6c122740EB326D1f80E9f470e7"; +const OLD_VTOKEN_IMPL = "0xb941C5D148c65Ce49115D12B5148247AaCeFF375"; + +// Extract facet addresses from cutParams +const getFacetAddresses = (): string[] => { + const addresses: string[] = []; + for (const param of cutParams) { + if (param[0] !== "0x0000000000000000000000000000000000000000" && !addresses.includes(param[0])) { + addresses.push(param[0]); + } + } + return addresses; +}; + +const NEW_FACET_ADDRESSES = getFacetAddresses(); + +forking(76316411, async () => { + let unitroller: Contract; + let comptroller: Contract; + let accessControlManager: Contract; + let vaiUnitroller: Contract; + let liquidator: Contract; + let proxyAdmin: SignerWithAddress; + + before(async () => { + unitroller = await ethers.getContractAt(COMPTROLLER_ABI, UNITROLLER); + comptroller = await ethers.getContractAt(COMPTROLLER_ABI, UNITROLLER); + accessControlManager = await ethers.getContractAt(ACM_ABI, ACM); + vaiUnitroller = await ethers.getContractAt(VAI_CONTROLLER_ABI, VAI_UNITROLLER); + liquidator = await ethers.getContractAt(LIQUIDATOR_ABI, LIQUIDATOR); + proxyAdmin = await initMainnetUser(LIQUIDATOR_PROXY_ADMIN, parseUnits("2", 18)); + }); + + describe("Pre-VIP state", async () => { + it("unitroller should have old implementation", async () => { + const implementation = await unitroller.comptrollerImplementation(); + expect(implementation.toLowerCase()).to.equal(OLD_DIAMOND.toLowerCase()); + }); + + it("comptroller should have old comptroller lens", async () => { + const lens = await comptroller.comptrollerLens(); + expect(lens.toLowerCase()).to.equal(OLD_COMPTROLLER_LENS.toLowerCase()); + }); + + it("VAI Unitroller should point to old VAI Controller", async () => { + const implementation = await vaiUnitroller.vaiControllerImplementation(); + expect(implementation).to.equal(OLD_VAI_CONTROLLER_IMPL); + }); + + it("Liquidator should point to old implementation", async () => { + const impl = await liquidator.connect(proxyAdmin).callStatic.implementation(); + expect(impl.toLowerCase()).to.equal(OLD_LIQUIDATOR_IMPL.toLowerCase()); + }); + + it("vTokens should have old implementation", async () => { + const vToken = await ethers.getContractAt(VBEP20_DELEGATOR_ABI, CORE_POOL_VTOKENS[0].address); + const implementation = await vToken.implementation(); + expect(implementation.toLowerCase()).to.equal(OLD_VTOKEN_IMPL.toLowerCase()); + }); + + it("old liquidation incentive permission should exist", async () => { + const hasPermission = await accessControlManager.hasPermission( + bsctestnet.NORMAL_TIMELOCK, + UNITROLLER, + "setLiquidationIncentive(address,uint256)", + ); + expect(hasPermission).to.equal(true); + }); + + it("new liquidation functions should not have permissions", async () => { + const hasPermission1 = await accessControlManager.hasPermission( + bsctestnet.NORMAL_TIMELOCK, + UNITROLLER, + "setMarketMaxLiquidationIncentive(address,uint256)", + ); + expect(hasPermission1).to.equal(false); + + const hasPermission2 = await accessControlManager.hasPermission( + bsctestnet.NORMAL_TIMELOCK, + UNITROLLER, + "setLiquidationManager(address)", + ); + expect(hasPermission2).to.equal(false); + + const hasPermission3 = await accessControlManager.hasPermission( + bsctestnet.NORMAL_TIMELOCK, + UNITROLLER, + "setDynamicCloseFactorEnabled(address,bool)", + ); + expect(hasPermission3).to.equal(false); + + const hasPermission4 = await accessControlManager.hasPermission( + bsctestnet.NORMAL_TIMELOCK, + UNITROLLER, + "setDynamicLiquidationIncentiveEnabled(address,bool)", + ); + expect(hasPermission4).to.equal(false); + }); + }); + + testVip("VIP-580 Liquidation Threshold and Dynamic Liquidation Improvements", await vip580(), { + callbackAfterExecution: async (txResponse: TransactionResponse) => { + const totalMarkets = CORE_POOL_VTOKENS.length; + const totalTimelocks = 4; // Normal, Fast, Critical, Guardian + const newPermissionsPerTimelock = 5; // 5 new permissions + const oldPermissionsPerTimelock = 2; // 2 old permissions to revoke + + await expectEvents( + txResponse, + [COMPTROLLER_ABI, ACM_ABI], + ["NewPendingImplementation", "DiamondCut", "PermissionGranted", "PermissionRevoked"], + [ + 4, // Unitroller + VAI Controller (2x each for pending + become) + 1, // Diamond cut + newPermissionsPerTimelock * totalTimelocks, // New permissions + oldPermissionsPerTimelock * totalTimelocks, // Revoked permissions + ], + ); + + await expectEvents( + txResponse, + [VBEP20_DELEGATOR_ABI], + ["NewImplementation"], + [totalMarkets + 2], // All vTokens + Unitroller + VAI Controller + ); + }, + }); + + describe("Post-VIP state", async () => { + it("unitroller should have new implementation", async () => { + const implementation = await unitroller.comptrollerImplementation(); + expect(implementation).to.equal(NEW_DIAMOND); + }); + + it("comptroller should have new comptroller lens", async () => { + const lens = await comptroller.comptrollerLens(); + expect(lens).to.equal(NEW_COMPTROLLER_LENS); + }); + + it("VAI Controller should point to new implementation", async () => { + const implementation = await vaiUnitroller.vaiControllerImplementation(); + expect(implementation).to.equal(NEW_VAI_CONTROLLER); + }); + + it("Liquidator should point to new implementation", async () => { + const impl = await liquidator.connect(proxyAdmin).callStatic.implementation(); + expect(impl).to.equal(NEW_LIQUIDATOR_IMPL); + }); + + it("all vTokens should have new implementation", async () => { + for (const vToken of CORE_POOL_VTOKENS) { + const vTokenContract = await ethers.getContractAt(VBEP20_DELEGATOR_ABI, vToken.address); + const implementation = await vTokenContract.implementation(); + expect(implementation).to.equal(NEW_VTOKEN_IMPLEMENTATION); + } + }); + + it("liquidation manager should be set in comptroller", async () => { + const liquidationManagerAddress = await comptroller.liquidationManager(); + expect(liquidationManagerAddress).to.equal(LIQUIDATION_MANAGER); + }); + + it("diamond should contain new facet addresses", async () => { + const facetAddresses = await unitroller.facetAddresses(); + for (const facetAddress of NEW_FACET_ADDRESSES) { + expect(facetAddresses).to.include(facetAddress); + } + }); + + it("diamond cut should have configured function selectors correctly", async () => { + for (const [facetAddress, action, functionSelectors] of cutParams) { + if (action === 1) { + // Add or Replace + const actualSelectors = await unitroller.facetFunctionSelectors(facetAddress); + for (const selector of functionSelectors) { + expect(actualSelectors).to.include(selector); + } + } else if (action === 2) { + // Remove + const actualSelectors = await unitroller.facetFunctionSelectors(facetAddress); + for (const selector of functionSelectors) { + expect(actualSelectors).to.not.include(selector); + } + } + } + }); + + it("old liquidation incentive permissions should be revoked", async () => { + for (const timelock of [ + bsctestnet.NORMAL_TIMELOCK, + bsctestnet.FAST_TRACK_TIMELOCK, + bsctestnet.CRITICAL_TIMELOCK, + bsctestnet.GUARDIAN, + ]) { + expect( + await accessControlManager.hasPermission(timelock, UNITROLLER, "setLiquidationIncentive(address,uint256)"), + ).to.equal(false); + expect( + await accessControlManager.hasPermission( + timelock, + UNITROLLER, + "setLiquidationIncentive(uint96,address,uint256)", + ), + ).to.equal(false); + } + }); + + it("new liquidation functions should have permissions", async () => { + const newMethods = [ + "setMarketMaxLiquidationIncentive(address,uint256)", + "setMarketMaxLiquidationIncentive(uint96,address,uint256)", + "setLiquidationManager(address)", + "setDynamicCloseFactorEnabled(address,bool)", + "setDynamicLiquidationIncentiveEnabled(address,bool)", + ]; + + for (const timelock of [ + bsctestnet.NORMAL_TIMELOCK, + bsctestnet.FAST_TRACK_TIMELOCK, + bsctestnet.CRITICAL_TIMELOCK, + bsctestnet.GUARDIAN, + ]) { + for (const method of newMethods) { + expect(await accessControlManager.hasPermission(timelock, UNITROLLER, method)).to.equal(true); + } + } + }); + + it("liquidation manager should be a valid contract", async () => { + const code = await ethers.provider.getCode(LIQUIDATION_MANAGER); + expect(code).to.not.equal("0x"); + }); + + it("comptroller should support new liquidation features", async () => { + // Test that new functions exist and are callable + // These will revert if the functions don't exist + try { + await comptroller.liquidationManager(); + // If it doesn't revert, the function exists + expect(true).to.be.true; + } catch (error) { + expect.fail("liquidationManager function should exist"); + } + }); + }); + + describe("Liquidation Threshold Features", async () => { + it("markets should have liquidation threshold set", async () => { + // Assuming liquidation threshold is stored in market data + // This test structure depends on the actual implementation + for (const vToken of CORE_POOL_VTOKENS.slice(0, 5)) { + // Test first 5 markets + const marketData = await comptroller.markets(vToken.address); + // Market data structure: [isListed, collateralFactorMantissa, isVenus, liquidationThresholdMantissa, ...] + // Verify liquidation threshold is set (assuming index 3) + expect(marketData[3]).to.exist; + } + }); + }); + + describe("Dynamic Liquidation Features", async () => { + it("dynamic liquidation incentive should be configurable", async () => { + // Test that the function exists + try { + // This is a read-only check to verify the function exists + const hasPermission = await accessControlManager.hasPermission( + bsctestnet.NORMAL_TIMELOCK, + UNITROLLER, + "setDynamicLiquidationIncentiveEnabled(address,bool)", + ); + expect(hasPermission).to.equal(true); + } catch (error) { + expect.fail("Dynamic liquidation incentive function should exist"); + } + }); + + it("dynamic close factor should be configurable", async () => { + // Test that the function exists + try { + const hasPermission = await accessControlManager.hasPermission( + bsctestnet.NORMAL_TIMELOCK, + UNITROLLER, + "setDynamicCloseFactorEnabled(address,bool)", + ); + expect(hasPermission).to.equal(true); + } catch (error) { + expect.fail("Dynamic close factor function should exist"); + } + }); + + it("max liquidation incentive should be configurable per market", async () => { + // Test that the function exists + try { + const hasPermission = await accessControlManager.hasPermission( + bsctestnet.NORMAL_TIMELOCK, + UNITROLLER, + "setMarketMaxLiquidationIncentive(address,uint256)", + ); + expect(hasPermission).to.equal(true); + } catch (error) { + expect.fail("Max liquidation incentive function should exist"); + } + }); + }); +}); diff --git a/simulations/vip-580/utils/cut-params-bsctestnet.json b/simulations/vip-580/utils/cut-params-bsctestnet.json new file mode 100644 index 000000000..80a6a82a1 --- /dev/null +++ b/simulations/vip-580/utils/cut-params-bsctestnet.json @@ -0,0 +1,130 @@ +{ + "cutParams": [ + [ + "0x2A926859f87C322eEe043B8e5f098e618F92c529", + 1, + [ + "0xa76b3fda", + "0x89c13be0", + "0x929fe9a1", + "0xd0d13036", + "0xc2998238", + "0xf9682732", + "0xede4edd0", + "0xb0772d0b", + "0xabfceffc", + "0x23617585", + "0xafd3783b", + "0x19ef3e8b", + "0xd686e9ee", + "0x7b86e42c", + "0x63e0d634", + "0xf02fdf97", + "0x007e3dd2", + "0x3d98a1e5", + "0x0ef332ca", + "0x8e8f294b", + "0x3093c11e", + "0xd137f36e", + "0xcab4f84c", + "0x0686dab6", + "0xddbf54fd" + ] + ], + [ + "0x2A926859f87C322eEe043B8e5f098e618F92c529", + 0, + ["0x1ec8ba02", "0xcd544f24", "0x9e9b1877", "0x13d97e2c", "0xe7ed111e"] + ], + [ + "0x0000000000000000000000000000000000000000", + 2, + ["0xc488847b", "0xa78dc775", "0xc5b4db55", "0xd463654c", "0x4d99c776", "0xd585c3c6"] + ], + [ + "0x550F5408D34793a723C22ff84A6872d74D5597f1", + 1, + [ + "0xead1a8a0", + "0xda3d454c", + "0x5c778605", + "0x5ec88c79", + "0x528a174c", + "0x4e79238f", + "0x5fc7e71e", + "0x47ef3b3b", + "0x4ef4c3e1", + "0x41c728b9", + "0xeabe7d91", + "0x51dff989", + "0x24008a62", + "0x1ededc91", + "0xd02f7351", + "0x6d35bf91", + "0xbdcdc258", + "0x6a56947e" + ] + ], + ["0x550F5408D34793a723C22ff84A6872d74D5597f1", 0, ["0x14ecd653"]], + [ + "0x03Be0AAd2EADc48892335C6Ac10A71DaD5a81A15", + 1, + [ + "0xa7604b41", + "0xe85a2960", + "0x70bf66f0", + "0x86df31ee", + "0xadcd5fb9", + "0xd09c54ba", + "0x7858524d", + "0xbf32442d", + "0xededbae6", + "0x655f0725" + ] + ], + ["0x03Be0AAd2EADc48892335C6Ac10A71DaD5a81A15", 0, ["0xd463654c", "0xa9e87456", "0x4d99c776", "0xc5b4db55"]], + [ + "0xe48f7E3F94349962A33D1e909b3F28E14A8770c9", + 1, + [ + "0xf519fc30", + "0x2b5d790c", + "0x9bf34cbb", + "0x522c656b", + "0x17db2163", + "0xbb857450", + "0x607ef6c1", + "0x51a485e4", + "0x5f5af1aa", + "0x55ee1fe1", + "0x9460c8b5", + "0x2a6a6065", + "0xd24febad", + "0x9cfdd9e6", + "0x2ec04124", + "0x4e0853db", + "0x6662c7c9", + "0x919a3736", + "0x4ef233fc", + "0x24aaa220", + "0x7938146f", + "0x5cc4fdeb", + "0x9159b177", + "0xd6ad5c39", + "0x8b3113f6", + "0xa89766dd", + "0x186db48f", + "0xd136af44", + "0xfd51a3ad", + "0x4964f48c", + "0xb88d846b", + "0x530e784f", + "0xc32094c7", + "0x42adb211" + ] + ], + ["0xe48f7E3F94349962A33D1e909b3F28E14A8770c9", 0, ["0x38f92fc7", "0x75b74550", "0xc3b9d89a"]], + ["0x0000000000000000000000000000000000000000", 2, ["0x317b0b77", "0x12348e96", "0x35439240", "0x9bd8f6e8"]], + ["0x07347914d067C9227836870D6Be8F78539B91437", 1, ["0x5544ed9c"]] + ] +} diff --git a/simulations/vip-580/utils/market.json b/simulations/vip-580/utils/market.json new file mode 100644 index 000000000..1421b0f76 --- /dev/null +++ b/simulations/vip-580/utils/market.json @@ -0,0 +1,154 @@ +[ + { + "symbol": "vUSDC", + "address": "0xD5C4C2e2facBEB59D0216D0595d63FcDc6F9A1a7" + }, + { + "symbol": "vUSDT", + "address": "0xb7526572FFE56AB9D7489838Bf2E18e3323b441A" + }, + { + "symbol": "vBUSD", + "address": "0x08e0A5575De71037aE36AbfAfb516595fE68e5e4" + }, + { + "symbol": "vSXP", + "address": "0x74469281310195A04840Daf6EdF576F559a3dE80" + }, + { + "symbol": "Venus XVS", + "address": "0x6d6F697e34145Bb95c54E77482d97cc261Dc237E" + }, + { + "symbol": "vETH", + "address": "0x162D005F0Fff510E54958Cfc5CF32A3180A84aab" + }, + { + "symbol": "vLTC", + "address": "0xAfc13BC065ABeE838540823431055D2ea52eBA52" + }, + { + "symbol": "vXRP", + "address": "0x488aB2826a154da01CC4CC16A8C83d4720D3cA2C" + }, + { + "symbol": "vBTC", + "address": "0xb6e9322C49FD75a367Fcb17B0Fcd62C5070EbCBe" + }, + { + "symbol": "vADA", + "address": "0x37C28DE42bA3d22217995D146FC684B2326Ede64" + }, + { + "symbol": "vDOGE", + "address": "0xF912d3001CAf6DC4ADD366A62Cc9115B4303c9A9" + }, + { + "symbol": "vCAKE", + "address": "0xeDaC03D29ff74b5fDc0CC936F6288312e1459BC6" + }, + { + "symbol": "vMATIC", + "address": "0x3619bdDc61189F33365CC572DF3a68FB3b316516" + }, + { + "symbol": "vAAVE", + "address": "0x714db6c38A17883964B68a07d56cE331501d9eb6" + }, + { + "symbol": "vTUSDOLD", + "address": "0x3A00d9B02781f47d033BAd62edc55fBF8D083Fb0" + }, + { + "symbol": "vTRXOLD", + "address": "0x369Fea97f6fB7510755DCA389088d9E2e2819278" + }, + { + "symbol": "vUST", + "address": "0xF206af85BC2761c4F876d27Bd474681CfB335EfA" + }, + { + "symbol": "vLUNA", + "address": "0x9C3015191d39cF1930F92EB7e7BCbd020bCA286a" + }, + { + "symbol": "vTRX", + "address": "0x6AF3Fdb3282c5bb6926269Db10837fa8Aec67C04" + }, + { + "symbol": "vWBETH", + "address": "0x35566ED3AF9E537Be487C98b1811cDf95ad0C32b" + }, + { + "symbol": "vTUSD", + "address": "0xEFAACF73CE2D38ED40991f29E72B12C74bd4cf23" + }, + { + "symbol": "vUNI", + "address": "0x171B468b52d7027F12cEF90cd065d6776a25E24e" + }, + { + "symbol": "vFDUSD", + "address": "0xF06e662a00796c122AaAE935EC4F0Be3F74f5636" + }, + { + "symbol": "vSolvBTC", + "address": "0xA38110ae4451A86ab754695057d5B5a9BEAd0387" + }, + { + "symbol": "vTWT", + "address": "0x95DaED37fdD3F557b3A5cCEb7D50Be65b36721DF" + }, + { + "symbol": "vTHE", + "address": "0x39A239F5117BFaC7a1b0b3A517c454113323451d" + }, + { + "symbol": "vSOL", + "address": "0xbd9EB061444665Df7282Ec0888b72D60aC41Eb8C" + }, + { + "symbol": "vlisUSD", + "address": "0x9447b1D4Bd192f25416B6aCc3B7f06be2f7D6309" + }, + { + "symbol": "vPT-sUSDE-26JUN2025", + "address": "0x90535B06ddB00453a5e5f2bC094d498F1cc86032" + }, + { + "symbol": "vsUSDe", + "address": "0x8c8A1a0b6e1cb8058037F7bF24de6b79Aca5B7B0" + }, + { + "symbol": "vUSDe", + "address": "0x86f8DfB7CA84455174EE9C3edd94867b51Da46BD" + }, + { + "symbol": "vUSD1", + "address": "0x519e61D2CDA04184FB086bbD2322C1bfEa0917Cf" + }, + { + "symbol": "vxSolvBTC", + "address": "0x97cB97B05697c377C0bd09feDce67DBd86B7aB1e" + }, + { + "symbol": "vasBNB", + "address": "0x73F506Aefd5e169D48Ea21A373B9B0a200E37585" + }, + { + "symbol": "vUSDF", + "address": "0x140d5Da2cE9fb9A8725cabdDB2Fe8ea831342C78" + }, + { + "symbol": "vWBNB", + "address": "0xd9E77847ec815E56ae2B9E69596C69b6972b0B1C" + }, + { + "symbol": "vPT-USDe-30OCT2025", + "address": "0x86a94290f2B8295daA3e53bA1286f2Ff21199143" + }, + { + "symbol": "vslisBNB", + "address": "0xaB5504A3cde0d8253E8F981D663c7Ff7128B3e56" + } +] diff --git a/src/networkAddresses.ts b/src/networkAddresses.ts index 757be8868..c9c45548e 100644 --- a/src/networkAddresses.ts +++ b/src/networkAddresses.ts @@ -75,6 +75,7 @@ export const NETWORK_ADDRESSES = { BINANCE_ORACLE: oracleBsctestnetContracts.addresses.BinanceOracle, RESILIENT_ORACLE: oracleBsctestnetContracts.addresses.ResilientOracle, REDSTONE_ORACLE: oracleBsctestnetContracts.addresses.RedStoneOracle, + LIQUIDATOR: bsctestnetDeployedContracts.addresses.Liquidator, }, ethereum: { NORMAL_TIMELOCK: "0xd969E79406c35E80750aAae061D402Aab9325714", diff --git a/vips/vip-580/bsctestnet.ts b/vips/vip-580/bsctestnet.ts new file mode 100644 index 000000000..d1528f1a0 --- /dev/null +++ b/vips/vip-580/bsctestnet.ts @@ -0,0 +1,198 @@ +import { NETWORK_ADDRESSES } from "src/networkAddresses"; +import { ProposalType } from "src/types"; +import { makeProposal } from "src/utils"; + +import { cutParams } from "../../simulations/vip-580/utils/cut-params-bsctestnet.json"; +import CORE_POOL_VTOKENS from "../../simulations/vip-580/utils/market.json"; + +const { bsctestnet } = NETWORK_ADDRESSES; + +const ACM = bsctestnet.ACCESS_CONTROL_MANAGER; +const LIQUIDATOR = bsctestnet.LIQUIDATOR; +const UNITROLLER = bsctestnet.UNITROLLER; +const VAI_UNITROLLER = bsctestnet.VAI_UNITROLLER; + +const NORMAL_TIMELOCK = bsctestnet.NORMAL_TIMELOCK; +const FAST_TRACK_TIMELOCK = bsctestnet.FAST_TRACK_TIMELOCK; +const CRITICAL_TIMELOCK = bsctestnet.CRITICAL_TIMELOCK; + +export const LIQUIDATOR_PROXY_ADMIN = "0x1469AeB2768931f979a1c957692e32Aa802dd55a"; +export const LIQUIDATION_MANAGER = "0xA0Eef73F94DB337F08f34F9013bCAF8D392289A5"; +export const NEW_COMPTROLLER_LENS = "0x9D542132fa552B6b416944501bF0D689286E1535"; +export const NEW_DIAMOND = "0xe492CCD207760fE4Dfa9B83Fd1590632dDE33BAB"; +export const NEW_LIQUIDATOR_IMPL = "0xbC89aA9ab926b8491CB7E613A56A13A14BCfa8bc"; +export const NEW_VAI_CONTROLLER = "0xBc98737283f10Dd759DB79cD5f8d11Da909D992b"; +export const NEW_VTOKEN_IMPLEMENTATION = "0xC0c413F41281C61E160c47FCC215D9d0BC6AC72d"; + +const PAUSE_GUARDIAN_MULTISIG = bsctestnet.GUARDIAN; + +interface AccessControl { + target: string; + signature: string; + params: Array; +} + +const revokeAccessControl = () => { + const accessProposals: Array = []; + [NORMAL_TIMELOCK, FAST_TRACK_TIMELOCK, CRITICAL_TIMELOCK, PAUSE_GUARDIAN_MULTISIG].map(target => { + accessProposals.push({ + target: ACM, + signature: "revokeCallPermission(address,string,address)", + params: [UNITROLLER, "setLiquidationIncentive(address,uint256)", target], + }); + accessProposals.push({ + target: ACM, + signature: "revokeCallPermission(address,string,address)", + params: [UNITROLLER, "setLiquidationIncentive(uint96,address,uint256)", target], + }); + }); + + return accessProposals; +}; + +const grantAccessControl = () => { + const accessProposals: Array = []; + [NORMAL_TIMELOCK, FAST_TRACK_TIMELOCK, CRITICAL_TIMELOCK, PAUSE_GUARDIAN_MULTISIG].map(target => { + accessProposals.push({ + target: ACM, + signature: "giveCallPermission(address,string,address)", + params: [UNITROLLER, "setMarketMaxLiquidationIncentive(address,uint256)", target], + }); + accessProposals.push({ + target: ACM, + signature: "giveCallPermission(address,string,address)", + params: [UNITROLLER, "setMarketMaxLiquidationIncentive(uint96,address,uint256)", target], + }); + accessProposals.push({ + target: ACM, + signature: "giveCallPermission(address,string,address)", + params: [UNITROLLER, "setLiquidationManager(address)", target], + }); + accessProposals.push({ + target: ACM, + signature: "giveCallPermission(address,string,address)", + params: [UNITROLLER, "setDynamicCloseFactorEnabled(address,bool)", target], + }); + accessProposals.push({ + target: ACM, + signature: "giveCallPermission(address,string,address)", + params: [UNITROLLER, "setDynamicLiquidationIncentiveEnabled(address,bool)", target], + }); + }); + + return accessProposals; +}; + +export const vip580 = () => { + const meta = { + version: "v2", + title: "VIP-580 Liquidation Threshold and Dynamic Liquidation Improvements", + description: `#### Summary + +This VIP introduces enhanced liquidation mechanisms to the Venus Core Pool on BNB Chain Testnet, implementing the following improvements: + +#### Description + +- **Liquidation Threshold**: Adds the concept of liquidation threshold to the Core Pool, enabling more granular control over when positions become eligible for liquidation +- **Dynamic Liquidation Incentive**: Implements dynamic liquidation incentives that adjust based on market conditions and account health factors +- **Dynamic Close Factor**: Introduces dynamic close factors that vary based on borrower position health +- **Maximum Liquidation Incentive**: Defines maximum liquidation incentive limits per seized asset to protect borrowers from excessive liquidation penalties + +#### Contract Upgrades + +This VIP will perform the following: + +- Upgrade Comptroller (Unitroller) to new Diamond implementation with updated facets +- Deploy and configure the new Liquidation Manager contract +- Upgrade VAI Controller with updated liquidation logic +- Upgrade Liquidator contract to support dynamic incentives +- Upgrade all Core Pool vTokens to new implementation +- Update Comptroller Lens for new data structures +- Configure new access controls for liquidation parameters + +#### References + +- [VIP Pull Request](https://github.com/VenusProtocol/venus-protocol/pull/604) +- [Community Forum Discussion](https://community.venus.io) + +#### Disclaimer + +Proposed changes do not guarantee profit and are subject to market risks. Users should conduct independent research before participating.`, + forDescription: "Execute this proposal", + againstDescription: "Do not execute this proposal", + abstainDescription: "Indifferent to execution", + }; + + return makeProposal( + [ + // Set new implementation for unitroller + { + target: UNITROLLER, + signature: "_setPendingImplementation(address)", + params: [NEW_DIAMOND], + }, + { + target: NEW_DIAMOND, + signature: "_become(address)", + params: [UNITROLLER], + }, + + // Configure facets and functions selectors + { + target: UNITROLLER, + signature: "diamondCut((address,uint8,bytes4[])[])", + params: [cutParams], + }, + + // Grant access to the new functions + ...grantAccessControl(), + + // Set liquidation manager in comptroller + { + target: UNITROLLER, + signature: "setLiquidationManager(address)", + params: [LIQUIDATION_MANAGER], + }, + + // Set new implementation for comptroller lens + { + target: UNITROLLER, + signature: "_setComptrollerLens(address)", + params: [NEW_COMPTROLLER_LENS], + }, + + // Set new implementation for vai controller + { + target: VAI_UNITROLLER, + signature: "_setPendingImplementation(address)", + params: [NEW_VAI_CONTROLLER], + }, + { + target: NEW_VAI_CONTROLLER, + signature: "_become(address)", + params: [VAI_UNITROLLER], + }, + + // Set new implementation for liquidator + { + target: LIQUIDATOR_PROXY_ADMIN, + signature: "upgrade(address,address)", + params: [LIQUIDATOR, NEW_LIQUIDATOR_IMPL], + }, + + // Set new implementation for core pool vtokens + ...CORE_POOL_VTOKENS.map(vToken => ({ + target: vToken.address, + signature: "_setImplementation(address,bool,bytes)", + params: [NEW_VTOKEN_IMPLEMENTATION, false, "0x"], + })), + + // Revoke access to the removed functions + ...revokeAccessControl(), + ], + meta, + ProposalType.REGULAR, + ); +}; + +export default vip580; From 5e8c3dc7d69f84c0c6e5f1cb5697a14dd9b6a728 Mon Sep 17 00:00:00 2001 From: Debugger022 Date: Tue, 9 Dec 2025 12:13:35 +0530 Subject: [PATCH 2/2] refactor: refactor cut-params and simulations for bsctestnet --- simulations/vip-580/bsctestnet.ts | 208 ++++++------------ .../vip-580/utils/cut-params-bsctestnet.json | 27 ++- vips/vip-580/bsctestnet.ts | 17 +- 3 files changed, 89 insertions(+), 163 deletions(-) diff --git a/simulations/vip-580/bsctestnet.ts b/simulations/vip-580/bsctestnet.ts index 115a7e2e8..ac7c2184b 100644 --- a/simulations/vip-580/bsctestnet.ts +++ b/simulations/vip-580/bsctestnet.ts @@ -35,25 +35,31 @@ const ACM = bsctestnet.ACCESS_CONTROL_MANAGER; const VAI_UNITROLLER = bsctestnet.VAI_UNITROLLER; const LIQUIDATOR = bsctestnet.LIQUIDATOR; -// Previous implementations (update these with actual addresses if known) const OLD_DIAMOND = "0x1774f993861B14B7C3963F3e09f67cfBd2B32198"; const OLD_COMPTROLLER_LENS = "0x72dCB93F8c3fB00D31076e93b6E87C342A3eCC9c"; const OLD_VAI_CONTROLLER_IMPL = "0xA8122Fe0F9db39E266DE7A5BF953Cd72a87fe345"; const OLD_LIQUIDATOR_IMPL = "0x91070E5b5Ff60a6c122740EB326D1f80E9f470e7"; const OLD_VTOKEN_IMPL = "0xb941C5D148c65Ce49115D12B5148247AaCeFF375"; -// Extract facet addresses from cutParams -const getFacetAddresses = (): string[] => { - const addresses: string[] = []; - for (const param of cutParams) { - if (param[0] !== "0x0000000000000000000000000000000000000000" && !addresses.includes(param[0])) { - addresses.push(param[0]); - } - } - return addresses; -}; - -const NEW_FACET_ADDRESSES = getFacetAddresses(); +const OLD_SETTER_FACET = "0x4fc4C41388237D13A430879417f143EF54e5BB05"; +const OLD_REWARD_FACET = "0x0bc7922Cc08Ea32E196d25805558a84dF54beC6a"; +const OLD_MARKET_FACET = "0x8e0e15C99Ab0985cB39B2FE36532E5692730eBA9"; +const OLD_POLICY_FACET = "0xf8d94ef23c1188f8ab1009E56D558d7834d1F019"; +const OLD_FLASHLOAN_FACET = "0x32348c5bB52E5468A11901e70BdE061192feCAf4"; + +const NEW_SETTER_FACET = "0xe48f7E3F94349962A33D1e909b3F28E14A8770c9"; +const NEW_REWARD_FACET = "0x03Be0AAd2EADc48892335C6Ac10A71DaD5a81A15"; +const NEW_MARKET_FACET = "0x2A926859f87C322eEe043B8e5f098e618F92c529"; +const NEW_POLICY_FACET = "0x550F5408D34793a723C22ff84A6872d74D5597f1"; +const NEW_FLASHLOAN_FACET = "0x07347914d067C9227836870D6Be8F78539B91437"; + +const newMethods = [ + "setMarketMaxLiquidationIncentive(address,uint256)", + "setMarketMaxLiquidationIncentive(uint96,address,uint256)", + "setLiquidationManager(address)", + "setDynamicCloseFactorEnabled(address,bool)", + "setDynamicLiquidationIncentiveEnabled(address,bool)", +]; forking(76316411, async () => { let unitroller: Contract; @@ -109,33 +115,16 @@ forking(76316411, async () => { }); it("new liquidation functions should not have permissions", async () => { - const hasPermission1 = await accessControlManager.hasPermission( - bsctestnet.NORMAL_TIMELOCK, - UNITROLLER, - "setMarketMaxLiquidationIncentive(address,uint256)", - ); - expect(hasPermission1).to.equal(false); - - const hasPermission2 = await accessControlManager.hasPermission( - bsctestnet.NORMAL_TIMELOCK, - UNITROLLER, - "setLiquidationManager(address)", - ); - expect(hasPermission2).to.equal(false); - - const hasPermission3 = await accessControlManager.hasPermission( - bsctestnet.NORMAL_TIMELOCK, - UNITROLLER, - "setDynamicCloseFactorEnabled(address,bool)", - ); - expect(hasPermission3).to.equal(false); - - const hasPermission4 = await accessControlManager.hasPermission( + for (const timelock of [ bsctestnet.NORMAL_TIMELOCK, - UNITROLLER, - "setDynamicLiquidationIncentiveEnabled(address,bool)", - ); - expect(hasPermission4).to.equal(false); + bsctestnet.FAST_TRACK_TIMELOCK, + bsctestnet.CRITICAL_TIMELOCK, + bsctestnet.GUARDIAN, + ]) { + for (const method of newMethods) { + expect(await accessControlManager.hasPermission(timelock, UNITROLLER, method)).to.equal(false); + } + } }); }); @@ -201,29 +190,44 @@ forking(76316411, async () => { expect(liquidationManagerAddress).to.equal(LIQUIDATION_MANAGER); }); - it("diamond should contain new facet addresses", async () => { - const facetAddresses = await unitroller.facetAddresses(); - for (const facetAddress of NEW_FACET_ADDRESSES) { - expect(facetAddresses).to.include(facetAddress); - } + it("market facet function selectors should be replaced with new facet address", async () => { + const functionSelectors = [...cutParams[0][2], ...cutParams[1][2]]; + expect(await unitroller.facetFunctionSelectors(NEW_MARKET_FACET)).to.deep.equal(functionSelectors); + expect(await unitroller.facetFunctionSelectors(OLD_MARKET_FACET)).to.deep.equal([]); }); - it("diamond cut should have configured function selectors correctly", async () => { - for (const [facetAddress, action, functionSelectors] of cutParams) { - if (action === 1) { - // Add or Replace - const actualSelectors = await unitroller.facetFunctionSelectors(facetAddress); - for (const selector of functionSelectors) { - expect(actualSelectors).to.include(selector); - } - } else if (action === 2) { - // Remove - const actualSelectors = await unitroller.facetFunctionSelectors(facetAddress); - for (const selector of functionSelectors) { - expect(actualSelectors).to.not.include(selector); - } - } - } + it("policy facet function selectors should be replaced with new facet address", async () => { + const functionSelectors = [...cutParams[2][2], ...cutParams[3][2]]; + expect(await unitroller.facetFunctionSelectors(NEW_POLICY_FACET)).to.deep.equal(functionSelectors); + expect(await unitroller.facetFunctionSelectors(OLD_POLICY_FACET)).to.deep.equal([]); + }); + + it("reward facet function selectors should be replaced with new facet address", async () => { + const functionSelectors = cutParams[4][2]; + expect(await unitroller.facetFunctionSelectors(NEW_REWARD_FACET)).to.deep.equal(functionSelectors); + expect(await unitroller.facetFunctionSelectors(OLD_REWARD_FACET)).to.deep.equal([]); + }); + + it("setter facet function selectors should be replaced with new facet address", async () => { + const functionSelectors = [...cutParams[5][2], ...cutParams[6][2]]; + expect(await unitroller.facetFunctionSelectors(NEW_SETTER_FACET)).to.deep.equal(functionSelectors); + expect(await unitroller.facetFunctionSelectors(OLD_SETTER_FACET)).to.deep.equal([]); + }); + + it("flashloan facet function selectors should be replaced with new facet address", async () => { + const functionSelectors = cutParams[7][2]; + expect(await unitroller.facetFunctionSelectors(NEW_FLASHLOAN_FACET)).to.deep.equal(functionSelectors); + expect(await unitroller.facetFunctionSelectors(OLD_FLASHLOAN_FACET)).to.deep.equal([]); + }); + + it("unitroller should contain the new facet addresses", async () => { + expect(await unitroller.facetAddresses()).to.include(NEW_SETTER_FACET, NEW_REWARD_FACET); + expect(await unitroller.facetAddresses()).to.include(NEW_MARKET_FACET, NEW_POLICY_FACET); + expect(await unitroller.facetAddresses()).to.include(NEW_FLASHLOAN_FACET); + + expect(await unitroller.facetAddresses()).to.not.include(OLD_SETTER_FACET, OLD_REWARD_FACET); + expect(await unitroller.facetAddresses()).to.not.include(OLD_MARKET_FACET, OLD_POLICY_FACET); + expect(await unitroller.facetAddresses()).to.not.include(OLD_FLASHLOAN_FACET); }); it("old liquidation incentive permissions should be revoked", async () => { @@ -247,14 +251,6 @@ forking(76316411, async () => { }); it("new liquidation functions should have permissions", async () => { - const newMethods = [ - "setMarketMaxLiquidationIncentive(address,uint256)", - "setMarketMaxLiquidationIncentive(uint96,address,uint256)", - "setLiquidationManager(address)", - "setDynamicCloseFactorEnabled(address,bool)", - "setDynamicLiquidationIncentiveEnabled(address,bool)", - ]; - for (const timelock of [ bsctestnet.NORMAL_TIMELOCK, bsctestnet.FAST_TRACK_TIMELOCK, @@ -266,81 +262,5 @@ forking(76316411, async () => { } } }); - - it("liquidation manager should be a valid contract", async () => { - const code = await ethers.provider.getCode(LIQUIDATION_MANAGER); - expect(code).to.not.equal("0x"); - }); - - it("comptroller should support new liquidation features", async () => { - // Test that new functions exist and are callable - // These will revert if the functions don't exist - try { - await comptroller.liquidationManager(); - // If it doesn't revert, the function exists - expect(true).to.be.true; - } catch (error) { - expect.fail("liquidationManager function should exist"); - } - }); - }); - - describe("Liquidation Threshold Features", async () => { - it("markets should have liquidation threshold set", async () => { - // Assuming liquidation threshold is stored in market data - // This test structure depends on the actual implementation - for (const vToken of CORE_POOL_VTOKENS.slice(0, 5)) { - // Test first 5 markets - const marketData = await comptroller.markets(vToken.address); - // Market data structure: [isListed, collateralFactorMantissa, isVenus, liquidationThresholdMantissa, ...] - // Verify liquidation threshold is set (assuming index 3) - expect(marketData[3]).to.exist; - } - }); - }); - - describe("Dynamic Liquidation Features", async () => { - it("dynamic liquidation incentive should be configurable", async () => { - // Test that the function exists - try { - // This is a read-only check to verify the function exists - const hasPermission = await accessControlManager.hasPermission( - bsctestnet.NORMAL_TIMELOCK, - UNITROLLER, - "setDynamicLiquidationIncentiveEnabled(address,bool)", - ); - expect(hasPermission).to.equal(true); - } catch (error) { - expect.fail("Dynamic liquidation incentive function should exist"); - } - }); - - it("dynamic close factor should be configurable", async () => { - // Test that the function exists - try { - const hasPermission = await accessControlManager.hasPermission( - bsctestnet.NORMAL_TIMELOCK, - UNITROLLER, - "setDynamicCloseFactorEnabled(address,bool)", - ); - expect(hasPermission).to.equal(true); - } catch (error) { - expect.fail("Dynamic close factor function should exist"); - } - }); - - it("max liquidation incentive should be configurable per market", async () => { - // Test that the function exists - try { - const hasPermission = await accessControlManager.hasPermission( - bsctestnet.NORMAL_TIMELOCK, - UNITROLLER, - "setMarketMaxLiquidationIncentive(address,uint256)", - ); - expect(hasPermission).to.equal(true); - } catch (error) { - expect.fail("Max liquidation incentive function should exist"); - } - }); }); }); diff --git a/simulations/vip-580/utils/cut-params-bsctestnet.json b/simulations/vip-580/utils/cut-params-bsctestnet.json index 80a6a82a1..3b10091d6 100644 --- a/simulations/vip-580/utils/cut-params-bsctestnet.json +++ b/simulations/vip-580/utils/cut-params-bsctestnet.json @@ -34,12 +34,7 @@ [ "0x2A926859f87C322eEe043B8e5f098e618F92c529", 0, - ["0x1ec8ba02", "0xcd544f24", "0x9e9b1877", "0x13d97e2c", "0xe7ed111e"] - ], - [ - "0x0000000000000000000000000000000000000000", - 2, - ["0xc488847b", "0xa78dc775", "0xc5b4db55", "0xd463654c", "0x4d99c776", "0xd585c3c6"] + ["0x1ec8ba02", "0xcd544f24", "0x9e9b1877", "0x13d97e2c", "0xe7ed111e", "0xa9e87456"] ], [ "0x550F5408D34793a723C22ff84A6872d74D5597f1", @@ -82,7 +77,6 @@ "0x655f0725" ] ], - ["0x03Be0AAd2EADc48892335C6Ac10A71DaD5a81A15", 0, ["0xd463654c", "0xa9e87456", "0x4d99c776", "0xc5b4db55"]], [ "0xe48f7E3F94349962A33D1e909b3F28E14A8770c9", 1, @@ -124,7 +118,22 @@ ] ], ["0xe48f7E3F94349962A33D1e909b3F28E14A8770c9", 0, ["0x38f92fc7", "0x75b74550", "0xc3b9d89a"]], - ["0x0000000000000000000000000000000000000000", 2, ["0x317b0b77", "0x12348e96", "0x35439240", "0x9bd8f6e8"]], - ["0x07347914d067C9227836870D6Be8F78539B91437", 1, ["0x5544ed9c"]] + ["0x07347914d067C9227836870D6Be8F78539B91437", 1, ["0x5544ed9c"]], + [ + "0x0000000000000000000000000000000000000000", + 2, + [ + "0xc488847b", + "0xa78dc775", + "0xc5b4db55", + "0xd463654c", + "0x4d99c776", + "0xd585c3c6", + "0x317b0b77", + "0x12348e96", + "0x35439240", + "0x9bd8f6e8" + ] + ] ] } diff --git a/vips/vip-580/bsctestnet.ts b/vips/vip-580/bsctestnet.ts index d1528f1a0..afa0a0d4f 100644 --- a/vips/vip-580/bsctestnet.ts +++ b/vips/vip-580/bsctestnet.ts @@ -17,7 +17,7 @@ const FAST_TRACK_TIMELOCK = bsctestnet.FAST_TRACK_TIMELOCK; const CRITICAL_TIMELOCK = bsctestnet.CRITICAL_TIMELOCK; export const LIQUIDATOR_PROXY_ADMIN = "0x1469AeB2768931f979a1c957692e32Aa802dd55a"; -export const LIQUIDATION_MANAGER = "0xA0Eef73F94DB337F08f34F9013bCAF8D392289A5"; +export const LIQUIDATION_MANAGER = "0x03CF41c8777A4e359147309F74a53c8b6b4c6969"; export const NEW_COMPTROLLER_LENS = "0x9D542132fa552B6b416944501bF0D689286E1535"; export const NEW_DIAMOND = "0xe492CCD207760fE4Dfa9B83Fd1590632dDE33BAB"; export const NEW_LIQUIDATOR_IMPL = "0xbC89aA9ab926b8491CB7E613A56A13A14BCfa8bc"; @@ -86,7 +86,7 @@ const grantAccessControl = () => { export const vip580 = () => { const meta = { version: "v2", - title: "VIP-580 Liquidation Threshold and Dynamic Liquidation Improvements", + title: "VIP-580 Liquidation improvements for Core Pool on BNB Chain Testnet", description: `#### Summary This VIP introduces enhanced liquidation mechanisms to the Venus Core Pool on BNB Chain Testnet, implementing the following improvements: @@ -94,9 +94,9 @@ This VIP introduces enhanced liquidation mechanisms to the Venus Core Pool on BN #### Description - **Liquidation Threshold**: Adds the concept of liquidation threshold to the Core Pool, enabling more granular control over when positions become eligible for liquidation -- **Dynamic Liquidation Incentive**: Implements dynamic liquidation incentives that adjust based on market conditions and account health factors -- **Dynamic Close Factor**: Introduces dynamic close factors that vary based on borrower position health -- **Maximum Liquidation Incentive**: Defines maximum liquidation incentive limits per seized asset to protect borrowers from excessive liquidation penalties +- **Dynamic Liquidation Incentive**: Implements dynamic liquidation incentives that adjust based on market conditions and users account health factors +- **Dynamic Close Factor**: Introduces dynamic close factors that vary based on borrowers health factor, allowing for more flexible liquidation amounts +- **Maximum Liquidation Incentive**: Defines maximum liquidation incentive per seized asset to protect borrowers from excessive liquidation penalties #### Contract Upgrades @@ -107,17 +107,14 @@ This VIP will perform the following: - Upgrade VAI Controller with updated liquidation logic - Upgrade Liquidator contract to support dynamic incentives - Upgrade all Core Pool vTokens to new implementation -- Update Comptroller Lens for new data structures +- Update Comptroller Lens to reflect new liquidation parameters - Configure new access controls for liquidation parameters #### References - [VIP Pull Request](https://github.com/VenusProtocol/venus-protocol/pull/604) - [Community Forum Discussion](https://community.venus.io) - -#### Disclaimer - -Proposed changes do not guarantee profit and are subject to market risks. Users should conduct independent research before participating.`, +`, forDescription: "Execute this proposal", againstDescription: "Do not execute this proposal", abstainDescription: "Indifferent to execution",