From bc80e437283d1b8d41c839c8d2635f134e749527 Mon Sep 17 00:00:00 2001 From: GitGuru7 Date: Mon, 6 Jul 2026 15:29:26 +0530 Subject: [PATCH 01/15] feat: draft Ceffu Custody BTC fixed rate vault proposal --- .../vip-999/abi/AccessControlManager.json | 157 +++++++++ .../vip-999/abi/AccessControlledERC20.json | 214 +++++++++++++ simulations/vip-999/abi/ResilientOracle.json | 301 ++++++++++++++++++ simulations/vip-999/bscmainnet.ts | 108 +++++++ vips/vip-999/bscmainnet.ts | 178 +++++++++++ 5 files changed, 958 insertions(+) create mode 100644 simulations/vip-999/abi/AccessControlManager.json create mode 100644 simulations/vip-999/abi/AccessControlledERC20.json create mode 100644 simulations/vip-999/abi/ResilientOracle.json create mode 100644 simulations/vip-999/bscmainnet.ts create mode 100644 vips/vip-999/bscmainnet.ts diff --git a/simulations/vip-999/abi/AccessControlManager.json b/simulations/vip-999/abi/AccessControlManager.json new file mode 100644 index 000000000..2ef119947 --- /dev/null +++ b/simulations/vip-999/abi/AccessControlManager.json @@ -0,0 +1,157 @@ +[ + { "inputs": [], "stateMutability": "nonpayable", "type": "constructor" }, + { + "anonymous": false, + "inputs": [ + { "indexed": false, "internalType": "address", "name": "account", "type": "address" }, + { "indexed": false, "internalType": "address", "name": "contractAddress", "type": "address" }, + { "indexed": false, "internalType": "string", "name": "functionSig", "type": "string" } + ], + "name": "PermissionGranted", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { "indexed": false, "internalType": "address", "name": "account", "type": "address" }, + { "indexed": false, "internalType": "address", "name": "contractAddress", "type": "address" }, + { "indexed": false, "internalType": "string", "name": "functionSig", "type": "string" } + ], + "name": "PermissionRevoked", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { "indexed": true, "internalType": "bytes32", "name": "role", "type": "bytes32" }, + { "indexed": true, "internalType": "bytes32", "name": "previousAdminRole", "type": "bytes32" }, + { "indexed": true, "internalType": "bytes32", "name": "newAdminRole", "type": "bytes32" } + ], + "name": "RoleAdminChanged", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { "indexed": true, "internalType": "bytes32", "name": "role", "type": "bytes32" }, + { "indexed": true, "internalType": "address", "name": "account", "type": "address" }, + { "indexed": true, "internalType": "address", "name": "sender", "type": "address" } + ], + "name": "RoleGranted", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { "indexed": true, "internalType": "bytes32", "name": "role", "type": "bytes32" }, + { "indexed": true, "internalType": "address", "name": "account", "type": "address" }, + { "indexed": true, "internalType": "address", "name": "sender", "type": "address" } + ], + "name": "RoleRevoked", + "type": "event" + }, + { + "inputs": [], + "name": "DEFAULT_ADMIN_ROLE", + "outputs": [{ "internalType": "bytes32", "name": "", "type": "bytes32" }], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [{ "internalType": "bytes32", "name": "role", "type": "bytes32" }], + "name": "getRoleAdmin", + "outputs": [{ "internalType": "bytes32", "name": "", "type": "bytes32" }], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { "internalType": "address", "name": "contractAddress", "type": "address" }, + { "internalType": "string", "name": "functionSig", "type": "string" }, + { "internalType": "address", "name": "accountToPermit", "type": "address" } + ], + "name": "giveCallPermission", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { "internalType": "bytes32", "name": "role", "type": "bytes32" }, + { "internalType": "address", "name": "account", "type": "address" } + ], + "name": "grantRole", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { "internalType": "address", "name": "account", "type": "address" }, + { "internalType": "address", "name": "contractAddress", "type": "address" }, + { "internalType": "string", "name": "functionSig", "type": "string" } + ], + "name": "hasPermission", + "outputs": [{ "internalType": "bool", "name": "", "type": "bool" }], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { "internalType": "bytes32", "name": "role", "type": "bytes32" }, + { "internalType": "address", "name": "account", "type": "address" } + ], + "name": "hasRole", + "outputs": [{ "internalType": "bool", "name": "", "type": "bool" }], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { "internalType": "address", "name": "account", "type": "address" }, + { "internalType": "string", "name": "functionSig", "type": "string" } + ], + "name": "isAllowedToCall", + "outputs": [{ "internalType": "bool", "name": "", "type": "bool" }], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { "internalType": "bytes32", "name": "role", "type": "bytes32" }, + { "internalType": "address", "name": "account", "type": "address" } + ], + "name": "renounceRole", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { "internalType": "address", "name": "contractAddress", "type": "address" }, + { "internalType": "string", "name": "functionSig", "type": "string" }, + { "internalType": "address", "name": "accountToRevoke", "type": "address" } + ], + "name": "revokeCallPermission", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { "internalType": "bytes32", "name": "role", "type": "bytes32" }, + { "internalType": "address", "name": "account", "type": "address" } + ], + "name": "revokeRole", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [{ "internalType": "bytes4", "name": "interfaceId", "type": "bytes4" }], + "name": "supportsInterface", + "outputs": [{ "internalType": "bool", "name": "", "type": "bool" }], + "stateMutability": "view", + "type": "function" + } +] diff --git a/simulations/vip-999/abi/AccessControlledERC20.json b/simulations/vip-999/abi/AccessControlledERC20.json new file mode 100644 index 000000000..8fcda808f --- /dev/null +++ b/simulations/vip-999/abi/AccessControlledERC20.json @@ -0,0 +1,214 @@ +[ + { + "inputs": [ + { "internalType": "string", "name": "name_", "type": "string" }, + { "internalType": "string", "name": "symbol_", "type": "string" }, + { "internalType": "uint8", "name": "decimals_", "type": "uint8" }, + { "internalType": "address", "name": "accessControlManager_", "type": "address" } + ], + "stateMutability": "nonpayable", + "type": "constructor" + }, + { "inputs": [], "name": "Unauthorized", "type": "error" }, + { "inputs": [], "name": "ZeroAddressNotAllowed", "type": "error" }, + { + "anonymous": false, + "inputs": [ + { "indexed": true, "internalType": "address", "name": "owner", "type": "address" }, + { "indexed": true, "internalType": "address", "name": "spender", "type": "address" }, + { "indexed": false, "internalType": "uint256", "name": "value", "type": "uint256" } + ], + "name": "Approval", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { "indexed": true, "internalType": "address", "name": "oldAccessControlManager", "type": "address" }, + { "indexed": true, "internalType": "address", "name": "newAccessControlManager", "type": "address" } + ], + "name": "NewAccessControlManager", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { "indexed": true, "internalType": "address", "name": "previousOwner", "type": "address" }, + { "indexed": true, "internalType": "address", "name": "newOwner", "type": "address" } + ], + "name": "OwnershipTransferStarted", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { "indexed": true, "internalType": "address", "name": "previousOwner", "type": "address" }, + { "indexed": true, "internalType": "address", "name": "newOwner", "type": "address" } + ], + "name": "OwnershipTransferred", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { "indexed": true, "internalType": "address", "name": "from", "type": "address" }, + { "indexed": true, "internalType": "address", "name": "to", "type": "address" }, + { "indexed": false, "internalType": "uint256", "name": "value", "type": "uint256" } + ], + "name": "Transfer", + "type": "event" + }, + { "inputs": [], "name": "acceptOwnership", "outputs": [], "stateMutability": "nonpayable", "type": "function" }, + { + "inputs": [], + "name": "accessControlManager", + "outputs": [{ "internalType": "address", "name": "", "type": "address" }], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { "internalType": "address", "name": "owner", "type": "address" }, + { "internalType": "address", "name": "spender", "type": "address" } + ], + "name": "allowance", + "outputs": [{ "internalType": "uint256", "name": "", "type": "uint256" }], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { "internalType": "address", "name": "spender", "type": "address" }, + { "internalType": "uint256", "name": "amount", "type": "uint256" } + ], + "name": "approve", + "outputs": [{ "internalType": "bool", "name": "", "type": "bool" }], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [{ "internalType": "address", "name": "account", "type": "address" }], + "name": "balanceOf", + "outputs": [{ "internalType": "uint256", "name": "", "type": "uint256" }], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { "internalType": "address", "name": "account_", "type": "address" }, + { "internalType": "uint256", "name": "amount_", "type": "uint256" } + ], + "name": "burn", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "decimals", + "outputs": [{ "internalType": "uint8", "name": "", "type": "uint8" }], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { "internalType": "address", "name": "spender", "type": "address" }, + { "internalType": "uint256", "name": "subtractedValue", "type": "uint256" } + ], + "name": "decreaseAllowance", + "outputs": [{ "internalType": "bool", "name": "", "type": "bool" }], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { "internalType": "address", "name": "spender", "type": "address" }, + { "internalType": "uint256", "name": "addedValue", "type": "uint256" } + ], + "name": "increaseAllowance", + "outputs": [{ "internalType": "bool", "name": "", "type": "bool" }], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { "internalType": "address", "name": "account_", "type": "address" }, + { "internalType": "uint256", "name": "amount_", "type": "uint256" } + ], + "name": "mint", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "name", + "outputs": [{ "internalType": "string", "name": "", "type": "string" }], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "owner", + "outputs": [{ "internalType": "address", "name": "", "type": "address" }], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "pendingOwner", + "outputs": [{ "internalType": "address", "name": "", "type": "address" }], + "stateMutability": "view", + "type": "function" + }, + { "inputs": [], "name": "renounceOwnership", "outputs": [], "stateMutability": "nonpayable", "type": "function" }, + { + "inputs": [{ "internalType": "address", "name": "newAccessControlManager_", "type": "address" }], + "name": "setAccessControlManager", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "symbol", + "outputs": [{ "internalType": "string", "name": "", "type": "string" }], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "totalSupply", + "outputs": [{ "internalType": "uint256", "name": "", "type": "uint256" }], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { "internalType": "address", "name": "to", "type": "address" }, + { "internalType": "uint256", "name": "amount", "type": "uint256" } + ], + "name": "transfer", + "outputs": [{ "internalType": "bool", "name": "", "type": "bool" }], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { "internalType": "address", "name": "from", "type": "address" }, + { "internalType": "address", "name": "to", "type": "address" }, + { "internalType": "uint256", "name": "amount", "type": "uint256" } + ], + "name": "transferFrom", + "outputs": [{ "internalType": "bool", "name": "", "type": "bool" }], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [{ "internalType": "address", "name": "newOwner", "type": "address" }], + "name": "transferOwnership", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + } +] diff --git a/simulations/vip-999/abi/ResilientOracle.json b/simulations/vip-999/abi/ResilientOracle.json new file mode 100644 index 000000000..e2f944a9d --- /dev/null +++ b/simulations/vip-999/abi/ResilientOracle.json @@ -0,0 +1,301 @@ +[ + { + "inputs": [ + { "internalType": "address", "name": "nativeMarketAddress", "type": "address" }, + { "internalType": "address", "name": "vaiAddress", "type": "address" }, + { "internalType": "contract BoundValidatorInterface", "name": "_boundValidator", "type": "address" } + ], + "stateMutability": "nonpayable", + "type": "constructor" + }, + { + "inputs": [ + { "internalType": "address", "name": "sender", "type": "address" }, + { "internalType": "address", "name": "calledContract", "type": "address" }, + { "internalType": "string", "name": "methodSignature", "type": "string" } + ], + "name": "Unauthorized", + "type": "error" + }, + { + "anonymous": false, + "inputs": [{ "indexed": false, "internalType": "uint8", "name": "version", "type": "uint8" }], + "name": "Initialized", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { "indexed": false, "internalType": "address", "name": "oldAccessControlManager", "type": "address" }, + { "indexed": false, "internalType": "address", "name": "newAccessControlManager", "type": "address" } + ], + "name": "NewAccessControlManager", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { "indexed": true, "internalType": "address", "name": "asset", "type": "address" }, + { "indexed": true, "internalType": "uint256", "name": "role", "type": "uint256" }, + { "indexed": true, "internalType": "bool", "name": "enable", "type": "bool" } + ], + "name": "OracleEnabled", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { "indexed": true, "internalType": "address", "name": "asset", "type": "address" }, + { "indexed": true, "internalType": "address", "name": "oracle", "type": "address" }, + { "indexed": true, "internalType": "uint256", "name": "role", "type": "uint256" } + ], + "name": "OracleSet", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { "indexed": true, "internalType": "address", "name": "previousOwner", "type": "address" }, + { "indexed": true, "internalType": "address", "name": "newOwner", "type": "address" } + ], + "name": "OwnershipTransferStarted", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { "indexed": true, "internalType": "address", "name": "previousOwner", "type": "address" }, + { "indexed": true, "internalType": "address", "name": "newOwner", "type": "address" } + ], + "name": "OwnershipTransferred", + "type": "event" + }, + { + "anonymous": false, + "inputs": [{ "indexed": false, "internalType": "address", "name": "account", "type": "address" }], + "name": "Paused", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { "indexed": true, "internalType": "address", "name": "asset", "type": "address" }, + { "indexed": true, "internalType": "address", "name": "mainOracle", "type": "address" }, + { "indexed": true, "internalType": "address", "name": "pivotOracle", "type": "address" }, + { "indexed": false, "internalType": "address", "name": "fallbackOracle", "type": "address" } + ], + "name": "TokenConfigAdded", + "type": "event" + }, + { + "anonymous": false, + "inputs": [{ "indexed": false, "internalType": "address", "name": "account", "type": "address" }], + "name": "Unpaused", + "type": "event" + }, + { + "inputs": [], + "name": "INVALID_PRICE", + "outputs": [{ "internalType": "uint256", "name": "", "type": "uint256" }], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "NATIVE_TOKEN_ADDR", + "outputs": [{ "internalType": "address", "name": "", "type": "address" }], + "stateMutability": "view", + "type": "function" + }, + { "inputs": [], "name": "acceptOwnership", "outputs": [], "stateMutability": "nonpayable", "type": "function" }, + { + "inputs": [], + "name": "accessControlManager", + "outputs": [{ "internalType": "contract IAccessControlManagerV8", "name": "", "type": "address" }], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "boundValidator", + "outputs": [{ "internalType": "contract BoundValidatorInterface", "name": "", "type": "address" }], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { "internalType": "address", "name": "asset", "type": "address" }, + { "internalType": "enum ResilientOracle.OracleRole", "name": "role", "type": "uint8" }, + { "internalType": "bool", "name": "enable", "type": "bool" } + ], + "name": "enableOracle", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { "internalType": "address", "name": "asset", "type": "address" }, + { "internalType": "enum ResilientOracle.OracleRole", "name": "role", "type": "uint8" } + ], + "name": "getOracle", + "outputs": [ + { "internalType": "address", "name": "oracle", "type": "address" }, + { "internalType": "bool", "name": "enabled", "type": "bool" } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [{ "internalType": "address", "name": "asset", "type": "address" }], + "name": "getPrice", + "outputs": [{ "internalType": "uint256", "name": "", "type": "uint256" }], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [{ "internalType": "address", "name": "asset", "type": "address" }], + "name": "getTokenConfig", + "outputs": [ + { + "components": [ + { "internalType": "address", "name": "asset", "type": "address" }, + { "internalType": "address[3]", "name": "oracles", "type": "address[3]" }, + { "internalType": "bool[3]", "name": "enableFlagsForOracles", "type": "bool[3]" } + ], + "internalType": "struct ResilientOracle.TokenConfig", + "name": "", + "type": "tuple" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [{ "internalType": "address", "name": "vToken", "type": "address" }], + "name": "getUnderlyingPrice", + "outputs": [{ "internalType": "uint256", "name": "", "type": "uint256" }], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [{ "internalType": "address", "name": "accessControlManager_", "type": "address" }], + "name": "initialize", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "nativeMarket", + "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": "pause", "outputs": [], "stateMutability": "nonpayable", "type": "function" }, + { + "inputs": [], + "name": "paused", + "outputs": [{ "internalType": "bool", "name": "", "type": "bool" }], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "pendingOwner", + "outputs": [{ "internalType": "address", "name": "", "type": "address" }], + "stateMutability": "view", + "type": "function" + }, + { "inputs": [], "name": "renounceOwnership", "outputs": [], "stateMutability": "nonpayable", "type": "function" }, + { + "inputs": [{ "internalType": "address", "name": "accessControlManager_", "type": "address" }], + "name": "setAccessControlManager", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { "internalType": "address", "name": "asset", "type": "address" }, + { "internalType": "address", "name": "oracle", "type": "address" }, + { "internalType": "enum ResilientOracle.OracleRole", "name": "role", "type": "uint8" } + ], + "name": "setOracle", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "components": [ + { "internalType": "address", "name": "asset", "type": "address" }, + { "internalType": "address[3]", "name": "oracles", "type": "address[3]" }, + { "internalType": "bool[3]", "name": "enableFlagsForOracles", "type": "bool[3]" } + ], + "internalType": "struct ResilientOracle.TokenConfig", + "name": "tokenConfig", + "type": "tuple" + } + ], + "name": "setTokenConfig", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "components": [ + { "internalType": "address", "name": "asset", "type": "address" }, + { "internalType": "address[3]", "name": "oracles", "type": "address[3]" }, + { "internalType": "bool[3]", "name": "enableFlagsForOracles", "type": "bool[3]" } + ], + "internalType": "struct ResilientOracle.TokenConfig[]", + "name": "tokenConfigs_", + "type": "tuple[]" + } + ], + "name": "setTokenConfigs", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [{ "internalType": "address", "name": "newOwner", "type": "address" }], + "name": "transferOwnership", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { "inputs": [], "name": "unpause", "outputs": [], "stateMutability": "nonpayable", "type": "function" }, + { + "inputs": [{ "internalType": "address", "name": "asset", "type": "address" }], + "name": "updateAssetPrice", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [{ "internalType": "address", "name": "vToken", "type": "address" }], + "name": "updatePrice", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "vai", + "outputs": [{ "internalType": "address", "name": "", "type": "address" }], + "stateMutability": "view", + "type": "function" + } +] diff --git a/simulations/vip-999/bscmainnet.ts b/simulations/vip-999/bscmainnet.ts new file mode 100644 index 000000000..a4d5ed639 --- /dev/null +++ b/simulations/vip-999/bscmainnet.ts @@ -0,0 +1,108 @@ +import { expect } from "chai"; +import { Contract } from "ethers"; +import { ethers } from "hardhat"; +import { NETWORK_ADDRESSES } from "src/networkAddresses"; +import { expectEvents, initMainnetUser, setMaxStalePeriodForAllAssets } from "src/utils"; +import { forking, testVip } from "src/vip-framework"; + +import vip999, { + BTCB, + FIXED_RATE_VAULT_CONTROLLER, + SUPPLY_ASSET, + VCEBTC, + VCEBTC_INITIAL_SUPPLY, +} from "../../vips/vip-999/bscmainnet"; +import ACM_ABI from "./abi/AccessControlManager.json"; +import ERC20_ABI from "./abi/AccessControlledERC20.json"; +import ORACLE_ABI from "./abi/ResilientOracle.json"; + +const { bscmainnet } = NETWORK_ADDRESSES; + +// ───────────────────────────────────────────────────────────────────────────── +// DRAFT SIMULATION — NOT RUNNABLE YET. +// +// This simulation cannot pass until the following are true (see the VIP header): +// 1. vceBTC is deployed and VCEBTC in the VIP file points to it (it is currently +// the zero address). On a fork, deploy it in the `before()` hook below. +// 2. The InstitutionalVaultController proxy has been upgraded to the 6-param +// `createVault` implementation. The impl live on bscmainnet today is 5-param, +// so the createVault command reverts against the current fork state. +// 3. The underlying oracle feeds resolve a price for vceBTC (cloning the +// ResilientOracle config alone is not enough — see the VIP's oracle TODO). +// 4. The DUMMY vault parameters are replaced with finalized values. +// +// Once the above are addressed, pick a fork block just before the proposal, fill in +// the assertions marked TODO, and remove `.skip`. +// ───────────────────────────────────────────────────────────────────────────── + +const FORK_BLOCK = 0; // TODO: set to a block shortly before the proposal. + +forking(FORK_BLOCK, async () => { + let oracle: Contract; + let acm: Contract; + let vceBTC: Contract; + + before(async () => { + oracle = await ethers.getContractAt(ORACLE_ABI, bscmainnet.RESILIENT_ORACLE); + acm = await ethers.getContractAt(ACM_ABI, bscmainnet.ACCESS_CONTROL_MANAGER); + vceBTC = await ethers.getContractAt(ERC20_ABI, VCEBTC); + + // The fork advances time, so oracle feeds go stale and getPrice reverts. Bump the + // max stale period on the underlying feeds for the priced assets. vceBTC shares + // BTCB's feeds, so bumping BTCB also covers vceBTC's price. + const btcb = await ethers.getContractAt(ERC20_ABI, BTCB); + const usdt = await ethers.getContractAt(ERC20_ABI, SUPPLY_ASSET); + await setMaxStalePeriodForAllAssets(oracle, [btcb, usdt]); + }); + + describe.skip("Pre-VIP behavior", () => { + it("vceBTC has no oracle config yet (getPrice reverts)", async () => { + await expect(oracle.getPrice(VCEBTC)).to.be.reverted; + }); + + it("Timelock cannot yet mint vceBTC", async () => { + expect(await acm.isAllowedToCall(bscmainnet.NORMAL_TIMELOCK, "mint(address,uint256)")).to.equal(false); + }); + }); + + testVip("VIP-999 Create Ceffu Custody BTC Fixed Rate Vault", await vip999(), { + callbackAfterExecution: async txResponse => { + // TODO: confirm the emitted events / counts once params are final. + await expectEvents(txResponse, [ACM_ABI], ["PermissionGranted"], [5]); + }, + }); + + describe.skip("Post-VIP behavior", () => { + it("vceBTC is priced identically to BTCB", async () => { + expect(await oracle.getPrice(VCEBTC)).to.equal(await oracle.getPrice(BTCB)); + }); + + it("Timelock and Guardian can mint/burn vceBTC", async () => { + expect(await acm.isAllowedToCall(bscmainnet.NORMAL_TIMELOCK, "mint(address,uint256)")).to.equal(true); + expect(await acm.isAllowedToCall(bscmainnet.CRITICAL_GUARDIAN, "mint(address,uint256)")).to.equal(true); + expect(await acm.isAllowedToCall(bscmainnet.NORMAL_TIMELOCK, "burn(address,uint256)")).to.equal(true); + expect(await acm.isAllowedToCall(bscmainnet.CRITICAL_GUARDIAN, "burn(address,uint256)")).to.equal(true); + }); + + it("initial vceBTC collateral was minted", async () => { + expect(await vceBTC.totalSupply()).to.equal(VCEBTC_INITIAL_SUPPLY); + }); + + it("a Fixed Rate Vault backed by vceBTC was created", async () => { + const controller = await ethers.getContractAt( + ["function allVaultsLength() view returns (uint256)"], + FIXED_RATE_VAULT_CONTROLLER, + ); + // TODO: assert a new vault is registered and its collateralAsset == vceBTC. + expect(await controller.allVaultsLength()).to.be.gt(0); + }); + }); + + describe.skip("Post-VIP functional", () => { + it("Guardian can mint vceBTC after the grant", async () => { + const guardian = await initMainnetUser(bscmainnet.CRITICAL_GUARDIAN, ethers.utils.parseEther("1")); + // TODO: mint a small amount and assert balance/totalSupply delta. + await expect(vceBTC.connect(guardian).mint(bscmainnet.CRITICAL_GUARDIAN, 1)).to.not.be.reverted; + }); + }); +}); diff --git a/vips/vip-999/bscmainnet.ts b/vips/vip-999/bscmainnet.ts new file mode 100644 index 000000000..55812d538 --- /dev/null +++ b/vips/vip-999/bscmainnet.ts @@ -0,0 +1,178 @@ +import { parseUnits } from "ethers/lib/utils"; +import { NETWORK_ADDRESSES } from "src/networkAddresses"; +import { ProposalType } from "src/types"; +import { makeProposal } from "src/utils"; + +const { bscmainnet } = NETWORK_ADDRESSES; + +// TODO: deployed vceBTC (AccessControlledERC20) address. +export const VCEBTC = "0x0000000000000000000000000000000000000000"; + +export const FIXED_RATE_VAULT_CONTROLLER = "0x6D9e91cB766259af42619c14c994E694E57e6E85"; +export const BTCB = "0x7130d2A12B9BCbFAe4f2634d864A1Ee1Ce3Ead9c"; + +// BTCB's oracle config +export const BTCB_ORACLE_MAIN = "0x1B2103441A0A108daD8848D8F5d790e4D402921F"; +export const BTCB_ORACLE_PIVOT = "0x8455EFA4D7Ff63b8BFD96AdD889483Ea7d39B70a"; +export const BTCB_ORACLE_FALLBACK = "0x9E6928Ec418948ceb9f1cd9872fD312b13D841D0"; +export const BTCB_FEED_MAIN = "0x8ECF7dE377F788A813F5215668E282556b35f300"; +export const BTCB_FEED_PIVOT = "0xa51738d1937FFc553d5070f43300B385AA2D9F55"; +export const BTCB_FEED_FALLBACK = "0x4f6c53fb9CdD46269d24bCa4E68bB680879132fc"; +export const BTCB_STALE_MAIN = 100; +export const BTCB_STALE_PIVOT = 100; +export const BTCB_STALE_FALLBACK = 120; + +export const BOUND_VALIDATOR = "0x6E332fF0bB52475304494E4AE5063c1051c7d735"; +export const BTCB_UPPER_BOUND = parseUnits("1.01", 18); +export const BTCB_LOWER_BOUND = parseUnits("0.99", 18); + +// TODO: supply asset +export const SUPPLY_ASSET = "0x55d398326f99059fF775485246999027B3197955"; + +// TODO: replace with the real Ceffu operator address before proposal +export const INSTITUTION_OPERATOR = "0x1111111111111111111111111111111111111111"; + +// Initial vceBTC collateral supply +export const VCEBTC_INITIAL_SUPPLY = parseUnits("2000", 18); + +// Fixed Rate Vault configuration. TODO: finalize values. +// VaultConfig: [supplyAsset, fixedAPY(bps), reserveFactor(1e18), minBorrowCap, +// maxBorrowCap, minSupplierDeposit, openDuration, lockDuration, settlementWindow] +const vaultConfig = [ + SUPPLY_ASSET, + 800, // fixedAPY = 8% + parseUnits("0.1", 18), // reserveFactor = 10% + parseUnits("1000000", 18), // minBorrowCap = 1M + parseUnits("50000000", 18), // maxBorrowCap = 50M loan market size + 0, // minSupplierDeposit + 7 * 24 * 60 * 60, // openDuration = 7 days + 30 * 24 * 60 * 60, // lockDuration = 30 days + 30 * 24 * 60 * 60, // settlementWindow = 30 days +]; + +// InstitutionalConfig: [collateralAsset, idealCollateralAmount, marginRate(1e18), +// institutionOperator, positionTokenId] +const instConfig = [ + VCEBTC, // collateral = vceBTC + parseUnits("2000", 18), // idealCollateralAmount (sized off-chain from 50% CF + 20% buffer) + parseUnits("0.1", 18), // marginRate = 10% + INSTITUTION_OPERATOR, + 0, // positionTokenId assigned by the controller +]; + +// RiskConfig: [liquidationThreshold(1e18), liquidationIncentive(1e18), latePenaltyRate(1e18)] +const riskConfig = [parseUnits("0.85", 18), parseUnits("1.1", 18), parseUnits("1.1", 18)]; + +// TODO: finalize values +const VAULT_SHARE_NAME = "Venus Ceffu Fixed Rate Vault"; +const VAULT_SHARE_SYMBOL = "vceFRV"; +const INSTITUTION_NAME = "Ceffu"; + +export const vip999 = () => { + const meta = { + version: "v2", + title: "VIP-999 [BNB Chain] Create Ceffu Custody BTC Fixed Rate Vault (DRAFT)", + description: `#### Summary + +This proposal onboards a new custody-mirror collateral token, **vceBTC ("Ceffu Custody BTC for Venus")**, and creates a new Venus **Fixed Rate Vault** (Institutional Loan Vault) that uses vceBTC as collateral. vceBTC is an accounting mirror for BTC held in custody by Ceffu; its supply is controlled by Venus governance and priced identically to BTCB. + +#### Actions + +1. **Oracle** — price vceBTC identically to BTCB by cloning BTCB's full oracle configuration: the main / pivot / fallback sub-oracle feeds, the BoundValidator bounds, and the ResilientOracle token config. +2. **Access control** — grant \`mint(address,uint256)\` and \`burn(address,uint256)\` on vceBTC to the Normal Timelock and the Guardian, and grant \`createVault(...)\` on the InstitutionalVaultController to the Normal Timelock. +3. **Initial supply** — mint the initial vceBTC collateral to the institution operator. +4. **Vault creation** — create the Fixed Rate Vault with vceBTC as collateral.`, + forDescription: "I agree that Venus Protocol should proceed with this proposal", + againstDescription: "I do not think that Venus Protocol should proceed with this proposal", + abstainDescription: "I am indifferent to whether Venus Protocol proceeds or not", + }; + + return makeProposal( + [ + // ────────────────────────────────────────────────────────────────────── + // 1. Oracle configuration identically to BTCB + // ────────────────────────────────────────────────────────────────────── + { + target: BTCB_ORACLE_MAIN, + signature: "setTokenConfig((address,address,uint256))", + params: [[VCEBTC, BTCB_FEED_MAIN, BTCB_STALE_MAIN]], + }, + { + target: BTCB_ORACLE_PIVOT, + signature: "setTokenConfig((address,address,uint256))", + params: [[VCEBTC, BTCB_FEED_PIVOT, BTCB_STALE_PIVOT]], + }, + { + target: BTCB_ORACLE_FALLBACK, + signature: "setTokenConfig((address,address,uint256))", + params: [[VCEBTC, BTCB_FEED_FALLBACK, BTCB_STALE_FALLBACK]], + }, + { + target: BOUND_VALIDATOR, + signature: "setValidateConfig((address,uint256,uint256))", + params: [[VCEBTC, BTCB_UPPER_BOUND, BTCB_LOWER_BOUND]], + }, + { + target: bscmainnet.RESILIENT_ORACLE, + signature: "setTokenConfig((address,address[3],bool[3]))", + params: [[VCEBTC, [BTCB_ORACLE_MAIN, BTCB_ORACLE_PIVOT, BTCB_ORACLE_FALLBACK], [true, true, true]]], + }, + + // ────────────────────────────────────────────────────────────────────── + // 2. Access control — vceBTC mint/burn + createVault + // ────────────────────────────────────────────────────────────────────── + { + target: bscmainnet.ACCESS_CONTROL_MANAGER, + signature: "giveCallPermission(address,string,address)", + params: [VCEBTC, "mint(address,uint256)", bscmainnet.NORMAL_TIMELOCK], + }, + { + target: bscmainnet.ACCESS_CONTROL_MANAGER, + signature: "giveCallPermission(address,string,address)", + params: [VCEBTC, "mint(address,uint256)", bscmainnet.CRITICAL_GUARDIAN], + }, + { + target: bscmainnet.ACCESS_CONTROL_MANAGER, + signature: "giveCallPermission(address,string,address)", + params: [VCEBTC, "burn(address,uint256)", bscmainnet.NORMAL_TIMELOCK], + }, + { + target: bscmainnet.ACCESS_CONTROL_MANAGER, + signature: "giveCallPermission(address,string,address)", + params: [VCEBTC, "burn(address,uint256)", bscmainnet.CRITICAL_GUARDIAN], + }, + { + target: bscmainnet.ACCESS_CONTROL_MANAGER, + signature: "giveCallPermission(address,string,address)", + params: [ + FIXED_RATE_VAULT_CONTROLLER, + "createVault(VaultConfig,InstitutionalConfig,RiskConfig,string,string,string)", + bscmainnet.NORMAL_TIMELOCK, + ], + }, + + // ────────────────────────────────────────────────────────────────────── + // 3. Mint initial vceBTC collateral + // ────────────────────────────────────────────────────────────────────── + { + target: VCEBTC, + signature: "mint(address,uint256)", + params: [INSTITUTION_OPERATOR, VCEBTC_INITIAL_SUPPLY], + }, + + // ────────────────────────────────────────────────────────────────────── + // 4. Create the Fixed Rate Vault (vceBTC as collateral) + // ────────────────────────────────────────────────────────────────────── + { + target: FIXED_RATE_VAULT_CONTROLLER, + signature: + "createVault((address,uint256,uint256,uint256,uint256,uint256,uint40,uint40,uint40),(address,uint256,uint256,address,uint256),(uint256,uint256,uint256),string,string,string)", + params: [vaultConfig, instConfig, riskConfig, VAULT_SHARE_NAME, VAULT_SHARE_SYMBOL, INSTITUTION_NAME], + }, + ], + meta, + ProposalType.REGULAR, + ); +}; + +export default vip999; From 4d103f1682fe54c69a3d1748d150f431524dad37 Mon Sep 17 00:00:00 2001 From: GitGuru7 Date: Mon, 6 Jul 2026 16:41:27 +0530 Subject: [PATCH 02/15] fix: mint initial vceBTC supply to Venus Treasury --- simulations/vip-999/bscmainnet.ts | 3 ++- vips/vip-999/bscmainnet.ts | 4 ++-- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/simulations/vip-999/bscmainnet.ts b/simulations/vip-999/bscmainnet.ts index a4d5ed639..335919b88 100644 --- a/simulations/vip-999/bscmainnet.ts +++ b/simulations/vip-999/bscmainnet.ts @@ -84,8 +84,9 @@ forking(FORK_BLOCK, async () => { expect(await acm.isAllowedToCall(bscmainnet.CRITICAL_GUARDIAN, "burn(address,uint256)")).to.equal(true); }); - it("initial vceBTC collateral was minted", async () => { + it("initial vceBTC collateral was minted to the Venus Treasury", async () => { expect(await vceBTC.totalSupply()).to.equal(VCEBTC_INITIAL_SUPPLY); + expect(await vceBTC.balanceOf(bscmainnet.VTREASURY)).to.equal(VCEBTC_INITIAL_SUPPLY); }); it("a Fixed Rate Vault backed by vceBTC was created", async () => { diff --git a/vips/vip-999/bscmainnet.ts b/vips/vip-999/bscmainnet.ts index 55812d538..b86f94184 100644 --- a/vips/vip-999/bscmainnet.ts +++ b/vips/vip-999/bscmainnet.ts @@ -80,7 +80,7 @@ This proposal onboards a new custody-mirror collateral token, **vceBTC ("Ceffu C 1. **Oracle** — price vceBTC identically to BTCB by cloning BTCB's full oracle configuration: the main / pivot / fallback sub-oracle feeds, the BoundValidator bounds, and the ResilientOracle token config. 2. **Access control** — grant \`mint(address,uint256)\` and \`burn(address,uint256)\` on vceBTC to the Normal Timelock and the Guardian, and grant \`createVault(...)\` on the InstitutionalVaultController to the Normal Timelock. -3. **Initial supply** — mint the initial vceBTC collateral to the institution operator. +3. **Initial supply** — mint the initial vceBTC collateral to the Venus Treasury. 4. **Vault creation** — create the Fixed Rate Vault with vceBTC as collateral.`, forDescription: "I agree that Venus Protocol should proceed with this proposal", againstDescription: "I do not think that Venus Protocol should proceed with this proposal", @@ -157,7 +157,7 @@ This proposal onboards a new custody-mirror collateral token, **vceBTC ("Ceffu C { target: VCEBTC, signature: "mint(address,uint256)", - params: [INSTITUTION_OPERATOR, VCEBTC_INITIAL_SUPPLY], + params: [bscmainnet.VTREASURY, VCEBTC_INITIAL_SUPPLY], }, // ────────────────────────────────────────────────────────────────────── From 7dddf5f79c455ab4917921623ea34d8a3acc80e8 Mon Sep 17 00:00:00 2001 From: GitGuru7 Date: Mon, 6 Jul 2026 17:59:45 +0530 Subject: [PATCH 03/15] feat: add institutional vault ABIs and deployment configuration --- .../vip-999/abi/InstitutionalLoanVault.json | 2012 +++++++++++++++++ .../abi/InstitutionalVaultController.json | 1105 +++++++++ simulations/vip-999/abi/ProxyAdmin.json | 151 ++ simulations/vip-999/abi/ResilientOracle.json | 488 +++- simulations/vip-999/abi/VenusERC20.json | 483 ++++ simulations/vip-999/bscmainnet.ts | 298 ++- vips/vip-999/bscmainnet.ts | 54 +- 7 files changed, 4452 insertions(+), 139 deletions(-) create mode 100644 simulations/vip-999/abi/InstitutionalLoanVault.json create mode 100644 simulations/vip-999/abi/InstitutionalVaultController.json create mode 100644 simulations/vip-999/abi/ProxyAdmin.json create mode 100644 simulations/vip-999/abi/VenusERC20.json diff --git a/simulations/vip-999/abi/InstitutionalLoanVault.json b/simulations/vip-999/abi/InstitutionalLoanVault.json new file mode 100644 index 000000000..21b72547f --- /dev/null +++ b/simulations/vip-999/abi/InstitutionalLoanVault.json @@ -0,0 +1,2012 @@ +[ + { + "type": "constructor", + "inputs": [], + "stateMutability": "nonpayable" + }, + { + "type": "function", + "name": "BPS", + "inputs": [], + "outputs": [ + { + "name": "", + "type": "uint256", + "internalType": "uint256" + } + ], + "stateMutability": "view" + }, + { + "type": "function", + "name": "MANTISSA_ONE", + "inputs": [], + "outputs": [ + { + "name": "", + "type": "uint256", + "internalType": "uint256" + } + ], + "stateMutability": "view" + }, + { + "type": "function", + "name": "YEAR", + "inputs": [], + "outputs": [ + { + "name": "", + "type": "uint256", + "internalType": "uint256" + } + ], + "stateMutability": "view" + }, + { + "type": "function", + "name": "allowance", + "inputs": [ + { + "name": "owner", + "type": "address", + "internalType": "address" + }, + { + "name": "spender", + "type": "address", + "internalType": "address" + } + ], + "outputs": [ + { + "name": "", + "type": "uint256", + "internalType": "uint256" + } + ], + "stateMutability": "view" + }, + { + "type": "function", + "name": "approve", + "inputs": [ + { + "name": "spender", + "type": "address", + "internalType": "address" + }, + { + "name": "amount", + "type": "uint256", + "internalType": "uint256" + } + ], + "outputs": [ + { + "name": "", + "type": "bool", + "internalType": "bool" + } + ], + "stateMutability": "nonpayable" + }, + { + "type": "function", + "name": "asset", + "inputs": [], + "outputs": [ + { + "name": "", + "type": "address", + "internalType": "address" + } + ], + "stateMutability": "view" + }, + { + "type": "function", + "name": "balanceOf", + "inputs": [ + { + "name": "account", + "type": "address", + "internalType": "address" + } + ], + "outputs": [ + { + "name": "", + "type": "uint256", + "internalType": "uint256" + } + ], + "stateMutability": "view" + }, + { + "type": "function", + "name": "calculateSeizeAmount", + "inputs": [ + { + "name": "repayAmount", + "type": "uint256", + "internalType": "uint256" + }, + { + "name": "liquidationType", + "type": "uint8", + "internalType": "enum LiquidationType" + } + ], + "outputs": [ + { + "name": "", + "type": "uint256", + "internalType": "uint256" + } + ], + "stateMutability": "view" + }, + { + "type": "function", + "name": "cancelVault", + "inputs": [], + "outputs": [], + "stateMutability": "nonpayable" + }, + { + "type": "function", + "name": "claimRaisedFunds", + "inputs": [], + "outputs": [], + "stateMutability": "nonpayable" + }, + { + "type": "function", + "name": "closeVault", + "inputs": [], + "outputs": [], + "stateMutability": "nonpayable" + }, + { + "type": "function", + "name": "completePause", + "inputs": [], + "outputs": [], + "stateMutability": "nonpayable" + }, + { + "type": "function", + "name": "config", + "inputs": [], + "outputs": [ + { + "name": "", + "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" + } + ] + } + ], + "stateMutability": "view" + }, + { + "type": "function", + "name": "convertToAssets", + "inputs": [ + { + "name": "shares", + "type": "uint256", + "internalType": "uint256" + } + ], + "outputs": [ + { + "name": "", + "type": "uint256", + "internalType": "uint256" + } + ], + "stateMutability": "view" + }, + { + "type": "function", + "name": "convertToShares", + "inputs": [ + { + "name": "assets", + "type": "uint256", + "internalType": "uint256" + } + ], + "outputs": [ + { + "name": "", + "type": "uint256", + "internalType": "uint256" + } + ], + "stateMutability": "view" + }, + { + "type": "function", + "name": "decimals", + "inputs": [], + "outputs": [ + { + "name": "", + "type": "uint8", + "internalType": "uint8" + } + ], + "stateMutability": "view" + }, + { + "type": "function", + "name": "decreaseAllowance", + "inputs": [ + { + "name": "spender", + "type": "address", + "internalType": "address" + }, + { + "name": "subtractedValue", + "type": "uint256", + "internalType": "uint256" + } + ], + "outputs": [ + { + "name": "", + "type": "bool", + "internalType": "bool" + } + ], + "stateMutability": "nonpayable" + }, + { + "type": "function", + "name": "deposit", + "inputs": [ + { + "name": "assets", + "type": "uint256", + "internalType": "uint256" + }, + { + "name": "receiver", + "type": "address", + "internalType": "address" + } + ], + "outputs": [ + { + "name": "shares", + "type": "uint256", + "internalType": "uint256" + } + ], + "stateMutability": "nonpayable" + }, + { + "type": "function", + "name": "depositCollateral", + "inputs": [ + { + "name": "amount", + "type": "uint256", + "internalType": "uint256" + } + ], + "outputs": [], + "stateMutability": "nonpayable" + }, + { + "type": "function", + "name": "getCollateralValueUSD", + "inputs": [], + "outputs": [ + { + "name": "", + "type": "uint256", + "internalType": "uint256" + } + ], + "stateMutability": "view" + }, + { + "type": "function", + "name": "getDebtValueUSD", + "inputs": [], + "outputs": [ + { + "name": "", + "type": "uint256", + "internalType": "uint256" + } + ], + "stateMutability": "view" + }, + { + "type": "function", + "name": "getHypotheticalVaultLiquidity", + "inputs": [ + { + "name": "withdrawAmount", + "type": "uint256", + "internalType": "uint256" + }, + { + "name": "additionalDebt", + "type": "uint256", + "internalType": "uint256" + } + ], + "outputs": [ + { + "name": "liquidity", + "type": "uint256", + "internalType": "uint256" + }, + { + "name": "shortfall", + "type": "uint256", + "internalType": "uint256" + } + ], + "stateMutability": "view" + }, + { + "type": "function", + "name": "getVaultLiquidity", + "inputs": [], + "outputs": [ + { + "name": "liquidity", + "type": "uint256", + "internalType": "uint256" + }, + { + "name": "shortfall", + "type": "uint256", + "internalType": "uint256" + } + ], + "stateMutability": "view" + }, + { + "type": "function", + "name": "increaseAllowance", + "inputs": [ + { + "name": "spender", + "type": "address", + "internalType": "address" + }, + { + "name": "addedValue", + "type": "uint256", + "internalType": "uint256" + } + ], + "outputs": [ + { + "name": "", + "type": "bool", + "internalType": "bool" + } + ], + "stateMutability": "nonpayable" + }, + { + "type": "function", + "name": "initialize", + "inputs": [ + { + "name": "config_", + "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" + } + ] + }, + { + "name": "instConfig_", + "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" + } + ] + }, + { + "name": "riskConfig_", + "type": "tuple", + "internalType": "struct RiskConfig", + "components": [ + { + "name": "liquidationThreshold", + "type": "uint256", + "internalType": "uint256" + }, + { + "name": "liquidationIncentive", + "type": "uint256", + "internalType": "uint256" + }, + { + "name": "latePenaltyRate", + "type": "uint256", + "internalType": "uint256" + } + ] + }, + { + "name": "positionToken_", + "type": "address", + "internalType": "contract IInstitutionPositionToken" + }, + { + "name": "name_", + "type": "string", + "internalType": "string" + }, + { + "name": "symbol_", + "type": "string", + "internalType": "string" + } + ], + "outputs": [], + "stateMutability": "nonpayable" + }, + { + "type": "function", + "name": "institutionalConfig", + "inputs": [], + "outputs": [ + { + "name": "", + "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" + } + ] + } + ], + "stateMutability": "view" + }, + { + "type": "function", + "name": "institutionalRuntime", + "inputs": [], + "outputs": [ + { + "name": "", + "type": "tuple", + "internalType": "struct InstitutionalRuntime", + "components": [ + { + "name": "totalCollateralDeposited", + "type": "uint256", + "internalType": "uint256" + }, + { + "name": "minimumCollateralRequired", + "type": "uint256", + "internalType": "uint256" + }, + { + "name": "confiscatedMarginRemaining", + "type": "uint256", + "internalType": "uint256" + }, + { + "name": "institutionDefaulted", + "type": "bool", + "internalType": "bool" + } + ] + } + ], + "stateMutability": "view" + }, + { + "type": "function", + "name": "liquidate", + "inputs": [ + { + "name": "repayAmount", + "type": "uint256", + "internalType": "uint256" + } + ], + "outputs": [ + { + "name": "actualRepay", + "type": "uint256", + "internalType": "uint256" + } + ], + "stateMutability": "nonpayable" + }, + { + "type": "function", + "name": "liquidateOverdueVault", + "inputs": [ + { + "name": "repayAmount", + "type": "uint256", + "internalType": "uint256" + } + ], + "outputs": [ + { + "name": "actualRepay", + "type": "uint256", + "internalType": "uint256" + } + ], + "stateMutability": "nonpayable" + }, + { + "type": "function", + "name": "maxDeposit", + "inputs": [ + { + "name": "", + "type": "address", + "internalType": "address" + } + ], + "outputs": [ + { + "name": "", + "type": "uint256", + "internalType": "uint256" + } + ], + "stateMutability": "view" + }, + { + "type": "function", + "name": "maxMint", + "inputs": [ + { + "name": "receiver", + "type": "address", + "internalType": "address" + } + ], + "outputs": [ + { + "name": "", + "type": "uint256", + "internalType": "uint256" + } + ], + "stateMutability": "view" + }, + { + "type": "function", + "name": "maxRedeem", + "inputs": [ + { + "name": "owner", + "type": "address", + "internalType": "address" + } + ], + "outputs": [ + { + "name": "", + "type": "uint256", + "internalType": "uint256" + } + ], + "stateMutability": "view" + }, + { + "type": "function", + "name": "maxWithdraw", + "inputs": [ + { + "name": "owner", + "type": "address", + "internalType": "address" + } + ], + "outputs": [ + { + "name": "", + "type": "uint256", + "internalType": "uint256" + } + ], + "stateMutability": "view" + }, + { + "type": "function", + "name": "mint", + "inputs": [ + { + "name": "shares", + "type": "uint256", + "internalType": "uint256" + }, + { + "name": "receiver", + "type": "address", + "internalType": "address" + } + ], + "outputs": [ + { + "name": "assets", + "type": "uint256", + "internalType": "uint256" + } + ], + "stateMutability": "nonpayable" + }, + { + "type": "function", + "name": "name", + "inputs": [], + "outputs": [ + { + "name": "", + "type": "string", + "internalType": "string" + } + ], + "stateMutability": "view" + }, + { + "type": "function", + "name": "openVault", + "inputs": [], + "outputs": [], + "stateMutability": "nonpayable" + }, + { + "type": "function", + "name": "outstandingDebt", + "inputs": [], + "outputs": [ + { + "name": "", + "type": "uint256", + "internalType": "uint256" + } + ], + "stateMutability": "view" + }, + { + "type": "function", + "name": "partialPause", + "inputs": [], + "outputs": [], + "stateMutability": "nonpayable" + }, + { + "type": "function", + "name": "pauseLevel", + "inputs": [], + "outputs": [ + { + "name": "", + "type": "uint8", + "internalType": "enum PauseLevel" + } + ], + "stateMutability": "view" + }, + { + "type": "function", + "name": "positionToken", + "inputs": [], + "outputs": [ + { + "name": "", + "type": "address", + "internalType": "contract IInstitutionPositionToken" + } + ], + "stateMutability": "view" + }, + { + "type": "function", + "name": "previewDeposit", + "inputs": [ + { + "name": "assets", + "type": "uint256", + "internalType": "uint256" + } + ], + "outputs": [ + { + "name": "", + "type": "uint256", + "internalType": "uint256" + } + ], + "stateMutability": "view" + }, + { + "type": "function", + "name": "previewMint", + "inputs": [ + { + "name": "shares", + "type": "uint256", + "internalType": "uint256" + } + ], + "outputs": [ + { + "name": "", + "type": "uint256", + "internalType": "uint256" + } + ], + "stateMutability": "view" + }, + { + "type": "function", + "name": "previewRedeem", + "inputs": [ + { + "name": "shares", + "type": "uint256", + "internalType": "uint256" + } + ], + "outputs": [ + { + "name": "", + "type": "uint256", + "internalType": "uint256" + } + ], + "stateMutability": "view" + }, + { + "type": "function", + "name": "previewWithdraw", + "inputs": [ + { + "name": "assets", + "type": "uint256", + "internalType": "uint256" + } + ], + "outputs": [ + { + "name": "", + "type": "uint256", + "internalType": "uint256" + } + ], + "stateMutability": "view" + }, + { + "type": "function", + "name": "redeem", + "inputs": [ + { + "name": "shares", + "type": "uint256", + "internalType": "uint256" + }, + { + "name": "receiver", + "type": "address", + "internalType": "address" + }, + { + "name": "owner", + "type": "address", + "internalType": "address" + } + ], + "outputs": [ + { + "name": "assets", + "type": "uint256", + "internalType": "uint256" + } + ], + "stateMutability": "nonpayable" + }, + { + "type": "function", + "name": "repay", + "inputs": [ + { + "name": "amount", + "type": "uint256", + "internalType": "uint256" + } + ], + "outputs": [], + "stateMutability": "nonpayable" + }, + { + "type": "function", + "name": "repayBadDebt", + "inputs": [ + { + "name": "repayAmount", + "type": "uint256", + "internalType": "uint256" + } + ], + "outputs": [], + "stateMutability": "nonpayable" + }, + { + "type": "function", + "name": "riskConfig", + "inputs": [], + "outputs": [ + { + "name": "", + "type": "tuple", + "internalType": "struct RiskConfig", + "components": [ + { + "name": "liquidationThreshold", + "type": "uint256", + "internalType": "uint256" + }, + { + "name": "liquidationIncentive", + "type": "uint256", + "internalType": "uint256" + }, + { + "name": "latePenaltyRate", + "type": "uint256", + "internalType": "uint256" + } + ] + } + ], + "stateMutability": "view" + }, + { + "type": "function", + "name": "runtime", + "inputs": [], + "outputs": [ + { + "name": "", + "type": "tuple", + "internalType": "struct VaultRuntime", + "components": [ + { + "name": "state", + "type": "uint8", + "internalType": "enum VaultState" + }, + { + "name": "openStartTime", + "type": "uint40", + "internalType": "uint40" + }, + { + "name": "openEndTime", + "type": "uint40", + "internalType": "uint40" + }, + { + "name": "lockStartTime", + "type": "uint40", + "internalType": "uint40" + }, + { + "name": "lockEndTime", + "type": "uint40", + "internalType": "uint40" + }, + { + "name": "settlementDeadline", + "type": "uint40", + "internalType": "uint40" + }, + { + "name": "totalRaised", + "type": "uint256", + "internalType": "uint256" + }, + { + "name": "totalDebt", + "type": "uint256", + "internalType": "uint256" + }, + { + "name": "settlementAmount", + "type": "uint256", + "internalType": "uint256" + }, + { + "name": "fundsWithdrawn", + "type": "bool", + "internalType": "bool" + }, + { + "name": "protocolShareSettled", + "type": "bool", + "internalType": "bool" + } + ] + } + ], + "stateMutability": "view" + }, + { + "type": "function", + "name": "setLatePenaltyRate", + "inputs": [ + { + "name": "newRate", + "type": "uint256", + "internalType": "uint256" + } + ], + "outputs": [], + "stateMutability": "nonpayable" + }, + { + "type": "function", + "name": "setLiquidationIncentive", + "inputs": [ + { + "name": "newLI", + "type": "uint256", + "internalType": "uint256" + } + ], + "outputs": [], + "stateMutability": "nonpayable" + }, + { + "type": "function", + "name": "setLiquidationThreshold", + "inputs": [ + { + "name": "newLT", + "type": "uint256", + "internalType": "uint256" + } + ], + "outputs": [], + "stateMutability": "nonpayable" + }, + { + "type": "function", + "name": "state", + "inputs": [], + "outputs": [ + { + "name": "", + "type": "uint8", + "internalType": "enum VaultState" + } + ], + "stateMutability": "view" + }, + { + "type": "function", + "name": "sweep", + "inputs": [ + { + "name": "token", + "type": "address", + "internalType": "address" + } + ], + "outputs": [], + "stateMutability": "nonpayable" + }, + { + "type": "function", + "name": "symbol", + "inputs": [], + "outputs": [ + { + "name": "", + "type": "string", + "internalType": "string" + } + ], + "stateMutability": "view" + }, + { + "type": "function", + "name": "totalAssets", + "inputs": [], + "outputs": [ + { + "name": "", + "type": "uint256", + "internalType": "uint256" + } + ], + "stateMutability": "view" + }, + { + "type": "function", + "name": "totalSupply", + "inputs": [], + "outputs": [ + { + "name": "", + "type": "uint256", + "internalType": "uint256" + } + ], + "stateMutability": "view" + }, + { + "type": "function", + "name": "transfer", + "inputs": [ + { + "name": "to", + "type": "address", + "internalType": "address" + }, + { + "name": "amount", + "type": "uint256", + "internalType": "uint256" + } + ], + "outputs": [ + { + "name": "", + "type": "bool", + "internalType": "bool" + } + ], + "stateMutability": "nonpayable" + }, + { + "type": "function", + "name": "transferFrom", + "inputs": [ + { + "name": "from", + "type": "address", + "internalType": "address" + }, + { + "name": "to", + "type": "address", + "internalType": "address" + }, + { + "name": "amount", + "type": "uint256", + "internalType": "uint256" + } + ], + "outputs": [ + { + "name": "", + "type": "bool", + "internalType": "bool" + } + ], + "stateMutability": "nonpayable" + }, + { + "type": "function", + "name": "unpause", + "inputs": [], + "outputs": [], + "stateMutability": "nonpayable" + }, + { + "type": "function", + "name": "updateVaultState", + "inputs": [], + "outputs": [], + "stateMutability": "nonpayable" + }, + { + "type": "function", + "name": "vaultController", + "inputs": [], + "outputs": [ + { + "name": "", + "type": "address", + "internalType": "address" + } + ], + "stateMutability": "view" + }, + { + "type": "function", + "name": "withdraw", + "inputs": [ + { + "name": "assets", + "type": "uint256", + "internalType": "uint256" + }, + { + "name": "receiver", + "type": "address", + "internalType": "address" + }, + { + "name": "owner", + "type": "address", + "internalType": "address" + } + ], + "outputs": [ + { + "name": "shares", + "type": "uint256", + "internalType": "uint256" + } + ], + "stateMutability": "nonpayable" + }, + { + "type": "function", + "name": "withdrawCollateral", + "inputs": [ + { + "name": "amount", + "type": "uint256", + "internalType": "uint256" + } + ], + "outputs": [], + "stateMutability": "nonpayable" + }, + { + "type": "event", + "name": "Approval", + "inputs": [ + { + "name": "owner", + "type": "address", + "indexed": true, + "internalType": "address" + }, + { + "name": "spender", + "type": "address", + "indexed": true, + "internalType": "address" + }, + { + "name": "value", + "type": "uint256", + "indexed": false, + "internalType": "uint256" + } + ], + "anonymous": false + }, + { + "type": "event", + "name": "CollateralDeposited", + "inputs": [ + { + "name": "amount", + "type": "uint256", + "indexed": false, + "internalType": "uint256" + }, + { + "name": "totalCollateral", + "type": "uint256", + "indexed": false, + "internalType": "uint256" + } + ], + "anonymous": false + }, + { + "type": "event", + "name": "CollateralWithdrawn", + "inputs": [ + { + "name": "positionHolder", + "type": "address", + "indexed": true, + "internalType": "address" + }, + { + "name": "amount", + "type": "uint256", + "indexed": false, + "internalType": "uint256" + }, + { + "name": "remaining", + "type": "uint256", + "indexed": false, + "internalType": "uint256" + } + ], + "anonymous": false + }, + { + "type": "event", + "name": "Deposit", + "inputs": [ + { + "name": "sender", + "type": "address", + "indexed": true, + "internalType": "address" + }, + { + "name": "owner", + "type": "address", + "indexed": true, + "internalType": "address" + }, + { + "name": "assets", + "type": "uint256", + "indexed": false, + "internalType": "uint256" + }, + { + "name": "shares", + "type": "uint256", + "indexed": false, + "internalType": "uint256" + } + ], + "anonymous": false + }, + { + "type": "event", + "name": "Initialized", + "inputs": [ + { + "name": "version", + "type": "uint8", + "indexed": false, + "internalType": "uint8" + } + ], + "anonymous": false + }, + { + "type": "event", + "name": "LatePenaltyRateUpdated", + "inputs": [ + { + "name": "oldRate", + "type": "uint256", + "indexed": false, + "internalType": "uint256" + }, + { + "name": "newRate", + "type": "uint256", + "indexed": false, + "internalType": "uint256" + } + ], + "anonymous": false + }, + { + "type": "event", + "name": "LiquidationExecuted", + "inputs": [ + { + "name": "liquidator", + "type": "address", + "indexed": true, + "internalType": "address" + }, + { + "name": "repayAmount", + "type": "uint256", + "indexed": false, + "internalType": "uint256" + }, + { + "name": "collateralSeized", + "type": "uint256", + "indexed": false, + "internalType": "uint256" + } + ], + "anonymous": false + }, + { + "type": "event", + "name": "LiquidationIncentiveUpdated", + "inputs": [ + { + "name": "oldLI", + "type": "uint256", + "indexed": false, + "internalType": "uint256" + }, + { + "name": "newLI", + "type": "uint256", + "indexed": false, + "internalType": "uint256" + } + ], + "anonymous": false + }, + { + "type": "event", + "name": "LiquidationThresholdUpdated", + "inputs": [ + { + "name": "oldLT", + "type": "uint256", + "indexed": false, + "internalType": "uint256" + }, + { + "name": "newLT", + "type": "uint256", + "indexed": false, + "internalType": "uint256" + } + ], + "anonymous": false + }, + { + "type": "event", + "name": "MarginCompensationClaimed", + "inputs": [ + { + "name": "receiver", + "type": "address", + "indexed": true, + "internalType": "address" + }, + { + "name": "amount", + "type": "uint256", + "indexed": false, + "internalType": "uint256" + } + ], + "anonymous": false + }, + { + "type": "event", + "name": "MarginConfiscated", + "inputs": [ + { + "name": "marginAmount", + "type": "uint256", + "indexed": false, + "internalType": "uint256" + } + ], + "anonymous": false + }, + { + "type": "event", + "name": "OverdueLiquidationExecuted", + "inputs": [ + { + "name": "settler", + "type": "address", + "indexed": true, + "internalType": "address" + }, + { + "name": "repayAmount", + "type": "uint256", + "indexed": false, + "internalType": "uint256" + }, + { + "name": "collateralSeized", + "type": "uint256", + "indexed": false, + "internalType": "uint256" + } + ], + "anonymous": false + }, + { + "type": "event", + "name": "PSRNotificationFailed", + "inputs": [ + { + "name": "psr", + "type": "address", + "indexed": true, + "internalType": "address" + }, + { + "name": "reason", + "type": "bytes", + "indexed": false, + "internalType": "bytes" + } + ], + "anonymous": false + }, + { + "type": "event", + "name": "PauseLevelSet", + "inputs": [ + { + "name": "oldLevel", + "type": "uint8", + "indexed": false, + "internalType": "enum PauseLevel" + }, + { + "name": "newLevel", + "type": "uint8", + "indexed": false, + "internalType": "enum PauseLevel" + } + ], + "anonymous": false + }, + { + "type": "event", + "name": "RaisedFundsClaimed", + "inputs": [ + { + "name": "amount", + "type": "uint256", + "indexed": false, + "internalType": "uint256" + } + ], + "anonymous": false + }, + { + "type": "event", + "name": "Repaid", + "inputs": [ + { + "name": "amount", + "type": "uint256", + "indexed": false, + "internalType": "uint256" + }, + { + "name": "remainingDebt", + "type": "uint256", + "indexed": false, + "internalType": "uint256" + } + ], + "anonymous": false + }, + { + "type": "event", + "name": "SettlementConfirmed", + "inputs": [ + { + "name": "settlementAmount", + "type": "uint256", + "indexed": false, + "internalType": "uint256" + }, + { + "name": "protocolFee", + "type": "uint256", + "indexed": false, + "internalType": "uint256" + }, + { + "name": "surplus", + "type": "uint256", + "indexed": false, + "internalType": "uint256" + } + ], + "anonymous": false + }, + { + "type": "event", + "name": "ShortfallDetected", + "inputs": [ + { + "name": "totalOwed", + "type": "uint256", + "indexed": false, + "internalType": "uint256" + }, + { + "name": "available", + "type": "uint256", + "indexed": false, + "internalType": "uint256" + } + ], + "anonymous": false + }, + { + "type": "event", + "name": "StateTransition", + "inputs": [ + { + "name": "from", + "type": "uint8", + "indexed": true, + "internalType": "enum VaultState" + }, + { + "name": "to", + "type": "uint8", + "indexed": true, + "internalType": "enum VaultState" + }, + { + "name": "timestamp", + "type": "uint256", + "indexed": false, + "internalType": "uint256" + } + ], + "anonymous": false + }, + { + "type": "event", + "name": "TokensSwept", + "inputs": [ + { + "name": "token", + "type": "address", + "indexed": true, + "internalType": "address" + }, + { + "name": "recipient", + "type": "address", + "indexed": true, + "internalType": "address" + }, + { + "name": "amount", + "type": "uint256", + "indexed": false, + "internalType": "uint256" + } + ], + "anonymous": false + }, + { + "type": "event", + "name": "Transfer", + "inputs": [ + { + "name": "from", + "type": "address", + "indexed": true, + "internalType": "address" + }, + { + "name": "to", + "type": "address", + "indexed": true, + "internalType": "address" + }, + { + "name": "value", + "type": "uint256", + "indexed": false, + "internalType": "uint256" + } + ], + "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", + "inputs": [ + { + "name": "state", + "type": "uint8", + "indexed": false, + "internalType": "enum VaultState" + } + ], + "anonymous": false + }, + { + "type": "event", + "name": "VaultFailed", + "inputs": [ + { + "name": "totalRaised", + "type": "uint256", + "indexed": false, + "internalType": "uint256" + }, + { + "name": "minBorrowCap", + "type": "uint256", + "indexed": false, + "internalType": "uint256" + } + ], + "anonymous": false + }, + { + "type": "event", + "name": "VaultLiquidated", + "inputs": [ + { + "name": "available", + "type": "uint256", + "indexed": false, + "internalType": "uint256" + } + ], + "anonymous": false + }, + { + "type": "event", + "name": "VaultLocked", + "inputs": [ + { + "name": "totalRaised", + "type": "uint256", + "indexed": false, + "internalType": "uint256" + }, + { + "name": "lockEndTime", + "type": "uint256", + "indexed": false, + "internalType": "uint256" + } + ], + "anonymous": false + }, + { + "type": "event", + "name": "VaultOpened", + "inputs": [ + { + "name": "openEndTime", + "type": "uint256", + "indexed": false, + "internalType": "uint256" + } + ], + "anonymous": false + }, + { + "type": "event", + "name": "Withdraw", + "inputs": [ + { + "name": "sender", + "type": "address", + "indexed": true, + "internalType": "address" + }, + { + "name": "receiver", + "type": "address", + "indexed": true, + "internalType": "address" + }, + { + "name": "owner", + "type": "address", + "indexed": true, + "internalType": "address" + }, + { + "name": "assets", + "type": "uint256", + "indexed": false, + "internalType": "uint256" + }, + { + "name": "shares", + "type": "uint256", + "indexed": false, + "internalType": "uint256" + } + ], + "anonymous": false + }, + { + "type": "error", + "name": "AlreadyWithdrawn", + "inputs": [] + }, + { + "type": "error", + "name": "BelowMinimumDepositAmount", + "inputs": [] + }, + { + "type": "error", + "name": "ClaimWouldBreachLT", + "inputs": [] + }, + { + "type": "error", + "name": "CompletelyPaused", + "inputs": [] + }, + { + "type": "error", + "name": "ExceedsCloseFactor", + "inputs": [] + }, + { + "type": "error", + "name": "ExceedsMaxCap", + "inputs": [] + }, + { + "type": "error", + "name": "InsufficientCollateral", + "inputs": [] + }, + { + "type": "error", + "name": "InsufficientCollateralForSeize", + "inputs": [ + { + "name": "seizeAmount", + "type": "uint256", + "internalType": "uint256" + }, + { + "name": "availableCollateral", + "type": "uint256", + "internalType": "uint256" + } + ] + }, + { + "type": "error", + "name": "InsufficientRepayment", + "inputs": [] + }, + { + "type": "error", + "name": "InvalidOraclePrice", + "inputs": [] + }, + { + "type": "error", + "name": "InvalidState", + "inputs": [] + }, + { + "type": "error", + "name": "InvalidStateForOverdueLiquidation", + "inputs": [] + }, + { + "type": "error", + "name": "NoOutstandingDebt", + "inputs": [] + }, + { + "type": "error", + "name": "NotBadDebt", + "inputs": [] + }, + { + "type": "error", + "name": "NotLiquidatable", + "inputs": [] + }, + { + "type": "error", + "name": "NotPositionHolder", + "inputs": [] + }, + { + "type": "error", + "name": "NothingToSweep", + "inputs": [] + }, + { + "type": "error", + "name": "PartiallyPaused", + "inputs": [] + }, + { + "type": "error", + "name": "SameStateTransition", + "inputs": [] + }, + { + "type": "error", + "name": "Unauthorized", + "inputs": [] + }, + { + "type": "error", + "name": "WithdrawExceedsCollateral", + "inputs": [] + }, + { + "type": "error", + "name": "WithdrawalWouldBreachLT", + "inputs": [] + }, + { + "type": "error", + "name": "ZeroRepayAmount", + "inputs": [] + } +] diff --git a/simulations/vip-999/abi/InstitutionalVaultController.json b/simulations/vip-999/abi/InstitutionalVaultController.json new file mode 100644 index 000000000..408194b97 --- /dev/null +++ b/simulations/vip-999/abi/InstitutionalVaultController.json @@ -0,0 +1,1105 @@ +[ + { + "type": "constructor", + "inputs": [], + "stateMutability": "nonpayable" + }, + { + "type": "function", + "name": "MANTISSA_ONE", + "inputs": [], + "outputs": [ + { + "name": "", + "type": "uint256", + "internalType": "uint256" + } + ], + "stateMutability": "view" + }, + { + "type": "function", + "name": "MANTISSA_ONE_AND_HALF", + "inputs": [], + "outputs": [ + { + "name": "", + "type": "uint256", + "internalType": "uint256" + } + ], + "stateMutability": "view" + }, + { + "type": "function", + "name": "MAX_APY_BPS", + "inputs": [], + "outputs": [ + { + "name": "", + "type": "uint256", + "internalType": "uint256" + } + ], + "stateMutability": "view" + }, + { + "type": "function", + "name": "acceptOwnership", + "inputs": [], + "outputs": [], + "stateMutability": "nonpayable" + }, + { + "type": "function", + "name": "acceptPositionTokenOwnership", + "inputs": [], + "outputs": [], + "stateMutability": "nonpayable" + }, + { + "type": "function", + "name": "accessControlManager", + "inputs": [], + "outputs": [ + { + "name": "", + "type": "address", + "internalType": "contract IAccessControlManagerV8" + } + ], + "stateMutability": "view" + }, + { + "type": "function", + "name": "allVaults", + "inputs": [ + { + "name": "", + "type": "uint256", + "internalType": "uint256" + } + ], + "outputs": [ + { + "name": "", + "type": "address", + "internalType": "address" + } + ], + "stateMutability": "view" + }, + { + "type": "function", + "name": "allVaultsLength", + "inputs": [], + "outputs": [ + { + "name": "", + "type": "uint256", + "internalType": "uint256" + } + ], + "stateMutability": "view" + }, + { + "type": "function", + "name": "approvePositionTransfer", + "inputs": [ + { + "name": "vault", + "type": "address", + "internalType": "address" + }, + { + "name": "recipient", + "type": "address", + "internalType": "address" + } + ], + "outputs": [], + "stateMutability": "nonpayable" + }, + { + "type": "function", + "name": "cancelVault", + "inputs": [ + { + "name": "vault", + "type": "address", + "internalType": "address" + } + ], + "outputs": [], + "stateMutability": "nonpayable" + }, + { + "type": "function", + "name": "closeVault", + "inputs": [ + { + "name": "vault", + "type": "address", + "internalType": "address" + } + ], + "outputs": [], + "stateMutability": "nonpayable" + }, + { + "type": "function", + "name": "completePauseVault", + "inputs": [ + { + "name": "vault", + "type": "address", + "internalType": "address" + } + ], + "outputs": [], + "stateMutability": "nonpayable" + }, + { + "type": "function", + "name": "comptroller", + "inputs": [], + "outputs": [ + { + "name": "", + "type": "address", + "internalType": "address" + } + ], + "stateMutability": "view" + }, + { + "type": "function", + "name": "createVault", + "inputs": [ + { + "name": "_vaultConfig", + "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" + } + ] + }, + { + "name": "_instConfig", + "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" + } + ] + }, + { + "name": "_riskConfig", + "type": "tuple", + "internalType": "struct RiskConfig", + "components": [ + { + "name": "liquidationThreshold", + "type": "uint256", + "internalType": "uint256" + }, + { + "name": "liquidationIncentive", + "type": "uint256", + "internalType": "uint256" + }, + { + "name": "latePenaltyRate", + "type": "uint256", + "internalType": "uint256" + } + ] + }, + { + "name": "_name", + "type": "string", + "internalType": "string" + }, + { + "name": "_symbol", + "type": "string", + "internalType": "string" + }, + { + "name": "_institutionName", + "type": "string", + "internalType": "string" + } + ], + "outputs": [ + { + "name": "vault", + "type": "address", + "internalType": "address" + } + ], + "stateMutability": "nonpayable" + }, + { + "type": "function", + "name": "getAggregatedVaultStates", + "inputs": [], + "outputs": [ + { + "name": "", + "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", + "name": "initialize", + "inputs": [ + { + "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" + } + ], + "outputs": [], + "stateMutability": "nonpayable" + }, + { + "type": "function", + "name": "institutionNonce", + "inputs": [ + { + "name": "", + "type": "address", + "internalType": "address" + } + ], + "outputs": [ + { + "name": "", + "type": "uint256", + "internalType": "uint256" + } + ], + "stateMutability": "view" + }, + { + "type": "function", + "name": "isRegistered", + "inputs": [ + { + "name": "", + "type": "address", + "internalType": "address" + } + ], + "outputs": [ + { + "name": "", + "type": "bool", + "internalType": "bool" + } + ], + "stateMutability": "view" + }, + { + "type": "function", + "name": "liquidationAdapter", + "inputs": [], + "outputs": [ + { + "name": "", + "type": "address", + "internalType": "address" + } + ], + "stateMutability": "view" + }, + { + "type": "function", + "name": "openVault", + "inputs": [ + { + "name": "vault", + "type": "address", + "internalType": "address" + } + ], + "outputs": [], + "stateMutability": "nonpayable" + }, + { + "type": "function", + "name": "oracle", + "inputs": [], + "outputs": [ + { + "name": "", + "type": "address", + "internalType": "address" + } + ], + "stateMutability": "view" + }, + { + "type": "function", + "name": "owner", + "inputs": [], + "outputs": [ + { + "name": "", + "type": "address", + "internalType": "address" + } + ], + "stateMutability": "view" + }, + { + "type": "function", + "name": "partialPauseVault", + "inputs": [ + { + "name": "vault", + "type": "address", + "internalType": "address" + } + ], + "outputs": [], + "stateMutability": "nonpayable" + }, + { + "type": "function", + "name": "pendingOwner", + "inputs": [], + "outputs": [ + { + "name": "", + "type": "address", + "internalType": "address" + } + ], + "stateMutability": "view" + }, + { + "type": "function", + "name": "positionToken", + "inputs": [], + "outputs": [ + { + "name": "", + "type": "address", + "internalType": "contract IInstitutionPositionToken" + } + ], + "stateMutability": "view" + }, + { + "type": "function", + "name": "predictVaultAddress", + "inputs": [ + { + "name": "institution", + "type": "address", + "internalType": "address" + } + ], + "outputs": [ + { + "name": "", + "type": "address", + "internalType": "address" + } + ], + "stateMutability": "view" + }, + { + "type": "function", + "name": "protocolShareReserve", + "inputs": [], + "outputs": [ + { + "name": "", + "type": "address", + "internalType": "address" + } + ], + "stateMutability": "view" + }, + { + "type": "function", + "name": "renounceOwnership", + "inputs": [], + "outputs": [], + "stateMutability": "pure" + }, + { + "type": "function", + "name": "revokePositionTransfer", + "inputs": [ + { + "name": "vault", + "type": "address", + "internalType": "address" + } + ], + "outputs": [], + "stateMutability": "nonpayable" + }, + { + "type": "function", + "name": "setAccessControlManager", + "inputs": [ + { + "name": "accessControlManager_", + "type": "address", + "internalType": "address" + } + ], + "outputs": [], + "stateMutability": "nonpayable" + }, + { + "type": "function", + "name": "setComptroller", + "inputs": [ + { + "name": "comptroller_", + "type": "address", + "internalType": "address" + } + ], + "outputs": [], + "stateMutability": "nonpayable" + }, + { + "type": "function", + "name": "setLatePenaltyRate", + "inputs": [ + { + "name": "vault", + "type": "address", + "internalType": "address" + }, + { + "name": "newRate", + "type": "uint256", + "internalType": "uint256" + } + ], + "outputs": [], + "stateMutability": "nonpayable" + }, + { + "type": "function", + "name": "setLiquidationAdapter", + "inputs": [ + { + "name": "adapter", + "type": "address", + "internalType": "address" + } + ], + "outputs": [], + "stateMutability": "nonpayable" + }, + { + "type": "function", + "name": "setLiquidationIncentive", + "inputs": [ + { + "name": "vault", + "type": "address", + "internalType": "address" + }, + { + "name": "newLI", + "type": "uint256", + "internalType": "uint256" + } + ], + "outputs": [], + "stateMutability": "nonpayable" + }, + { + "type": "function", + "name": "setLiquidationThreshold", + "inputs": [ + { + "name": "vault", + "type": "address", + "internalType": "address" + }, + { + "name": "newLT", + "type": "uint256", + "internalType": "uint256" + } + ], + "outputs": [], + "stateMutability": "nonpayable" + }, + { + "type": "function", + "name": "setOracle", + "inputs": [ + { + "name": "oracle_", + "type": "address", + "internalType": "address" + } + ], + "outputs": [], + "stateMutability": "nonpayable" + }, + { + "type": "function", + "name": "setProtocolShareReserve", + "inputs": [ + { + "name": "psr", + "type": "address", + "internalType": "address" + } + ], + "outputs": [], + "stateMutability": "nonpayable" + }, + { + "type": "function", + "name": "setTreasury", + "inputs": [ + { + "name": "treasury_", + "type": "address", + "internalType": "address" + } + ], + "outputs": [], + "stateMutability": "nonpayable" + }, + { + "type": "function", + "name": "setVaultImplementation", + "inputs": [ + { + "name": "impl", + "type": "address", + "internalType": "address" + } + ], + "outputs": [], + "stateMutability": "nonpayable" + }, + { + "type": "function", + "name": "sweep", + "inputs": [ + { + "name": "vault", + "type": "address", + "internalType": "address" + }, + { + "name": "token", + "type": "address", + "internalType": "address" + } + ], + "outputs": [], + "stateMutability": "nonpayable" + }, + { + "type": "function", + "name": "transferOwnership", + "inputs": [ + { + "name": "newOwner", + "type": "address", + "internalType": "address" + } + ], + "outputs": [], + "stateMutability": "nonpayable" + }, + { + "type": "function", + "name": "treasury", + "inputs": [], + "outputs": [ + { + "name": "", + "type": "address", + "internalType": "address" + } + ], + "stateMutability": "view" + }, + { + "type": "function", + "name": "unpauseVault", + "inputs": [ + { + "name": "vault", + "type": "address", + "internalType": "address" + } + ], + "outputs": [], + "stateMutability": "nonpayable" + }, + { + "type": "function", + "name": "vaultImplementation", + "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-999/abi/ProxyAdmin.json b/simulations/vip-999/abi/ProxyAdmin.json new file mode 100644 index 000000000..b4c51d8df --- /dev/null +++ b/simulations/vip-999/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-999/abi/ResilientOracle.json b/simulations/vip-999/abi/ResilientOracle.json index e2f944a9d..927d27526 100644 --- a/simulations/vip-999/abi/ResilientOracle.json +++ b/simulations/vip-999/abi/ResilientOracle.json @@ -1,33 +1,74 @@ [ { "inputs": [ - { "internalType": "address", "name": "nativeMarketAddress", "type": "address" }, - { "internalType": "address", "name": "vaiAddress", "type": "address" }, - { "internalType": "contract BoundValidatorInterface", "name": "_boundValidator", "type": "address" } + { + "internalType": "address", + "name": "nativeMarketAddress", + "type": "address" + }, + { + "internalType": "address", + "name": "vaiAddress", + "type": "address" + }, + { + "internalType": "contract BoundValidatorInterface", + "name": "_boundValidator", + "type": "address" + } ], "stateMutability": "nonpayable", "type": "constructor" }, { "inputs": [ - { "internalType": "address", "name": "sender", "type": "address" }, - { "internalType": "address", "name": "calledContract", "type": "address" }, - { "internalType": "string", "name": "methodSignature", "type": "string" } + { + "internalType": "address", + "name": "sender", + "type": "address" + }, + { + "internalType": "address", + "name": "calledContract", + "type": "address" + }, + { + "internalType": "string", + "name": "methodSignature", + "type": "string" + } ], "name": "Unauthorized", "type": "error" }, { "anonymous": false, - "inputs": [{ "indexed": false, "internalType": "uint8", "name": "version", "type": "uint8" }], + "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" } + { + "indexed": false, + "internalType": "address", + "name": "oldAccessControlManager", + "type": "address" + }, + { + "indexed": false, + "internalType": "address", + "name": "newAccessControlManager", + "type": "address" + } ], "name": "NewAccessControlManager", "type": "event" @@ -35,9 +76,24 @@ { "anonymous": false, "inputs": [ - { "indexed": true, "internalType": "address", "name": "asset", "type": "address" }, - { "indexed": true, "internalType": "uint256", "name": "role", "type": "uint256" }, - { "indexed": true, "internalType": "bool", "name": "enable", "type": "bool" } + { + "indexed": true, + "internalType": "address", + "name": "asset", + "type": "address" + }, + { + "indexed": true, + "internalType": "uint256", + "name": "role", + "type": "uint256" + }, + { + "indexed": true, + "internalType": "bool", + "name": "enable", + "type": "bool" + } ], "name": "OracleEnabled", "type": "event" @@ -45,9 +101,24 @@ { "anonymous": false, "inputs": [ - { "indexed": true, "internalType": "address", "name": "asset", "type": "address" }, - { "indexed": true, "internalType": "address", "name": "oracle", "type": "address" }, - { "indexed": true, "internalType": "uint256", "name": "role", "type": "uint256" } + { + "indexed": true, + "internalType": "address", + "name": "asset", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "oracle", + "type": "address" + }, + { + "indexed": true, + "internalType": "uint256", + "name": "role", + "type": "uint256" + } ], "name": "OracleSet", "type": "event" @@ -55,8 +126,18 @@ { "anonymous": false, "inputs": [ - { "indexed": true, "internalType": "address", "name": "previousOwner", "type": "address" }, - { "indexed": true, "internalType": "address", "name": "newOwner", "type": "address" } + { + "indexed": true, + "internalType": "address", + "name": "previousOwner", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "newOwner", + "type": "address" + } ], "name": "OwnershipTransferStarted", "type": "event" @@ -64,69 +145,155 @@ { "anonymous": false, "inputs": [ - { "indexed": true, "internalType": "address", "name": "previousOwner", "type": "address" }, - { "indexed": true, "internalType": "address", "name": "newOwner", "type": "address" } + { + "indexed": true, + "internalType": "address", + "name": "previousOwner", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "newOwner", + "type": "address" + } ], "name": "OwnershipTransferred", "type": "event" }, { "anonymous": false, - "inputs": [{ "indexed": false, "internalType": "address", "name": "account", "type": "address" }], + "inputs": [ + { + "indexed": false, + "internalType": "address", + "name": "account", + "type": "address" + } + ], "name": "Paused", "type": "event" }, { "anonymous": false, "inputs": [ - { "indexed": true, "internalType": "address", "name": "asset", "type": "address" }, - { "indexed": true, "internalType": "address", "name": "mainOracle", "type": "address" }, - { "indexed": true, "internalType": "address", "name": "pivotOracle", "type": "address" }, - { "indexed": false, "internalType": "address", "name": "fallbackOracle", "type": "address" } + { + "indexed": true, + "internalType": "address", + "name": "asset", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "mainOracle", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "pivotOracle", + "type": "address" + }, + { + "indexed": false, + "internalType": "address", + "name": "fallbackOracle", + "type": "address" + } ], "name": "TokenConfigAdded", "type": "event" }, { "anonymous": false, - "inputs": [{ "indexed": false, "internalType": "address", "name": "account", "type": "address" }], + "inputs": [ + { + "indexed": false, + "internalType": "address", + "name": "account", + "type": "address" + } + ], "name": "Unpaused", "type": "event" }, { "inputs": [], "name": "INVALID_PRICE", - "outputs": [{ "internalType": "uint256", "name": "", "type": "uint256" }], + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], "stateMutability": "view", "type": "function" }, { "inputs": [], "name": "NATIVE_TOKEN_ADDR", - "outputs": [{ "internalType": "address", "name": "", "type": "address" }], + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], "stateMutability": "view", "type": "function" }, - { "inputs": [], "name": "acceptOwnership", "outputs": [], "stateMutability": "nonpayable", "type": "function" }, + { + "inputs": [], + "name": "acceptOwnership", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, { "inputs": [], "name": "accessControlManager", - "outputs": [{ "internalType": "contract IAccessControlManagerV8", "name": "", "type": "address" }], + "outputs": [ + { + "internalType": "contract IAccessControlManagerV8", + "name": "", + "type": "address" + } + ], "stateMutability": "view", "type": "function" }, { "inputs": [], "name": "boundValidator", - "outputs": [{ "internalType": "contract BoundValidatorInterface", "name": "", "type": "address" }], + "outputs": [ + { + "internalType": "contract BoundValidatorInterface", + "name": "", + "type": "address" + } + ], "stateMutability": "view", "type": "function" }, { "inputs": [ - { "internalType": "address", "name": "asset", "type": "address" }, - { "internalType": "enum ResilientOracle.OracleRole", "name": "role", "type": "uint8" }, - { "internalType": "bool", "name": "enable", "type": "bool" } + { + "internalType": "address", + "name": "asset", + "type": "address" + }, + { + "internalType": "enum ResilientOracle.OracleRole", + "name": "role", + "type": "uint8" + }, + { + "internalType": "bool", + "name": "enable", + "type": "bool" + } ], "name": "enableOracle", "outputs": [], @@ -135,33 +302,84 @@ }, { "inputs": [ - { "internalType": "address", "name": "asset", "type": "address" }, - { "internalType": "enum ResilientOracle.OracleRole", "name": "role", "type": "uint8" } + { + "internalType": "address", + "name": "asset", + "type": "address" + }, + { + "internalType": "enum ResilientOracle.OracleRole", + "name": "role", + "type": "uint8" + } ], "name": "getOracle", "outputs": [ - { "internalType": "address", "name": "oracle", "type": "address" }, - { "internalType": "bool", "name": "enabled", "type": "bool" } + { + "internalType": "address", + "name": "oracle", + "type": "address" + }, + { + "internalType": "bool", + "name": "enabled", + "type": "bool" + } ], "stateMutability": "view", "type": "function" }, { - "inputs": [{ "internalType": "address", "name": "asset", "type": "address" }], + "inputs": [ + { + "internalType": "address", + "name": "asset", + "type": "address" + } + ], "name": "getPrice", - "outputs": [{ "internalType": "uint256", "name": "", "type": "uint256" }], + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], "stateMutability": "view", "type": "function" }, { - "inputs": [{ "internalType": "address", "name": "asset", "type": "address" }], + "inputs": [ + { + "internalType": "address", + "name": "asset", + "type": "address" + } + ], "name": "getTokenConfig", "outputs": [ { "components": [ - { "internalType": "address", "name": "asset", "type": "address" }, - { "internalType": "address[3]", "name": "oracles", "type": "address[3]" }, - { "internalType": "bool[3]", "name": "enableFlagsForOracles", "type": "bool[3]" } + { + "internalType": "address", + "name": "asset", + "type": "address" + }, + { + "internalType": "address[3]", + "name": "oracles", + "type": "address[3]" + }, + { + "internalType": "bool[3]", + "name": "enableFlagsForOracles", + "type": "bool[3]" + }, + { + "internalType": "bool", + "name": "cachingEnabled", + "type": "bool" + } ], "internalType": "struct ResilientOracle.TokenConfig", "name": "", @@ -172,14 +390,32 @@ "type": "function" }, { - "inputs": [{ "internalType": "address", "name": "vToken", "type": "address" }], + "inputs": [ + { + "internalType": "address", + "name": "vToken", + "type": "address" + } + ], "name": "getUnderlyingPrice", - "outputs": [{ "internalType": "uint256", "name": "", "type": "uint256" }], + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], "stateMutability": "view", "type": "function" }, { - "inputs": [{ "internalType": "address", "name": "accessControlManager_", "type": "address" }], + "inputs": [ + { + "internalType": "address", + "name": "accessControlManager_", + "type": "address" + } + ], "name": "initialize", "outputs": [], "stateMutability": "nonpayable", @@ -188,35 +424,77 @@ { "inputs": [], "name": "nativeMarket", - "outputs": [{ "internalType": "address", "name": "", "type": "address" }], + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], "stateMutability": "view", "type": "function" }, { "inputs": [], "name": "owner", - "outputs": [{ "internalType": "address", "name": "", "type": "address" }], + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], "stateMutability": "view", "type": "function" }, - { "inputs": [], "name": "pause", "outputs": [], "stateMutability": "nonpayable", "type": "function" }, + { + "inputs": [], + "name": "pause", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, { "inputs": [], "name": "paused", - "outputs": [{ "internalType": "bool", "name": "", "type": "bool" }], + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], "stateMutability": "view", "type": "function" }, { "inputs": [], "name": "pendingOwner", - "outputs": [{ "internalType": "address", "name": "", "type": "address" }], + "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" }], + "inputs": [], + "name": "renounceOwnership", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "accessControlManager_", + "type": "address" + } + ], "name": "setAccessControlManager", "outputs": [], "stateMutability": "nonpayable", @@ -224,9 +502,21 @@ }, { "inputs": [ - { "internalType": "address", "name": "asset", "type": "address" }, - { "internalType": "address", "name": "oracle", "type": "address" }, - { "internalType": "enum ResilientOracle.OracleRole", "name": "role", "type": "uint8" } + { + "internalType": "address", + "name": "asset", + "type": "address" + }, + { + "internalType": "address", + "name": "oracle", + "type": "address" + }, + { + "internalType": "enum ResilientOracle.OracleRole", + "name": "role", + "type": "uint8" + } ], "name": "setOracle", "outputs": [], @@ -237,9 +527,26 @@ "inputs": [ { "components": [ - { "internalType": "address", "name": "asset", "type": "address" }, - { "internalType": "address[3]", "name": "oracles", "type": "address[3]" }, - { "internalType": "bool[3]", "name": "enableFlagsForOracles", "type": "bool[3]" } + { + "internalType": "address", + "name": "asset", + "type": "address" + }, + { + "internalType": "address[3]", + "name": "oracles", + "type": "address[3]" + }, + { + "internalType": "bool[3]", + "name": "enableFlagsForOracles", + "type": "bool[3]" + }, + { + "internalType": "bool", + "name": "cachingEnabled", + "type": "bool" + } ], "internalType": "struct ResilientOracle.TokenConfig", "name": "tokenConfig", @@ -255,9 +562,26 @@ "inputs": [ { "components": [ - { "internalType": "address", "name": "asset", "type": "address" }, - { "internalType": "address[3]", "name": "oracles", "type": "address[3]" }, - { "internalType": "bool[3]", "name": "enableFlagsForOracles", "type": "bool[3]" } + { + "internalType": "address", + "name": "asset", + "type": "address" + }, + { + "internalType": "address[3]", + "name": "oracles", + "type": "address[3]" + }, + { + "internalType": "bool[3]", + "name": "enableFlagsForOracles", + "type": "bool[3]" + }, + { + "internalType": "bool", + "name": "cachingEnabled", + "type": "bool" + } ], "internalType": "struct ResilientOracle.TokenConfig[]", "name": "tokenConfigs_", @@ -270,22 +594,46 @@ "type": "function" }, { - "inputs": [{ "internalType": "address", "name": "newOwner", "type": "address" }], + "inputs": [ + { + "internalType": "address", + "name": "newOwner", + "type": "address" + } + ], "name": "transferOwnership", "outputs": [], "stateMutability": "nonpayable", "type": "function" }, - { "inputs": [], "name": "unpause", "outputs": [], "stateMutability": "nonpayable", "type": "function" }, { - "inputs": [{ "internalType": "address", "name": "asset", "type": "address" }], + "inputs": [], + "name": "unpause", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "asset", + "type": "address" + } + ], "name": "updateAssetPrice", "outputs": [], "stateMutability": "nonpayable", "type": "function" }, { - "inputs": [{ "internalType": "address", "name": "vToken", "type": "address" }], + "inputs": [ + { + "internalType": "address", + "name": "vToken", + "type": "address" + } + ], "name": "updatePrice", "outputs": [], "stateMutability": "nonpayable", @@ -294,7 +642,13 @@ { "inputs": [], "name": "vai", - "outputs": [{ "internalType": "address", "name": "", "type": "address" }], + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], "stateMutability": "view", "type": "function" } diff --git a/simulations/vip-999/abi/VenusERC20.json b/simulations/vip-999/abi/VenusERC20.json new file mode 100644 index 000000000..06c888bdf --- /dev/null +++ b/simulations/vip-999/abi/VenusERC20.json @@ -0,0 +1,483 @@ +{ + "abi": [ + { + "inputs": [ + { + "internalType": "string", + "name": "name_", + "type": "string" + }, + { + "internalType": "string", + "name": "symbol_", + "type": "string" + }, + { + "internalType": "uint8", + "name": "decimals_", + "type": "uint8" + }, + { + "internalType": "address", + "name": "accessControlManager_", + "type": "address" + } + ], + "stateMutability": "nonpayable", + "type": "constructor" + }, + { + "inputs": [], + "name": "Unauthorized", + "type": "error" + }, + { + "inputs": [], + "name": "ZeroAddressNotAllowed", + "type": "error" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "owner", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "spender", + "type": "address" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "value", + "type": "uint256" + } + ], + "name": "Approval", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "oldAccessControlManager", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "newAccessControlManager", + "type": "address" + } + ], + "name": "NewAccessControlManager", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "previousOwner", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "newOwner", + "type": "address" + } + ], + "name": "OwnershipTransferStarted", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "previousOwner", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "newOwner", + "type": "address" + } + ], + "name": "OwnershipTransferred", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "from", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "to", + "type": "address" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "value", + "type": "uint256" + } + ], + "name": "Transfer", + "type": "event" + }, + { + "inputs": [], + "name": "acceptOwnership", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "accessControlManager", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "owner", + "type": "address" + }, + { + "internalType": "address", + "name": "spender", + "type": "address" + } + ], + "name": "allowance", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "spender", + "type": "address" + }, + { + "internalType": "uint256", + "name": "amount", + "type": "uint256" + } + ], + "name": "approve", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "account", + "type": "address" + } + ], + "name": "balanceOf", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "account_", + "type": "address" + }, + { + "internalType": "uint256", + "name": "amount_", + "type": "uint256" + } + ], + "name": "burn", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "decimals", + "outputs": [ + { + "internalType": "uint8", + "name": "", + "type": "uint8" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "spender", + "type": "address" + }, + { + "internalType": "uint256", + "name": "subtractedValue", + "type": "uint256" + } + ], + "name": "decreaseAllowance", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "spender", + "type": "address" + }, + { + "internalType": "uint256", + "name": "addedValue", + "type": "uint256" + } + ], + "name": "increaseAllowance", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "account_", + "type": "address" + }, + { + "internalType": "uint256", + "name": "amount_", + "type": "uint256" + } + ], + "name": "mint", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "name", + "outputs": [ + { + "internalType": "string", + "name": "", + "type": "string" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "owner", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "pendingOwner", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "renounceOwnership", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "newAccessControlManager_", + "type": "address" + } + ], + "name": "setAccessControlManager", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "symbol", + "outputs": [ + { + "internalType": "string", + "name": "", + "type": "string" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "totalSupply", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "to", + "type": "address" + }, + { + "internalType": "uint256", + "name": "amount", + "type": "uint256" + } + ], + "name": "transfer", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "from", + "type": "address" + }, + { + "internalType": "address", + "name": "to", + "type": "address" + }, + { + "internalType": "uint256", + "name": "amount", + "type": "uint256" + } + ], + "name": "transferFrom", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "newOwner", + "type": "address" + } + ], + "name": "transferOwnership", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + } + ], + "bytecode": "0x60a060405234801561000f575f80fd5b506040516112c03803806112c083398101604081905261002e916101cd565b8383600361003c83826102e5565b50600461004982826102e5565b50505061006261005d61009960201b60201c565b61009d565b61006b816100b9565b600780546001600160a01b0319166001600160a01b039290921691909117905560ff16608052506103a49050565b3390565b600680546001600160a01b03191690556100b6816100e0565b50565b6001600160a01b0381166100b6576040516342bcdf7f60e11b815260040160405180910390fd5b600580546001600160a01b038381166001600160a01b0319831681179093556040519116919082907f8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e0905f90a35050565b634e487b7160e01b5f52604160045260245ffd5b5f82601f830112610154575f80fd5b81516001600160401b038082111561016e5761016e610131565b604051601f8301601f19908116603f0116810190828211818310171561019657610196610131565b816040528381528660208588010111156101ae575f80fd5b8360208701602083015e5f602085830101528094505050505092915050565b5f805f80608085870312156101e0575f80fd5b84516001600160401b03808211156101f6575f80fd5b61020288838901610145565b95506020870151915080821115610217575f80fd5b5061022487828801610145565b935050604085015160ff8116811461023a575f80fd5b60608601519092506001600160a01b0381168114610256575f80fd5b939692955090935050565b600181811c9082168061027557607f821691505b60208210810361029357634e487b7160e01b5f52602260045260245ffd5b50919050565b601f8211156102e057805f5260205f20601f840160051c810160208510156102be5750805b601f840160051c820191505b818110156102dd575f81556001016102ca565b50505b505050565b81516001600160401b038111156102fe576102fe610131565b6103128161030c8454610261565b84610299565b602080601f831160018114610345575f841561032e5750858301515b5f19600386901b1c1916600185901b17855561039c565b5f85815260208120601f198616915b8281101561037357888601518255948401946001909101908401610354565b508582101561039057878501515f19600388901b60f8161c191681555b505060018460011b0185555b505050505050565b608051610f046103bc5f395f6101ad0152610f045ff3fe608060405234801561000f575f80fd5b5060043610610127575f3560e01c806379ba5097116100a9578063a9059cbb1161006e578063a9059cbb14610288578063b4a0bdf31461029b578063dd62ed3e146102ae578063e30c3978146102c1578063f2fde38b146102d2575f80fd5b806379ba50971461022d5780638da5cb5b1461023557806395d89b411461025a5780639dc29fac14610262578063a457c2d714610275575f80fd5b8063313ce567116100ef578063313ce567146101a657806339509351146101d757806340c10f19146101ea57806370a08231146101fd578063715018a614610225575f80fd5b806306fdde031461012b578063095ea7b3146101495780630e32cb861461016c57806318160ddd1461018157806323b872dd14610193575b5f80fd5b6101336102e5565b6040516101409190610d4e565b60405180910390f35b61015c610157366004610d82565b610375565b6040519015158152602001610140565b61017f61017a366004610daa565b61038e565b005b6002545b604051908152602001610140565b61015c6101a1366004610dc3565b6103fa565b60405160ff7f0000000000000000000000000000000000000000000000000000000000000000168152602001610140565b61015c6101e5366004610d82565b61041d565b61017f6101f8366004610d82565b61043e565b61018561020b366004610daa565b6001600160a01b03165f9081526020819052604090205490565b61017f610482565b61017f610495565b6005546001600160a01b03165b6040516001600160a01b039091168152602001610140565b610133610514565b61017f610270366004610d82565b610523565b61015c610283366004610d82565b610563565b61015c610296366004610d82565b6105dd565b600754610242906001600160a01b031681565b6101856102bc366004610dfc565b6105ea565b6006546001600160a01b0316610242565b61017f6102e0366004610daa565b610614565b6060600380546102f490610e2d565b80601f016020809104026020016040519081016040528092919081815260200182805461032090610e2d565b801561036b5780601f106103425761010080835404028352916020019161036b565b820191905f5260205f20905b81548152906001019060200180831161034e57829003601f168201915b5050505050905090565b5f33610382818585610685565b60019150505b92915050565b6103966107a9565b61039f81610803565b6007546040516001600160a01b038084169216907f66fd58e82f7b31a2a5c30e0888f3093efe4e111b00cd2b0c31fe014601293aa0905f90a3600780546001600160a01b0319166001600160a01b0392909216919091179055565b5f3361040785828561082a565b6104128585856108a2565b506001949350505050565b5f3361038281858561042f83836105ea565b6104399190610e65565b610685565b610474604051806040016040528060158152602001746d696e7428616464726573732c75696e743235362960581b815250610a44565b61047e8282610ad1565b5050565b61048a6107a9565b6104935f610b8e565b565b60065433906001600160a01b031681146105085760405162461bcd60e51b815260206004820152602960248201527f4f776e61626c6532537465703a2063616c6c6572206973206e6f7420746865206044820152683732bb9037bbb732b960b91b60648201526084015b60405180910390fd5b61051181610b8e565b50565b6060600480546102f490610e2d565b610559604051806040016040528060158152602001746275726e28616464726573732c75696e743235362960581b815250610a44565b61047e8282610ba7565b5f338161057082866105ea565b9050838110156105d05760405162461bcd60e51b815260206004820152602560248201527f45524332303a2064656372656173656420616c6c6f77616e63652062656c6f77604482015264207a65726f60d81b60648201526084016104ff565b6104128286868403610685565b5f336103828185856108a2565b6001600160a01b039182165f90815260016020908152604080832093909416825291909152205490565b61061c6107a9565b600680546001600160a01b0383166001600160a01b0319909116811790915561064d6005546001600160a01b031690565b6001600160a01b03167f38d16b8cac22d99fc7c124b9cd0de2d3fa1faef420bfe791d8c362d765e2270060405160405180910390a350565b6001600160a01b0383166106e75760405162461bcd60e51b8152602060048201526024808201527f45524332303a20617070726f76652066726f6d20746865207a65726f206164646044820152637265737360e01b60648201526084016104ff565b6001600160a01b0382166107485760405162461bcd60e51b815260206004820152602260248201527f45524332303a20617070726f766520746f20746865207a65726f206164647265604482015261737360f01b60648201526084016104ff565b6001600160a01b038381165f8181526001602090815260408083209487168084529482529182902085905590518481527f8c5be1e5ebec7d5bd14f71427d1e84f3dd0314c0f7b2291e5b200ac8c7c3b92591015b60405180910390a3505050565b6005546001600160a01b031633146104935760405162461bcd60e51b815260206004820181905260248201527f4f776e61626c653a2063616c6c6572206973206e6f7420746865206f776e657260448201526064016104ff565b6001600160a01b038116610511576040516342bcdf7f60e11b815260040160405180910390fd5b5f61083584846105ea565b90505f19811461089c578181101561088f5760405162461bcd60e51b815260206004820152601d60248201527f45524332303a20696e73756666696369656e7420616c6c6f77616e636500000060448201526064016104ff565b61089c8484848403610685565b50505050565b6001600160a01b0383166109065760405162461bcd60e51b815260206004820152602560248201527f45524332303a207472616e736665722066726f6d20746865207a65726f206164604482015264647265737360d81b60648201526084016104ff565b6001600160a01b0382166109685760405162461bcd60e51b815260206004820152602360248201527f45524332303a207472616e7366657220746f20746865207a65726f206164647260448201526265737360e81b60648201526084016104ff565b6001600160a01b0383165f90815260208190526040902054818110156109df5760405162461bcd60e51b815260206004820152602660248201527f45524332303a207472616e7366657220616d6f756e7420657863656564732062604482015265616c616e636560d01b60648201526084016104ff565b6001600160a01b038481165f81815260208181526040808320878703905593871680835291849020805487019055925185815290927fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef910160405180910390a361089c565b6007546040516318c5e8ab60e01b81526001600160a01b03909116906318c5e8ab90610a769033908590600401610e84565b602060405180830381865afa158015610a91573d5f803e3d5ffd5b505050506040513d601f19601f82011682018060405250810190610ab59190610eaf565b610511576040516282b42960e81b815260040160405180910390fd5b6001600160a01b038216610b275760405162461bcd60e51b815260206004820152601f60248201527f45524332303a206d696e7420746f20746865207a65726f20616464726573730060448201526064016104ff565b8060025f828254610b389190610e65565b90915550506001600160a01b0382165f81815260208181526040808320805486019055518481527fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef910160405180910390a35050565b600680546001600160a01b031916905561051181610ccf565b6001600160a01b038216610c075760405162461bcd60e51b815260206004820152602160248201527f45524332303a206275726e2066726f6d20746865207a65726f206164647265736044820152607360f81b60648201526084016104ff565b6001600160a01b0382165f9081526020819052604090205481811015610c7a5760405162461bcd60e51b815260206004820152602260248201527f45524332303a206275726e20616d6f756e7420657863656564732062616c616e604482015261636560f01b60648201526084016104ff565b6001600160a01b0383165f818152602081815260408083208686039055600280548790039055518581529192917fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef910161079c565b600580546001600160a01b038381166001600160a01b0319831681179093556040519116919082907f8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e0905f90a35050565b5f81518084528060208401602086015e5f602082860101526020601f19601f83011685010191505092915050565b602081525f610d606020830184610d20565b9392505050565b80356001600160a01b0381168114610d7d575f80fd5b919050565b5f8060408385031215610d93575f80fd5b610d9c83610d67565b946020939093013593505050565b5f60208284031215610dba575f80fd5b610d6082610d67565b5f805f60608486031215610dd5575f80fd5b610dde84610d67565b9250610dec60208501610d67565b9150604084013590509250925092565b5f8060408385031215610e0d575f80fd5b610e1683610d67565b9150610e2460208401610d67565b90509250929050565b600181811c90821680610e4157607f821691505b602082108103610e5f57634e487b7160e01b5f52602260045260245ffd5b50919050565b8082018082111561038857634e487b7160e01b5f52601160045260245ffd5b6001600160a01b03831681526040602082018190525f90610ea790830184610d20565b949350505050565b5f60208284031215610ebf575f80fd5b81518015158114610d60575f80fdfea26469706673582212207e13613c2cd266922c898bf18f6bf39f661af737abd0337a1c9972a6646ebe9c64736f6c63430008190033" +} diff --git a/simulations/vip-999/bscmainnet.ts b/simulations/vip-999/bscmainnet.ts index 335919b88..1106f2559 100644 --- a/simulations/vip-999/bscmainnet.ts +++ b/simulations/vip-999/bscmainnet.ts @@ -1,87 +1,193 @@ +import { time } from "@nomicfoundation/hardhat-network-helpers"; import { expect } from "chai"; -import { Contract } from "ethers"; +import { BigNumber, Contract } from "ethers"; +import { parseUnits } from "ethers/lib/utils"; import { ethers } from "hardhat"; import { NETWORK_ADDRESSES } from "src/networkAddresses"; -import { expectEvents, initMainnetUser, setMaxStalePeriodForAllAssets } from "src/utils"; +import { initMainnetUser, setMaxStalePeriodForAllAssets, setMaxStalePeriodInChainlinkOracle } from "src/utils"; import { forking, testVip } from "src/vip-framework"; import vip999, { + ATLAS_ORACLE, BTCB, + BTCB_FEED_FALLBACK, + BTCB_FEED_MAIN, + BTCB_FEED_PIVOT, + CHAINLINK_ORACLE, FIXED_RATE_VAULT_CONTROLLER, + INSTITUTION_OPERATOR, + REDSTONE_ORACLE, SUPPLY_ASSET, VCEBTC, VCEBTC_INITIAL_SUPPLY, + instConfig, + vaultConfig, } from "../../vips/vip-999/bscmainnet"; import ACM_ABI from "./abi/AccessControlManager.json"; import ERC20_ABI from "./abi/AccessControlledERC20.json"; +import VAULT_ABI from "./abi/InstitutionalLoanVault.json"; +import CONTROLLER_ABI from "./abi/InstitutionalVaultController.json"; +import PROXY_ADMIN_ABI from "./abi/ProxyAdmin.json"; import ORACLE_ABI from "./abi/ResilientOracle.json"; const { bscmainnet } = NETWORK_ADDRESSES; -// ───────────────────────────────────────────────────────────────────────────── -// DRAFT SIMULATION — NOT RUNNABLE YET. -// -// This simulation cannot pass until the following are true (see the VIP header): -// 1. vceBTC is deployed and VCEBTC in the VIP file points to it (it is currently -// the zero address). On a fork, deploy it in the `before()` hook below. -// 2. The InstitutionalVaultController proxy has been upgraded to the 6-param -// `createVault` implementation. The impl live on bscmainnet today is 5-param, -// so the createVault command reverts against the current fork state. -// 3. The underlying oracle feeds resolve a price for vceBTC (cloning the -// ResilientOracle config alone is not enough — see the VIP's oracle TODO). -// 4. The DUMMY vault parameters are replaced with finalized values. -// -// Once the above are addressed, pick a fork block just before the proposal, fill in -// the assertions marked TODO, and remove `.skip`. -// ───────────────────────────────────────────────────────────────────────────── - -const FORK_BLOCK = 0; // TODO: set to a block shortly before the proposal. +const FORK_BLOCK = 108402150; // shortly after vceBTC's deployment +const ONE_YEAR = 31536000; + +// A separate, not-yet-executed VIP upgrades the controller/vault implementations and +// grants createVault. +const PROXY_ADMIN = "0x6beb6D2695B67FEb73ad4f172E8E2975497187e4"; +const NEW_CONTROLLER_IMPLEMENTATION = "0xBD9df626c642591cef3612586CC5e45E9767360f"; +const NEW_VAULT_IMPLEMENTATION = "0xC25b2B657D24380eDd1a1Cff5296385541e85204"; + +const USDT_WHALE = "0xF977814e90dA44bFA03b6295A0616a897441aceC"; // Binance Hot Wallet +const LENDER = "0x2222222222222222222222222222222222222222"; // dummy test lender + +const STUB_ORACLE_BYTECODE = + "0x6080604052348015600e575f80fd5b5061015e8061001c5f395ff3fe608060405234801561000f575f80fd5b5060043610610029575f3560e01c806341976e091461002d575b5f80fd5b610047600480360381019061004291906100cc565b61005d565b604051610054919061010f565b60405180910390f35b5f670de0b6b3a76400009050919050565b5f80fd5b5f73ffffffffffffffffffffffffffffffffffffffff82169050919050565b5f61009b82610072565b9050919050565b6100ab81610091565b81146100b5575f80fd5b50565b5f813590506100c6816100a2565b92915050565b5f602082840312156100e1576100e061006e565b5b5f6100ee848285016100b8565b91505092915050565b5f819050919050565b610109816100f7565b82525050565b5f6020820190506101225f830184610100565b9291505056fea26469706673582212209282f7f2d85233912d0088d6dc45ce2459097d2866597e41f0a286059758c12c64736f6c63430008190033"; +const STUB_ORACLE_ABI = ["function getPrice(address) external pure returns (uint256)"]; + +// IVaultTypes.VaultState +const VaultState = { + WaitingForMargin: 0, + MarginDeposited: 1, + Fundraising: 2, + Lock: 4, + PendingSettlement: 5, + Matured: 7, +}; forking(FORK_BLOCK, async () => { let oracle: Contract; let acm: Contract; + let controller: Contract; let vceBTC: Contract; + let btcb: Contract; + let usdt: Contract; + let timelock: any; + let vaultsBefore: any; + let btcbPriceAtFork: any; before(async () => { oracle = await ethers.getContractAt(ORACLE_ABI, bscmainnet.RESILIENT_ORACLE); + btcbPriceAtFork = await oracle.getPrice(BTCB); acm = await ethers.getContractAt(ACM_ABI, bscmainnet.ACCESS_CONTROL_MANAGER); - vceBTC = await ethers.getContractAt(ERC20_ABI, VCEBTC); + controller = await ethers.getContractAt(CONTROLLER_ABI, FIXED_RATE_VAULT_CONTROLLER); + btcb = await ethers.getContractAt(ERC20_ABI, BTCB); + usdt = await ethers.getContractAt(ERC20_ABI, SUPPLY_ASSET); + timelock = await initMainnetUser(bscmainnet.NORMAL_TIMELOCK, parseUnits("40")); + vaultsBefore = await controller.allVaultsLength(); - // The fork advances time, so oracle feeds go stale and getPrice reverts. Bump the - // max stale period on the underlying feeds for the priced assets. vceBTC shares - // BTCB's feeds, so bumping BTCB also covers vceBTC's price. - const btcb = await ethers.getContractAt(ERC20_ABI, BTCB); - const usdt = await ethers.getContractAt(ERC20_ABI, SUPPLY_ASSET); + const usdtPriceAtFork = await oracle.getPrice(SUPPLY_ASSET); await setMaxStalePeriodForAllAssets(oracle, [btcb, usdt]); + const redstoneOracleForExisting = await ethers.getContractAt( + ["function setDirectPrice(address asset, uint256 price) external"], + REDSTONE_ORACLE, + ); + await redstoneOracleForExisting.connect(timelock).setDirectPrice(BTCB, btcbPriceAtFork); + await redstoneOracleForExisting.connect(timelock).setDirectPrice(SUPPLY_ASSET, usdtPriceAtFork); + + // --- Reproduce the separate controller/vault-upgrade VIP's end state --- + const proxyAdmin = await ethers.getContractAt(PROXY_ADMIN_ABI, PROXY_ADMIN); + const proxyAdminOwner = await initMainnetUser(await proxyAdmin.owner(), parseUnits("1")); + await proxyAdmin.connect(proxyAdminOwner).upgrade(FIXED_RATE_VAULT_CONTROLLER, NEW_CONTROLLER_IMPLEMENTATION); + await controller.connect(timelock).setVaultImplementation(NEW_VAULT_IMPLEMENTATION); + await acm + .connect(timelock) + .giveCallPermission( + FIXED_RATE_VAULT_CONTROLLER, + "createVault(VaultConfig,InstitutionalConfig,RiskConfig,string,string,string)", + bscmainnet.NORMAL_TIMELOCK, + ); + await acm + .connect(timelock) + .giveCallPermission(FIXED_RATE_VAULT_CONTROLLER, "openVault(address)", bscmainnet.NORMAL_TIMELOCK); + + 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(); + await controller.connect(timelock).setOracle(stubOracle.address); + + vceBTC = await ethers.getContractAt(ERC20_ABI, VCEBTC); }); - describe.skip("Pre-VIP behavior", () => { + describe("Pre-VIP behavior", () => { + it("vceBTC is deployed and wired to the real ACM", async () => { + expect(await vceBTC.accessControlManager()).to.equal(bscmainnet.ACCESS_CONTROL_MANAGER); + expect(await vceBTC.decimals()).to.equal(18); + expect(await vceBTC.totalSupply()).to.equal(0); + }); + + it("ownership has been offered to the Normal Timelock but not yet accepted", async () => { + expect(await vceBTC.pendingOwner()).to.equal(bscmainnet.NORMAL_TIMELOCK); + expect(await vceBTC.owner()).to.not.equal(bscmainnet.NORMAL_TIMELOCK); + }); + it("vceBTC has no oracle config yet (getPrice reverts)", async () => { await expect(oracle.getPrice(VCEBTC)).to.be.reverted; }); - it("Timelock cannot yet mint vceBTC", async () => { - expect(await acm.isAllowedToCall(bscmainnet.NORMAL_TIMELOCK, "mint(address,uint256)")).to.equal(false); + it("Timelock and Guardian cannot yet mint/burn vceBTC", async () => { + const vceBtcAsCaller = await initMainnetUser(VCEBTC, parseUnits("1")); + expect( + await acm.connect(vceBtcAsCaller).isAllowedToCall(bscmainnet.NORMAL_TIMELOCK, "mint(address,uint256)"), + ).to.equal(false); + expect( + await acm.connect(vceBtcAsCaller).isAllowedToCall(bscmainnet.CRITICAL_GUARDIAN, "mint(address,uint256)"), + ).to.equal(false); }); }); testVip("VIP-999 Create Ceffu Custody BTC Fixed Rate Vault", await vip999(), { - callbackAfterExecution: async txResponse => { - // TODO: confirm the emitted events / counts once params are final. - await expectEvents(txResponse, [ACM_ABI], ["PermissionGranted"], [5]); + callbackAfterExecution: async () => { + // vceBTC's oracle config is only created by the VIP itself, so its stale period + // can only be bumped now. + for (const [oracleAddress, feed] of [ + [CHAINLINK_ORACLE, BTCB_FEED_MAIN], + [REDSTONE_ORACLE, BTCB_FEED_PIVOT], + [ATLAS_ORACLE, BTCB_FEED_FALLBACK], + ]) { + await setMaxStalePeriodInChainlinkOracle(oracleAddress, VCEBTC, feed, bscmainnet.NORMAL_TIMELOCK, ONE_YEAR); + } + const redstoneOracle = await ethers.getContractAt( + ["function setDirectPrice(address asset, uint256 price) external"], + REDSTONE_ORACLE, + ); + await redstoneOracle.connect(timelock).setDirectPrice(VCEBTC, btcbPriceAtFork); + await controller.connect(timelock).setOracle(bscmainnet.RESILIENT_ORACLE); }, }); - describe.skip("Post-VIP behavior", () => { - it("vceBTC is priced identically to BTCB", async () => { - expect(await oracle.getPrice(VCEBTC)).to.equal(await oracle.getPrice(BTCB)); + describe("Post-VIP behavior", () => { + it("vceBTC is priced close to BTCB", async () => { + const vceBtcPrice = await oracle.getPrice(VCEBTC); + const diff = vceBtcPrice.gt(btcbPriceAtFork) + ? vceBtcPrice.sub(btcbPriceAtFork) + : btcbPriceAtFork.sub(vceBtcPrice); + expect(diff.mul(100).lte(btcbPriceAtFork)).to.equal(true); // within 1% + }); + + it("the Normal Timelock accepted ownership of vceBTC", async () => { + expect(await vceBTC.owner()).to.equal(bscmainnet.NORMAL_TIMELOCK); + expect(await vceBTC.pendingOwner()).to.equal(ethers.constants.AddressZero); }); it("Timelock and Guardian can mint/burn vceBTC", async () => { - expect(await acm.isAllowedToCall(bscmainnet.NORMAL_TIMELOCK, "mint(address,uint256)")).to.equal(true); - expect(await acm.isAllowedToCall(bscmainnet.CRITICAL_GUARDIAN, "mint(address,uint256)")).to.equal(true); - expect(await acm.isAllowedToCall(bscmainnet.NORMAL_TIMELOCK, "burn(address,uint256)")).to.equal(true); - expect(await acm.isAllowedToCall(bscmainnet.CRITICAL_GUARDIAN, "burn(address,uint256)")).to.equal(true); + const vceBtcAsCaller = await initMainnetUser(VCEBTC, parseUnits("1")); + expect( + await acm.connect(vceBtcAsCaller).isAllowedToCall(bscmainnet.NORMAL_TIMELOCK, "mint(address,uint256)"), + ).to.equal(true); + expect( + await acm.connect(vceBtcAsCaller).isAllowedToCall(bscmainnet.CRITICAL_GUARDIAN, "mint(address,uint256)"), + ).to.equal(true); + expect( + await acm.connect(vceBtcAsCaller).isAllowedToCall(bscmainnet.NORMAL_TIMELOCK, "burn(address,uint256)"), + ).to.equal(true); + expect( + await acm.connect(vceBtcAsCaller).isAllowedToCall(bscmainnet.CRITICAL_GUARDIAN, "burn(address,uint256)"), + ).to.equal(true); }); it("initial vceBTC collateral was minted to the Venus Treasury", async () => { @@ -90,20 +196,112 @@ forking(FORK_BLOCK, async () => { }); it("a Fixed Rate Vault backed by vceBTC was created", async () => { - const controller = await ethers.getContractAt( - ["function allVaultsLength() view returns (uint256)"], - FIXED_RATE_VAULT_CONTROLLER, - ); - // TODO: assert a new vault is registered and its collateralAsset == vceBTC. - expect(await controller.allVaultsLength()).to.be.gt(0); + expect(await controller.allVaultsLength()).to.equal(vaultsBefore.add(1)); + const vaultAddress = await controller.allVaults(vaultsBefore); + expect(await controller.isRegistered(vaultAddress)).to.equal(true); + + const vault = await ethers.getContractAt(VAULT_ABI, vaultAddress); + expect((await vault.config()).supplyAsset).to.equal(SUPPLY_ASSET); + expect((await vault.institutionalConfig()).collateralAsset).to.equal(VCEBTC); + expect((await vault.institutionalConfig()).institutionOperator).to.equal(INSTITUTION_OPERATOR); }); }); - describe.skip("Post-VIP functional", () => { - it("Guardian can mint vceBTC after the grant", async () => { - const guardian = await initMainnetUser(bscmainnet.CRITICAL_GUARDIAN, ethers.utils.parseEther("1")); - // TODO: mint a small amount and assert balance/totalSupply delta. - await expect(vceBTC.connect(guardian).mint(bscmainnet.CRITICAL_GUARDIAN, 1)).to.not.be.reverted; + describe("Vault lifecycle", () => { + let vault: Contract; + let institution: any; + let lender: any; + + const idealCollateralAmount = BigNumber.from(instConfig[1]); + const marginRate = BigNumber.from(instConfig[2]); + const minBorrowCap = BigNumber.from(vaultConfig[3]); + const openDuration = Number(vaultConfig[6]); + const lockDuration = Number(vaultConfig[7]); + const marginAmount = idealCollateralAmount.mul(marginRate).div(parseUnits("1", 18)); + const lenderDepositAmount = minBorrowCap.mul(2); + + before(async () => { + const vaultAddress = await controller.allVaults(vaultsBefore); + vault = await ethers.getContractAt(VAULT_ABI, vaultAddress); + + institution = await initMainnetUser(INSTITUTION_OPERATOR, parseUnits("1")); + lender = await initMainnetUser(LENDER, parseUnits("1")); + + // Fund the institution with vceBTC collateral and both parties with the supply asset. + await vceBTC.connect(timelock).mint(INSTITUTION_OPERATOR, idealCollateralAmount); + const whale = await initMainnetUser(USDT_WHALE, parseUnits("1")); + await usdt.connect(whale).transfer(LENDER, lenderDepositAmount); + await usdt.connect(whale).transfer(INSTITUTION_OPERATOR, lenderDepositAmount.div(5)); // to repay principal + interest + }); + + it("institution deposits margin collateral (WaitingForMargin -> MarginDeposited)", async () => { + expect(await vault.state()).to.equal(VaultState.WaitingForMargin); + await vceBTC.connect(institution).approve(vault.address, idealCollateralAmount); + await vault.connect(institution).depositCollateral(marginAmount); + expect(await vault.state()).to.equal(VaultState.MarginDeposited); + }); + + it("controller opens the vault (MarginDeposited -> Fundraising)", async () => { + await controller.connect(timelock).openVault(vault.address); + expect(await vault.state()).to.equal(VaultState.Fundraising); + }); + + it("institution tops up collateral to the full ideal amount", async () => { + await vault.connect(institution).depositCollateral(idealCollateralAmount.sub(marginAmount)); + expect(await vceBTC.balanceOf(vault.address)).to.equal(idealCollateralAmount); + }); + + it("a lender deposits the supply asset", async () => { + await usdt.connect(lender).approve(vault.address, lenderDepositAmount); + await vault.connect(lender).deposit(lenderDepositAmount, LENDER); + expect(await vault.balanceOf(LENDER)).to.be.gt(0); + }); + + it("the open window elapses and the vault locks (Fundraising -> Lock)", async () => { + await time.increase(openDuration + 1); + await vault.updateVaultState(); + expect(await vault.state()).to.equal(VaultState.Lock); + }); + + it("the institution claims the raised funds", async () => { + const usdtBefore = await usdt.balanceOf(INSTITUTION_OPERATOR); + await vault.connect(institution).claimRaisedFunds(); + expect(await usdt.balanceOf(INSTITUTION_OPERATOR)).to.equal(usdtBefore.add(lenderDepositAmount)); + expect(await vault.outstandingDebt()).to.be.gt(lenderDepositAmount); // principal + accrued interest + }); + + it("the institution repays in full after the lock period (Lock -> PendingSettlement -> Matured)", async () => { + await time.increase(lockDuration + 1); + await vault.updateVaultState(); + expect(await vault.state()).to.equal(VaultState.PendingSettlement); + + const owed = await vault.outstandingDebt(); + await usdt.connect(institution).approve(vault.address, owed); + await vault.connect(institution).repay(owed); + await vault.updateVaultState(); + expect(await vault.state()).to.equal(VaultState.Matured); + expect(await vault.outstandingDebt()).to.equal(0); + }); + + it("the lender redeems principal plus interest", async () => { + const shares = await vault.balanceOf(LENDER); + const expectedAssets = await vault.previewRedeem(shares); + expect(expectedAssets).to.be.gt(lenderDepositAmount); // net of the reserve-factor cut + + const usdtBefore = await usdt.balanceOf(LENDER); + await vault.connect(lender).redeem(shares, LENDER, LENDER); + expect(await usdt.balanceOf(LENDER)).to.equal(usdtBefore.add(expectedAssets)); + expect(await vault.balanceOf(LENDER)).to.equal(0); + }); + + it("the institution withdraws its collateral", async () => { + const collateral = await vceBTC.balanceOf(vault.address); + expect(collateral).to.equal(idealCollateralAmount); + + const before = await vceBTC.balanceOf(INSTITUTION_OPERATOR); + await vault.connect(institution).withdrawCollateral(collateral); + expect(await vceBTC.balanceOf(INSTITUTION_OPERATOR)).to.equal(before.add(collateral)); + expect(await vceBTC.balanceOf(vault.address)).to.equal(0); }); }); }); diff --git a/vips/vip-999/bscmainnet.ts b/vips/vip-999/bscmainnet.ts index b86f94184..b631a3365 100644 --- a/vips/vip-999/bscmainnet.ts +++ b/vips/vip-999/bscmainnet.ts @@ -5,16 +5,16 @@ import { makeProposal } from "src/utils"; const { bscmainnet } = NETWORK_ADDRESSES; -// TODO: deployed vceBTC (AccessControlledERC20) address. -export const VCEBTC = "0x0000000000000000000000000000000000000000"; +// Deployed vceBTC +export const VCEBTC = "0xba63642A893b0F15aDE730943972824c9e2147a7"; export const FIXED_RATE_VAULT_CONTROLLER = "0x6D9e91cB766259af42619c14c994E694E57e6E85"; export const BTCB = "0x7130d2A12B9BCbFAe4f2634d864A1Ee1Ce3Ead9c"; // BTCB's oracle config -export const BTCB_ORACLE_MAIN = "0x1B2103441A0A108daD8848D8F5d790e4D402921F"; -export const BTCB_ORACLE_PIVOT = "0x8455EFA4D7Ff63b8BFD96AdD889483Ea7d39B70a"; -export const BTCB_ORACLE_FALLBACK = "0x9E6928Ec418948ceb9f1cd9872fD312b13D841D0"; +export const CHAINLINK_ORACLE = "0x1B2103441A0A108daD8848D8F5d790e4D402921F"; +export const REDSTONE_ORACLE = "0x8455EFA4D7Ff63b8BFD96AdD889483Ea7d39B70a"; +export const ATLAS_ORACLE = "0x9E6928Ec418948ceb9f1cd9872fD312b13D841D0"; export const BTCB_FEED_MAIN = "0x8ECF7dE377F788A813F5215668E282556b35f300"; export const BTCB_FEED_PIVOT = "0xa51738d1937FFc553d5070f43300B385AA2D9F55"; export const BTCB_FEED_FALLBACK = "0x4f6c53fb9CdD46269d24bCa4E68bB680879132fc"; @@ -38,7 +38,7 @@ export const VCEBTC_INITIAL_SUPPLY = parseUnits("2000", 18); // Fixed Rate Vault configuration. TODO: finalize values. // VaultConfig: [supplyAsset, fixedAPY(bps), reserveFactor(1e18), minBorrowCap, // maxBorrowCap, minSupplierDeposit, openDuration, lockDuration, settlementWindow] -const vaultConfig = [ +export const vaultConfig = [ SUPPLY_ASSET, 800, // fixedAPY = 8% parseUnits("0.1", 18), // reserveFactor = 10% @@ -52,7 +52,7 @@ const vaultConfig = [ // InstitutionalConfig: [collateralAsset, idealCollateralAmount, marginRate(1e18), // institutionOperator, positionTokenId] -const instConfig = [ +export const instConfig = [ VCEBTC, // collateral = vceBTC parseUnits("2000", 18), // idealCollateralAmount (sized off-chain from 50% CF + 20% buffer) parseUnits("0.1", 18), // marginRate = 10% @@ -61,12 +61,12 @@ const instConfig = [ ]; // RiskConfig: [liquidationThreshold(1e18), liquidationIncentive(1e18), latePenaltyRate(1e18)] -const riskConfig = [parseUnits("0.85", 18), parseUnits("1.1", 18), parseUnits("1.1", 18)]; +export const riskConfig = [parseUnits("0.85", 18), parseUnits("1.1", 18), parseUnits("1.1", 18)]; // TODO: finalize values -const VAULT_SHARE_NAME = "Venus Ceffu Fixed Rate Vault"; -const VAULT_SHARE_SYMBOL = "vceFRV"; -const INSTITUTION_NAME = "Ceffu"; +export const VAULT_SHARE_NAME = "Venus Ceffu Fixed Rate Vault"; +export const VAULT_SHARE_SYMBOL = "vceFRV"; +export const INSTITUTION_NAME = "Ceffu"; export const vip999 = () => { const meta = { @@ -79,9 +79,10 @@ This proposal onboards a new custody-mirror collateral token, **vceBTC ("Ceffu C #### Actions 1. **Oracle** — price vceBTC identically to BTCB by cloning BTCB's full oracle configuration: the main / pivot / fallback sub-oracle feeds, the BoundValidator bounds, and the ResilientOracle token config. -2. **Access control** — grant \`mint(address,uint256)\` and \`burn(address,uint256)\` on vceBTC to the Normal Timelock and the Guardian, and grant \`createVault(...)\` on the InstitutionalVaultController to the Normal Timelock. -3. **Initial supply** — mint the initial vceBTC collateral to the Venus Treasury. -4. **Vault creation** — create the Fixed Rate Vault with vceBTC as collateral.`, +2. **Ownership** — accept ownership of vceBTC (already transferred to the Normal Timelock by the deployer). +3. **Access control** — grant \`mint(address,uint256)\` and \`burn(address,uint256)\` on vceBTC to the Normal Timelock and the Guardian, and grant \`createVault(...)\` on the InstitutionalVaultController to the Normal Timelock. +4. **Initial supply** — mint the initial vceBTC collateral to the Venus Treasury. +5. **Vault creation** — create the Fixed Rate Vault with vceBTC as collateral.`, 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", @@ -93,17 +94,17 @@ This proposal onboards a new custody-mirror collateral token, **vceBTC ("Ceffu C // 1. Oracle configuration identically to BTCB // ────────────────────────────────────────────────────────────────────── { - target: BTCB_ORACLE_MAIN, + target: CHAINLINK_ORACLE, signature: "setTokenConfig((address,address,uint256))", params: [[VCEBTC, BTCB_FEED_MAIN, BTCB_STALE_MAIN]], }, { - target: BTCB_ORACLE_PIVOT, + target: REDSTONE_ORACLE, signature: "setTokenConfig((address,address,uint256))", params: [[VCEBTC, BTCB_FEED_PIVOT, BTCB_STALE_PIVOT]], }, { - target: BTCB_ORACLE_FALLBACK, + target: ATLAS_ORACLE, signature: "setTokenConfig((address,address,uint256))", params: [[VCEBTC, BTCB_FEED_FALLBACK, BTCB_STALE_FALLBACK]], }, @@ -114,12 +115,21 @@ This proposal onboards a new custody-mirror collateral token, **vceBTC ("Ceffu C }, { target: bscmainnet.RESILIENT_ORACLE, - signature: "setTokenConfig((address,address[3],bool[3]))", - params: [[VCEBTC, [BTCB_ORACLE_MAIN, BTCB_ORACLE_PIVOT, BTCB_ORACLE_FALLBACK], [true, true, true]]], + signature: "setTokenConfig((address,address[3],bool[3],bool))", + params: [[VCEBTC, [CHAINLINK_ORACLE, REDSTONE_ORACLE, ATLAS_ORACLE], [true, true, true], false]], }, // ────────────────────────────────────────────────────────────────────── - // 2. Access control — vceBTC mint/burn + createVault + // 2. Accept ownership of vceBTC + // ────────────────────────────────────────────────────────────────────── + { + target: VCEBTC, + signature: "acceptOwnership()", + params: [], + }, + + // ────────────────────────────────────────────────────────────────────── + // 3. Access control — vceBTC mint/burn + createVault // ────────────────────────────────────────────────────────────────────── { target: bscmainnet.ACCESS_CONTROL_MANAGER, @@ -152,7 +162,7 @@ This proposal onboards a new custody-mirror collateral token, **vceBTC ("Ceffu C }, // ────────────────────────────────────────────────────────────────────── - // 3. Mint initial vceBTC collateral + // 4. Mint initial vceBTC collateral // ────────────────────────────────────────────────────────────────────── { target: VCEBTC, @@ -161,7 +171,7 @@ This proposal onboards a new custody-mirror collateral token, **vceBTC ("Ceffu C }, // ────────────────────────────────────────────────────────────────────── - // 4. Create the Fixed Rate Vault (vceBTC as collateral) + // 5. Create the Fixed Rate Vault (vceBTC as collateral) // ────────────────────────────────────────────────────────────────────── { target: FIXED_RATE_VAULT_CONTROLLER, From 45e4cc3ea2ecfb6ddfedeba3bcc868a995fd4ed9 Mon Sep 17 00:00:00 2001 From: GitGuru7 Date: Mon, 6 Jul 2026 18:18:57 +0530 Subject: [PATCH 04/15] fix: use VenusERC20.json ABI by extracting from wrapped object --- simulations/vip-999/bscmainnet.ts | 42 ++++++++++++++++++++++++++++--- 1 file changed, 39 insertions(+), 3 deletions(-) diff --git a/simulations/vip-999/bscmainnet.ts b/simulations/vip-999/bscmainnet.ts index 1106f2559..503b2a715 100644 --- a/simulations/vip-999/bscmainnet.ts +++ b/simulations/vip-999/bscmainnet.ts @@ -13,6 +13,8 @@ import vip999, { BTCB_FEED_FALLBACK, BTCB_FEED_MAIN, BTCB_FEED_PIVOT, + BTCB_UPPER_BOUND, + BOUND_VALIDATOR, CHAINLINK_ORACLE, FIXED_RATE_VAULT_CONTROLLER, INSTITUTION_OPERATOR, @@ -24,11 +26,13 @@ import vip999, { vaultConfig, } from "../../vips/vip-999/bscmainnet"; import ACM_ABI from "./abi/AccessControlManager.json"; -import ERC20_ABI from "./abi/AccessControlledERC20.json"; +import ERC20_ABI from "./abi/VenusERC20.json"; import VAULT_ABI from "./abi/InstitutionalLoanVault.json"; import CONTROLLER_ABI from "./abi/InstitutionalVaultController.json"; import PROXY_ADMIN_ABI from "./abi/ProxyAdmin.json"; import ORACLE_ABI from "./abi/ResilientOracle.json"; + +const BOUND_VALIDATOR_ABI = ["function validateConfigs(address) external view returns (uint256, uint256)"]; const { bscmainnet } = NETWORK_ADDRESSES; @@ -71,7 +75,6 @@ forking(FORK_BLOCK, async () => { before(async () => { oracle = await ethers.getContractAt(ORACLE_ABI, bscmainnet.RESILIENT_ORACLE); - btcbPriceAtFork = await oracle.getPrice(BTCB); acm = await ethers.getContractAt(ACM_ABI, bscmainnet.ACCESS_CONTROL_MANAGER); controller = await ethers.getContractAt(CONTROLLER_ABI, FIXED_RATE_VAULT_CONTROLLER); btcb = await ethers.getContractAt(ERC20_ABI, BTCB); @@ -80,6 +83,7 @@ forking(FORK_BLOCK, async () => { vaultsBefore = await controller.allVaultsLength(); const usdtPriceAtFork = await oracle.getPrice(SUPPLY_ASSET); + btcbPriceAtFork = await oracle.getPrice(BTCB); await setMaxStalePeriodForAllAssets(oracle, [btcb, usdt]); const redstoneOracleForExisting = await ethers.getContractAt( ["function setDirectPrice(address asset, uint256 price) external"], @@ -114,8 +118,10 @@ forking(FORK_BLOCK, async () => { }); describe("Pre-VIP behavior", () => { - it("vceBTC is deployed and wired to the real ACM", async () => { + it("vceBTC is deployed with correct ACM, name, symbol, and decimals", async () => { expect(await vceBTC.accessControlManager()).to.equal(bscmainnet.ACCESS_CONTROL_MANAGER); + expect(await vceBTC.name()).to.equal("Ceffu Custody BTC for Venus"); + expect(await vceBTC.symbol()).to.equal("vceBTC"); expect(await vceBTC.decimals()).to.equal(18); expect(await vceBTC.totalSupply()).to.equal(0); }); @@ -207,6 +213,36 @@ forking(FORK_BLOCK, async () => { }); }); + describe("Oracle configuration for BTCB and vceBTC", () => { + it("BTCB price is available from ResilientOracle", async () => { + const btcbPrice = await oracle.getPrice(BTCB); + expect(btcbPrice).to.be.gt(0); + }); + + it("vceBTC price is available from ResilientOracle", async () => { + const vceBtcPrice = await oracle.getPrice(VCEBTC); + expect(vceBtcPrice).to.be.gt(0); + }); + + it("vceBTC price matches BTCB price configuration", async () => { + const btcbPrice = await oracle.getPrice(BTCB); + const vceBtcPrice = await oracle.getPrice(VCEBTC); + expect(vceBtcPrice).to.equal(btcbPrice); + }); + + it("BTCB bounds are configured correctly in BoundValidator", async () => { + const boundValidator = await ethers.getContractAt(BOUND_VALIDATOR_ABI, BOUND_VALIDATOR); + const btcbBounds = await boundValidator.validateConfigs(BTCB); + expect(btcbBounds[1]).to.equal(BTCB_UPPER_BOUND); + }); + + it("vceBTC bounds are configured correctly in BoundValidator (cloned from BTCB)", async () => { + const boundValidator = await ethers.getContractAt(BOUND_VALIDATOR_ABI, BOUND_VALIDATOR); + const vceBtcBounds = await boundValidator.validateConfigs(VCEBTC); + expect(vceBtcBounds[1]).to.equal(BTCB_UPPER_BOUND); + }); + }); + describe("Vault lifecycle", () => { let vault: Contract; let institution: any; From 739393be32c67be861ffb7dc1abe7492bd522e5c Mon Sep 17 00:00:00 2001 From: GitGuru7 Date: Mon, 6 Jul 2026 18:24:57 +0530 Subject: [PATCH 05/15] refactor: simplify vceBTC price comparison using closeTo --- .../vip-999/abi/AccessControlledERC20.json | 214 ---- simulations/vip-999/abi/VenusERC20.json | 963 +++++++++--------- simulations/vip-999/bscmainnet.ts | 12 +- 3 files changed, 485 insertions(+), 704 deletions(-) delete mode 100644 simulations/vip-999/abi/AccessControlledERC20.json diff --git a/simulations/vip-999/abi/AccessControlledERC20.json b/simulations/vip-999/abi/AccessControlledERC20.json deleted file mode 100644 index 8fcda808f..000000000 --- a/simulations/vip-999/abi/AccessControlledERC20.json +++ /dev/null @@ -1,214 +0,0 @@ -[ - { - "inputs": [ - { "internalType": "string", "name": "name_", "type": "string" }, - { "internalType": "string", "name": "symbol_", "type": "string" }, - { "internalType": "uint8", "name": "decimals_", "type": "uint8" }, - { "internalType": "address", "name": "accessControlManager_", "type": "address" } - ], - "stateMutability": "nonpayable", - "type": "constructor" - }, - { "inputs": [], "name": "Unauthorized", "type": "error" }, - { "inputs": [], "name": "ZeroAddressNotAllowed", "type": "error" }, - { - "anonymous": false, - "inputs": [ - { "indexed": true, "internalType": "address", "name": "owner", "type": "address" }, - { "indexed": true, "internalType": "address", "name": "spender", "type": "address" }, - { "indexed": false, "internalType": "uint256", "name": "value", "type": "uint256" } - ], - "name": "Approval", - "type": "event" - }, - { - "anonymous": false, - "inputs": [ - { "indexed": true, "internalType": "address", "name": "oldAccessControlManager", "type": "address" }, - { "indexed": true, "internalType": "address", "name": "newAccessControlManager", "type": "address" } - ], - "name": "NewAccessControlManager", - "type": "event" - }, - { - "anonymous": false, - "inputs": [ - { "indexed": true, "internalType": "address", "name": "previousOwner", "type": "address" }, - { "indexed": true, "internalType": "address", "name": "newOwner", "type": "address" } - ], - "name": "OwnershipTransferStarted", - "type": "event" - }, - { - "anonymous": false, - "inputs": [ - { "indexed": true, "internalType": "address", "name": "previousOwner", "type": "address" }, - { "indexed": true, "internalType": "address", "name": "newOwner", "type": "address" } - ], - "name": "OwnershipTransferred", - "type": "event" - }, - { - "anonymous": false, - "inputs": [ - { "indexed": true, "internalType": "address", "name": "from", "type": "address" }, - { "indexed": true, "internalType": "address", "name": "to", "type": "address" }, - { "indexed": false, "internalType": "uint256", "name": "value", "type": "uint256" } - ], - "name": "Transfer", - "type": "event" - }, - { "inputs": [], "name": "acceptOwnership", "outputs": [], "stateMutability": "nonpayable", "type": "function" }, - { - "inputs": [], - "name": "accessControlManager", - "outputs": [{ "internalType": "address", "name": "", "type": "address" }], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [ - { "internalType": "address", "name": "owner", "type": "address" }, - { "internalType": "address", "name": "spender", "type": "address" } - ], - "name": "allowance", - "outputs": [{ "internalType": "uint256", "name": "", "type": "uint256" }], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [ - { "internalType": "address", "name": "spender", "type": "address" }, - { "internalType": "uint256", "name": "amount", "type": "uint256" } - ], - "name": "approve", - "outputs": [{ "internalType": "bool", "name": "", "type": "bool" }], - "stateMutability": "nonpayable", - "type": "function" - }, - { - "inputs": [{ "internalType": "address", "name": "account", "type": "address" }], - "name": "balanceOf", - "outputs": [{ "internalType": "uint256", "name": "", "type": "uint256" }], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [ - { "internalType": "address", "name": "account_", "type": "address" }, - { "internalType": "uint256", "name": "amount_", "type": "uint256" } - ], - "name": "burn", - "outputs": [], - "stateMutability": "nonpayable", - "type": "function" - }, - { - "inputs": [], - "name": "decimals", - "outputs": [{ "internalType": "uint8", "name": "", "type": "uint8" }], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [ - { "internalType": "address", "name": "spender", "type": "address" }, - { "internalType": "uint256", "name": "subtractedValue", "type": "uint256" } - ], - "name": "decreaseAllowance", - "outputs": [{ "internalType": "bool", "name": "", "type": "bool" }], - "stateMutability": "nonpayable", - "type": "function" - }, - { - "inputs": [ - { "internalType": "address", "name": "spender", "type": "address" }, - { "internalType": "uint256", "name": "addedValue", "type": "uint256" } - ], - "name": "increaseAllowance", - "outputs": [{ "internalType": "bool", "name": "", "type": "bool" }], - "stateMutability": "nonpayable", - "type": "function" - }, - { - "inputs": [ - { "internalType": "address", "name": "account_", "type": "address" }, - { "internalType": "uint256", "name": "amount_", "type": "uint256" } - ], - "name": "mint", - "outputs": [], - "stateMutability": "nonpayable", - "type": "function" - }, - { - "inputs": [], - "name": "name", - "outputs": [{ "internalType": "string", "name": "", "type": "string" }], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [], - "name": "owner", - "outputs": [{ "internalType": "address", "name": "", "type": "address" }], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [], - "name": "pendingOwner", - "outputs": [{ "internalType": "address", "name": "", "type": "address" }], - "stateMutability": "view", - "type": "function" - }, - { "inputs": [], "name": "renounceOwnership", "outputs": [], "stateMutability": "nonpayable", "type": "function" }, - { - "inputs": [{ "internalType": "address", "name": "newAccessControlManager_", "type": "address" }], - "name": "setAccessControlManager", - "outputs": [], - "stateMutability": "nonpayable", - "type": "function" - }, - { - "inputs": [], - "name": "symbol", - "outputs": [{ "internalType": "string", "name": "", "type": "string" }], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [], - "name": "totalSupply", - "outputs": [{ "internalType": "uint256", "name": "", "type": "uint256" }], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [ - { "internalType": "address", "name": "to", "type": "address" }, - { "internalType": "uint256", "name": "amount", "type": "uint256" } - ], - "name": "transfer", - "outputs": [{ "internalType": "bool", "name": "", "type": "bool" }], - "stateMutability": "nonpayable", - "type": "function" - }, - { - "inputs": [ - { "internalType": "address", "name": "from", "type": "address" }, - { "internalType": "address", "name": "to", "type": "address" }, - { "internalType": "uint256", "name": "amount", "type": "uint256" } - ], - "name": "transferFrom", - "outputs": [{ "internalType": "bool", "name": "", "type": "bool" }], - "stateMutability": "nonpayable", - "type": "function" - }, - { - "inputs": [{ "internalType": "address", "name": "newOwner", "type": "address" }], - "name": "transferOwnership", - "outputs": [], - "stateMutability": "nonpayable", - "type": "function" - } -] diff --git a/simulations/vip-999/abi/VenusERC20.json b/simulations/vip-999/abi/VenusERC20.json index 06c888bdf..174f57a0f 100644 --- a/simulations/vip-999/abi/VenusERC20.json +++ b/simulations/vip-999/abi/VenusERC20.json @@ -1,483 +1,480 @@ -{ - "abi": [ - { - "inputs": [ - { - "internalType": "string", - "name": "name_", - "type": "string" - }, - { - "internalType": "string", - "name": "symbol_", - "type": "string" - }, - { - "internalType": "uint8", - "name": "decimals_", - "type": "uint8" - }, - { - "internalType": "address", - "name": "accessControlManager_", - "type": "address" - } - ], - "stateMutability": "nonpayable", - "type": "constructor" - }, - { - "inputs": [], - "name": "Unauthorized", - "type": "error" - }, - { - "inputs": [], - "name": "ZeroAddressNotAllowed", - "type": "error" - }, - { - "anonymous": false, - "inputs": [ - { - "indexed": true, - "internalType": "address", - "name": "owner", - "type": "address" - }, - { - "indexed": true, - "internalType": "address", - "name": "spender", - "type": "address" - }, - { - "indexed": false, - "internalType": "uint256", - "name": "value", - "type": "uint256" - } - ], - "name": "Approval", - "type": "event" - }, - { - "anonymous": false, - "inputs": [ - { - "indexed": true, - "internalType": "address", - "name": "oldAccessControlManager", - "type": "address" - }, - { - "indexed": true, - "internalType": "address", - "name": "newAccessControlManager", - "type": "address" - } - ], - "name": "NewAccessControlManager", - "type": "event" - }, - { - "anonymous": false, - "inputs": [ - { - "indexed": true, - "internalType": "address", - "name": "previousOwner", - "type": "address" - }, - { - "indexed": true, - "internalType": "address", - "name": "newOwner", - "type": "address" - } - ], - "name": "OwnershipTransferStarted", - "type": "event" - }, - { - "anonymous": false, - "inputs": [ - { - "indexed": true, - "internalType": "address", - "name": "previousOwner", - "type": "address" - }, - { - "indexed": true, - "internalType": "address", - "name": "newOwner", - "type": "address" - } - ], - "name": "OwnershipTransferred", - "type": "event" - }, - { - "anonymous": false, - "inputs": [ - { - "indexed": true, - "internalType": "address", - "name": "from", - "type": "address" - }, - { - "indexed": true, - "internalType": "address", - "name": "to", - "type": "address" - }, - { - "indexed": false, - "internalType": "uint256", - "name": "value", - "type": "uint256" - } - ], - "name": "Transfer", - "type": "event" - }, - { - "inputs": [], - "name": "acceptOwnership", - "outputs": [], - "stateMutability": "nonpayable", - "type": "function" - }, - { - "inputs": [], - "name": "accessControlManager", - "outputs": [ - { - "internalType": "address", - "name": "", - "type": "address" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "address", - "name": "owner", - "type": "address" - }, - { - "internalType": "address", - "name": "spender", - "type": "address" - } - ], - "name": "allowance", - "outputs": [ - { - "internalType": "uint256", - "name": "", - "type": "uint256" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "address", - "name": "spender", - "type": "address" - }, - { - "internalType": "uint256", - "name": "amount", - "type": "uint256" - } - ], - "name": "approve", - "outputs": [ - { - "internalType": "bool", - "name": "", - "type": "bool" - } - ], - "stateMutability": "nonpayable", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "address", - "name": "account", - "type": "address" - } - ], - "name": "balanceOf", - "outputs": [ - { - "internalType": "uint256", - "name": "", - "type": "uint256" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "address", - "name": "account_", - "type": "address" - }, - { - "internalType": "uint256", - "name": "amount_", - "type": "uint256" - } - ], - "name": "burn", - "outputs": [], - "stateMutability": "nonpayable", - "type": "function" - }, - { - "inputs": [], - "name": "decimals", - "outputs": [ - { - "internalType": "uint8", - "name": "", - "type": "uint8" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "address", - "name": "spender", - "type": "address" - }, - { - "internalType": "uint256", - "name": "subtractedValue", - "type": "uint256" - } - ], - "name": "decreaseAllowance", - "outputs": [ - { - "internalType": "bool", - "name": "", - "type": "bool" - } - ], - "stateMutability": "nonpayable", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "address", - "name": "spender", - "type": "address" - }, - { - "internalType": "uint256", - "name": "addedValue", - "type": "uint256" - } - ], - "name": "increaseAllowance", - "outputs": [ - { - "internalType": "bool", - "name": "", - "type": "bool" - } - ], - "stateMutability": "nonpayable", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "address", - "name": "account_", - "type": "address" - }, - { - "internalType": "uint256", - "name": "amount_", - "type": "uint256" - } - ], - "name": "mint", - "outputs": [], - "stateMutability": "nonpayable", - "type": "function" - }, - { - "inputs": [], - "name": "name", - "outputs": [ - { - "internalType": "string", - "name": "", - "type": "string" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [], - "name": "owner", - "outputs": [ - { - "internalType": "address", - "name": "", - "type": "address" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [], - "name": "pendingOwner", - "outputs": [ - { - "internalType": "address", - "name": "", - "type": "address" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [], - "name": "renounceOwnership", - "outputs": [], - "stateMutability": "nonpayable", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "address", - "name": "newAccessControlManager_", - "type": "address" - } - ], - "name": "setAccessControlManager", - "outputs": [], - "stateMutability": "nonpayable", - "type": "function" - }, - { - "inputs": [], - "name": "symbol", - "outputs": [ - { - "internalType": "string", - "name": "", - "type": "string" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [], - "name": "totalSupply", - "outputs": [ - { - "internalType": "uint256", - "name": "", - "type": "uint256" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "address", - "name": "to", - "type": "address" - }, - { - "internalType": "uint256", - "name": "amount", - "type": "uint256" - } - ], - "name": "transfer", - "outputs": [ - { - "internalType": "bool", - "name": "", - "type": "bool" - } - ], - "stateMutability": "nonpayable", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "address", - "name": "from", - "type": "address" - }, - { - "internalType": "address", - "name": "to", - "type": "address" - }, - { - "internalType": "uint256", - "name": "amount", - "type": "uint256" - } - ], - "name": "transferFrom", - "outputs": [ - { - "internalType": "bool", - "name": "", - "type": "bool" - } - ], - "stateMutability": "nonpayable", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "address", - "name": "newOwner", - "type": "address" - } - ], - "name": "transferOwnership", - "outputs": [], - "stateMutability": "nonpayable", - "type": "function" - } - ], - "bytecode": "0x60a060405234801561000f575f80fd5b506040516112c03803806112c083398101604081905261002e916101cd565b8383600361003c83826102e5565b50600461004982826102e5565b50505061006261005d61009960201b60201c565b61009d565b61006b816100b9565b600780546001600160a01b0319166001600160a01b039290921691909117905560ff16608052506103a49050565b3390565b600680546001600160a01b03191690556100b6816100e0565b50565b6001600160a01b0381166100b6576040516342bcdf7f60e11b815260040160405180910390fd5b600580546001600160a01b038381166001600160a01b0319831681179093556040519116919082907f8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e0905f90a35050565b634e487b7160e01b5f52604160045260245ffd5b5f82601f830112610154575f80fd5b81516001600160401b038082111561016e5761016e610131565b604051601f8301601f19908116603f0116810190828211818310171561019657610196610131565b816040528381528660208588010111156101ae575f80fd5b8360208701602083015e5f602085830101528094505050505092915050565b5f805f80608085870312156101e0575f80fd5b84516001600160401b03808211156101f6575f80fd5b61020288838901610145565b95506020870151915080821115610217575f80fd5b5061022487828801610145565b935050604085015160ff8116811461023a575f80fd5b60608601519092506001600160a01b0381168114610256575f80fd5b939692955090935050565b600181811c9082168061027557607f821691505b60208210810361029357634e487b7160e01b5f52602260045260245ffd5b50919050565b601f8211156102e057805f5260205f20601f840160051c810160208510156102be5750805b601f840160051c820191505b818110156102dd575f81556001016102ca565b50505b505050565b81516001600160401b038111156102fe576102fe610131565b6103128161030c8454610261565b84610299565b602080601f831160018114610345575f841561032e5750858301515b5f19600386901b1c1916600185901b17855561039c565b5f85815260208120601f198616915b8281101561037357888601518255948401946001909101908401610354565b508582101561039057878501515f19600388901b60f8161c191681555b505060018460011b0185555b505050505050565b608051610f046103bc5f395f6101ad0152610f045ff3fe608060405234801561000f575f80fd5b5060043610610127575f3560e01c806379ba5097116100a9578063a9059cbb1161006e578063a9059cbb14610288578063b4a0bdf31461029b578063dd62ed3e146102ae578063e30c3978146102c1578063f2fde38b146102d2575f80fd5b806379ba50971461022d5780638da5cb5b1461023557806395d89b411461025a5780639dc29fac14610262578063a457c2d714610275575f80fd5b8063313ce567116100ef578063313ce567146101a657806339509351146101d757806340c10f19146101ea57806370a08231146101fd578063715018a614610225575f80fd5b806306fdde031461012b578063095ea7b3146101495780630e32cb861461016c57806318160ddd1461018157806323b872dd14610193575b5f80fd5b6101336102e5565b6040516101409190610d4e565b60405180910390f35b61015c610157366004610d82565b610375565b6040519015158152602001610140565b61017f61017a366004610daa565b61038e565b005b6002545b604051908152602001610140565b61015c6101a1366004610dc3565b6103fa565b60405160ff7f0000000000000000000000000000000000000000000000000000000000000000168152602001610140565b61015c6101e5366004610d82565b61041d565b61017f6101f8366004610d82565b61043e565b61018561020b366004610daa565b6001600160a01b03165f9081526020819052604090205490565b61017f610482565b61017f610495565b6005546001600160a01b03165b6040516001600160a01b039091168152602001610140565b610133610514565b61017f610270366004610d82565b610523565b61015c610283366004610d82565b610563565b61015c610296366004610d82565b6105dd565b600754610242906001600160a01b031681565b6101856102bc366004610dfc565b6105ea565b6006546001600160a01b0316610242565b61017f6102e0366004610daa565b610614565b6060600380546102f490610e2d565b80601f016020809104026020016040519081016040528092919081815260200182805461032090610e2d565b801561036b5780601f106103425761010080835404028352916020019161036b565b820191905f5260205f20905b81548152906001019060200180831161034e57829003601f168201915b5050505050905090565b5f33610382818585610685565b60019150505b92915050565b6103966107a9565b61039f81610803565b6007546040516001600160a01b038084169216907f66fd58e82f7b31a2a5c30e0888f3093efe4e111b00cd2b0c31fe014601293aa0905f90a3600780546001600160a01b0319166001600160a01b0392909216919091179055565b5f3361040785828561082a565b6104128585856108a2565b506001949350505050565b5f3361038281858561042f83836105ea565b6104399190610e65565b610685565b610474604051806040016040528060158152602001746d696e7428616464726573732c75696e743235362960581b815250610a44565b61047e8282610ad1565b5050565b61048a6107a9565b6104935f610b8e565b565b60065433906001600160a01b031681146105085760405162461bcd60e51b815260206004820152602960248201527f4f776e61626c6532537465703a2063616c6c6572206973206e6f7420746865206044820152683732bb9037bbb732b960b91b60648201526084015b60405180910390fd5b61051181610b8e565b50565b6060600480546102f490610e2d565b610559604051806040016040528060158152602001746275726e28616464726573732c75696e743235362960581b815250610a44565b61047e8282610ba7565b5f338161057082866105ea565b9050838110156105d05760405162461bcd60e51b815260206004820152602560248201527f45524332303a2064656372656173656420616c6c6f77616e63652062656c6f77604482015264207a65726f60d81b60648201526084016104ff565b6104128286868403610685565b5f336103828185856108a2565b6001600160a01b039182165f90815260016020908152604080832093909416825291909152205490565b61061c6107a9565b600680546001600160a01b0383166001600160a01b0319909116811790915561064d6005546001600160a01b031690565b6001600160a01b03167f38d16b8cac22d99fc7c124b9cd0de2d3fa1faef420bfe791d8c362d765e2270060405160405180910390a350565b6001600160a01b0383166106e75760405162461bcd60e51b8152602060048201526024808201527f45524332303a20617070726f76652066726f6d20746865207a65726f206164646044820152637265737360e01b60648201526084016104ff565b6001600160a01b0382166107485760405162461bcd60e51b815260206004820152602260248201527f45524332303a20617070726f766520746f20746865207a65726f206164647265604482015261737360f01b60648201526084016104ff565b6001600160a01b038381165f8181526001602090815260408083209487168084529482529182902085905590518481527f8c5be1e5ebec7d5bd14f71427d1e84f3dd0314c0f7b2291e5b200ac8c7c3b92591015b60405180910390a3505050565b6005546001600160a01b031633146104935760405162461bcd60e51b815260206004820181905260248201527f4f776e61626c653a2063616c6c6572206973206e6f7420746865206f776e657260448201526064016104ff565b6001600160a01b038116610511576040516342bcdf7f60e11b815260040160405180910390fd5b5f61083584846105ea565b90505f19811461089c578181101561088f5760405162461bcd60e51b815260206004820152601d60248201527f45524332303a20696e73756666696369656e7420616c6c6f77616e636500000060448201526064016104ff565b61089c8484848403610685565b50505050565b6001600160a01b0383166109065760405162461bcd60e51b815260206004820152602560248201527f45524332303a207472616e736665722066726f6d20746865207a65726f206164604482015264647265737360d81b60648201526084016104ff565b6001600160a01b0382166109685760405162461bcd60e51b815260206004820152602360248201527f45524332303a207472616e7366657220746f20746865207a65726f206164647260448201526265737360e81b60648201526084016104ff565b6001600160a01b0383165f90815260208190526040902054818110156109df5760405162461bcd60e51b815260206004820152602660248201527f45524332303a207472616e7366657220616d6f756e7420657863656564732062604482015265616c616e636560d01b60648201526084016104ff565b6001600160a01b038481165f81815260208181526040808320878703905593871680835291849020805487019055925185815290927fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef910160405180910390a361089c565b6007546040516318c5e8ab60e01b81526001600160a01b03909116906318c5e8ab90610a769033908590600401610e84565b602060405180830381865afa158015610a91573d5f803e3d5ffd5b505050506040513d601f19601f82011682018060405250810190610ab59190610eaf565b610511576040516282b42960e81b815260040160405180910390fd5b6001600160a01b038216610b275760405162461bcd60e51b815260206004820152601f60248201527f45524332303a206d696e7420746f20746865207a65726f20616464726573730060448201526064016104ff565b8060025f828254610b389190610e65565b90915550506001600160a01b0382165f81815260208181526040808320805486019055518481527fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef910160405180910390a35050565b600680546001600160a01b031916905561051181610ccf565b6001600160a01b038216610c075760405162461bcd60e51b815260206004820152602160248201527f45524332303a206275726e2066726f6d20746865207a65726f206164647265736044820152607360f81b60648201526084016104ff565b6001600160a01b0382165f9081526020819052604090205481811015610c7a5760405162461bcd60e51b815260206004820152602260248201527f45524332303a206275726e20616d6f756e7420657863656564732062616c616e604482015261636560f01b60648201526084016104ff565b6001600160a01b0383165f818152602081815260408083208686039055600280548790039055518581529192917fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef910161079c565b600580546001600160a01b038381166001600160a01b0319831681179093556040519116919082907f8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e0905f90a35050565b5f81518084528060208401602086015e5f602082860101526020601f19601f83011685010191505092915050565b602081525f610d606020830184610d20565b9392505050565b80356001600160a01b0381168114610d7d575f80fd5b919050565b5f8060408385031215610d93575f80fd5b610d9c83610d67565b946020939093013593505050565b5f60208284031215610dba575f80fd5b610d6082610d67565b5f805f60608486031215610dd5575f80fd5b610dde84610d67565b9250610dec60208501610d67565b9150604084013590509250925092565b5f8060408385031215610e0d575f80fd5b610e1683610d67565b9150610e2460208401610d67565b90509250929050565b600181811c90821680610e4157607f821691505b602082108103610e5f57634e487b7160e01b5f52602260045260245ffd5b50919050565b8082018082111561038857634e487b7160e01b5f52601160045260245ffd5b6001600160a01b03831681526040602082018190525f90610ea790830184610d20565b949350505050565b5f60208284031215610ebf575f80fd5b81518015158114610d60575f80fdfea26469706673582212207e13613c2cd266922c898bf18f6bf39f661af737abd0337a1c9972a6646ebe9c64736f6c63430008190033" -} +[ + { + "inputs": [ + { + "internalType": "string", + "name": "name_", + "type": "string" + }, + { + "internalType": "string", + "name": "symbol_", + "type": "string" + }, + { + "internalType": "uint8", + "name": "decimals_", + "type": "uint8" + }, + { + "internalType": "address", + "name": "accessControlManager_", + "type": "address" + } + ], + "stateMutability": "nonpayable", + "type": "constructor" + }, + { + "inputs": [], + "name": "Unauthorized", + "type": "error" + }, + { + "inputs": [], + "name": "ZeroAddressNotAllowed", + "type": "error" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "owner", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "spender", + "type": "address" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "value", + "type": "uint256" + } + ], + "name": "Approval", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "oldAccessControlManager", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "newAccessControlManager", + "type": "address" + } + ], + "name": "NewAccessControlManager", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "previousOwner", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "newOwner", + "type": "address" + } + ], + "name": "OwnershipTransferStarted", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "previousOwner", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "newOwner", + "type": "address" + } + ], + "name": "OwnershipTransferred", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "from", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "to", + "type": "address" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "value", + "type": "uint256" + } + ], + "name": "Transfer", + "type": "event" + }, + { + "inputs": [], + "name": "acceptOwnership", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "accessControlManager", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "owner", + "type": "address" + }, + { + "internalType": "address", + "name": "spender", + "type": "address" + } + ], + "name": "allowance", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "spender", + "type": "address" + }, + { + "internalType": "uint256", + "name": "amount", + "type": "uint256" + } + ], + "name": "approve", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "account", + "type": "address" + } + ], + "name": "balanceOf", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "account_", + "type": "address" + }, + { + "internalType": "uint256", + "name": "amount_", + "type": "uint256" + } + ], + "name": "burn", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "decimals", + "outputs": [ + { + "internalType": "uint8", + "name": "", + "type": "uint8" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "spender", + "type": "address" + }, + { + "internalType": "uint256", + "name": "subtractedValue", + "type": "uint256" + } + ], + "name": "decreaseAllowance", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "spender", + "type": "address" + }, + { + "internalType": "uint256", + "name": "addedValue", + "type": "uint256" + } + ], + "name": "increaseAllowance", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "account_", + "type": "address" + }, + { + "internalType": "uint256", + "name": "amount_", + "type": "uint256" + } + ], + "name": "mint", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "name", + "outputs": [ + { + "internalType": "string", + "name": "", + "type": "string" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "owner", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "pendingOwner", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "renounceOwnership", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "newAccessControlManager_", + "type": "address" + } + ], + "name": "setAccessControlManager", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "symbol", + "outputs": [ + { + "internalType": "string", + "name": "", + "type": "string" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "totalSupply", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "to", + "type": "address" + }, + { + "internalType": "uint256", + "name": "amount", + "type": "uint256" + } + ], + "name": "transfer", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "from", + "type": "address" + }, + { + "internalType": "address", + "name": "to", + "type": "address" + }, + { + "internalType": "uint256", + "name": "amount", + "type": "uint256" + } + ], + "name": "transferFrom", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "newOwner", + "type": "address" + } + ], + "name": "transferOwnership", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + } +] diff --git a/simulations/vip-999/bscmainnet.ts b/simulations/vip-999/bscmainnet.ts index 503b2a715..68149c7cf 100644 --- a/simulations/vip-999/bscmainnet.ts +++ b/simulations/vip-999/bscmainnet.ts @@ -9,12 +9,12 @@ import { forking, testVip } from "src/vip-framework"; import vip999, { ATLAS_ORACLE, + BOUND_VALIDATOR, BTCB, BTCB_FEED_FALLBACK, BTCB_FEED_MAIN, BTCB_FEED_PIVOT, BTCB_UPPER_BOUND, - BOUND_VALIDATOR, CHAINLINK_ORACLE, FIXED_RATE_VAULT_CONTROLLER, INSTITUTION_OPERATOR, @@ -26,12 +26,12 @@ import vip999, { vaultConfig, } from "../../vips/vip-999/bscmainnet"; import ACM_ABI from "./abi/AccessControlManager.json"; -import ERC20_ABI from "./abi/VenusERC20.json"; import VAULT_ABI from "./abi/InstitutionalLoanVault.json"; import CONTROLLER_ABI from "./abi/InstitutionalVaultController.json"; import PROXY_ADMIN_ABI from "./abi/ProxyAdmin.json"; import ORACLE_ABI from "./abi/ResilientOracle.json"; - +import ERC20_ABI from "./abi/VenusERC20.json"; + const BOUND_VALIDATOR_ABI = ["function validateConfigs(address) external view returns (uint256, uint256)"]; const { bscmainnet } = NETWORK_ADDRESSES; @@ -169,10 +169,8 @@ forking(FORK_BLOCK, async () => { describe("Post-VIP behavior", () => { it("vceBTC is priced close to BTCB", async () => { const vceBtcPrice = await oracle.getPrice(VCEBTC); - const diff = vceBtcPrice.gt(btcbPriceAtFork) - ? vceBtcPrice.sub(btcbPriceAtFork) - : btcbPriceAtFork.sub(vceBtcPrice); - expect(diff.mul(100).lte(btcbPriceAtFork)).to.equal(true); // within 1% + const tolerance = btcbPriceAtFork.div(100); // 1% tolerance + expect(vceBtcPrice).to.be.closeTo(btcbPriceAtFork, tolerance); }); it("the Normal Timelock accepted ownership of vceBTC", async () => { From 3686cc45d3a9165b9339c2e4b0635e6c2a79761b Mon Sep 17 00:00:00 2001 From: GitGuru7 Date: Mon, 6 Jul 2026 19:05:54 +0530 Subject: [PATCH 06/15] feat: add testnet VIP-999 and drop createVault grant from both VIPs --- simulations/vip-999/bscmainnet.ts | 2 +- simulations/vip-999/bsctestnet.ts | 314 ++++++++++++++++++++++++++++++ vips/vip-999/bscmainnet.ts | 13 +- vips/vip-999/bsctestnet.ts | 155 +++++++++++++++ 4 files changed, 472 insertions(+), 12 deletions(-) create mode 100644 simulations/vip-999/bsctestnet.ts create mode 100644 vips/vip-999/bsctestnet.ts diff --git a/simulations/vip-999/bscmainnet.ts b/simulations/vip-999/bscmainnet.ts index 68149c7cf..9a3bef6cb 100644 --- a/simulations/vip-999/bscmainnet.ts +++ b/simulations/vip-999/bscmainnet.ts @@ -36,7 +36,7 @@ const BOUND_VALIDATOR_ABI = ["function validateConfigs(address) external view re const { bscmainnet } = NETWORK_ADDRESSES; -const FORK_BLOCK = 108402150; // shortly after vceBTC's deployment +const FORK_BLOCK = 108402150; const ONE_YEAR = 31536000; // A separate, not-yet-executed VIP upgrades the controller/vault implementations and diff --git a/simulations/vip-999/bsctestnet.ts b/simulations/vip-999/bsctestnet.ts new file mode 100644 index 000000000..a7c33e0de --- /dev/null +++ b/simulations/vip-999/bsctestnet.ts @@ -0,0 +1,314 @@ +import { time } from "@nomicfoundation/hardhat-network-helpers"; +import { expect } from "chai"; +import { BigNumber, Contract } from "ethers"; +import { parseUnits } from "ethers/lib/utils"; +import { ethers } from "hardhat"; +import { NETWORK_ADDRESSES } from "src/networkAddresses"; +import { initMainnetUser, setMaxStalePeriodForAllAssets, setMaxStalePeriodInChainlinkOracle } from "src/utils"; +import { forking, testVip } from "src/vip-framework"; + +import vip999, { + BTCB, + BTCB_FEED_MAIN, + CHAINLINK_ORACLE, + FIXED_RATE_VAULT_CONTROLLER, + INSTITUTION_OPERATOR, + SUPPLY_ASSET, + VCEBTC, + VCEBTC_INITIAL_SUPPLY, + instConfig, + vaultConfig, +} from "../../vips/vip-999/bsctestnet"; +import ACM_ABI from "./abi/AccessControlManager.json"; +import VAULT_ABI from "./abi/InstitutionalLoanVault.json"; +import CONTROLLER_ABI from "./abi/InstitutionalVaultController.json"; +import PROXY_ADMIN_ABI from "./abi/ProxyAdmin.json"; +import ORACLE_ABI from "./abi/ResilientOracle.json"; +import ERC20_ABI from "./abi/VenusERC20.json"; + +const { bsctestnet } = NETWORK_ADDRESSES; + +const FORK_BLOCK = 117554000; +const ONE_YEAR = 31536000; + +// A separate, not-yet-executed VIP upgrades the controller/vault implementations to the +// versions supporting the 6-argument createVault (with institution name). Reproduced here. +const PROXY_ADMIN = "0x7877ffd62649b6a1557b55d4c20fcbab17344c91"; +const NEW_CONTROLLER_IMPLEMENTATION = "0xC36dFaCc7a125859C106F29b9F2d874CCF29A55A"; +const NEW_VAULT_IMPLEMENTATION = "0x97421799419Eb782628e73e7220d8E0A207469a3"; + +const USDT_FAUCET_ABI = ["function allocateTo(address to, uint256 amount) external"]; +const LENDER = "0x2222222222222222222222222222222222222222"; // dummy test lender + +const CHAINLINK_ORACLE_GETPRICE_ABI = ["function getPrice(address) external view returns (uint256)"]; + +const STUB_ORACLE_BYTECODE = + "0x6080604052348015600e575f80fd5b5061015e8061001c5f395ff3fe608060405234801561000f575f80fd5b5060043610610029575f3560e01c806341976e091461002d575b5f80fd5b610047600480360381019061004291906100cc565b61005d565b604051610054919061010f565b60405180910390f35b5f670de0b6b3a76400009050919050565b5f80fd5b5f73ffffffffffffffffffffffffffffffffffffffff82169050919050565b5f61009b82610072565b9050919050565b6100ab81610091565b81146100b5575f80fd5b50565b5f813590506100c6816100a2565b92915050565b5f602082840312156100e1576100e061006e565b5b5f6100ee848285016100b8565b91505092915050565b5f819050919050565b610109816100f7565b82525050565b5f6020820190506101225f830184610100565b9291505056fea26469706673582212209282f7f2d85233912d0088d6dc45ce2459097d2866597e41f0a286059758c12c64736f6c63430008190033"; +const STUB_ORACLE_ABI = ["function getPrice(address) external pure returns (uint256)"]; + +// IVaultTypes.VaultState +const VaultState = { + WaitingForMargin: 0, + MarginDeposited: 1, + Fundraising: 2, + Lock: 4, + PendingSettlement: 5, + Matured: 7, +}; + +forking(FORK_BLOCK, async () => { + let oracle: Contract; + let acm: Contract; + let controller: Contract; + let vceBTC: Contract; + let btcb: Contract; + let usdt: Contract; + let timelock: any; + let vaultsBefore: any; + + before(async () => { + oracle = await ethers.getContractAt(ORACLE_ABI, bsctestnet.RESILIENT_ORACLE); + acm = await ethers.getContractAt(ACM_ABI, bsctestnet.ACCESS_CONTROL_MANAGER); + controller = await ethers.getContractAt(CONTROLLER_ABI, FIXED_RATE_VAULT_CONTROLLER); + btcb = await ethers.getContractAt(ERC20_ABI, BTCB); + usdt = await ethers.getContractAt(ERC20_ABI, SUPPLY_ASSET); + timelock = await initMainnetUser(bsctestnet.NORMAL_TIMELOCK, parseUnits("40")); + vaultsBefore = await controller.allVaultsLength(); + + // Bump the stale period for the pre-existing assets so their prices remain readable on the fork. + await setMaxStalePeriodForAllAssets(oracle, [btcb, usdt]); + + // --- Reproduce the separate controller/vault-upgrade VIP's end state --- + const proxyAdmin = await ethers.getContractAt(PROXY_ADMIN_ABI, PROXY_ADMIN); + const proxyAdminOwner = await initMainnetUser(await proxyAdmin.owner(), parseUnits("1")); + await proxyAdmin.connect(proxyAdminOwner).upgrade(FIXED_RATE_VAULT_CONTROLLER, NEW_CONTROLLER_IMPLEMENTATION); + await controller.connect(timelock).setVaultImplementation(NEW_VAULT_IMPLEMENTATION); + await acm + .connect(timelock) + .giveCallPermission( + FIXED_RATE_VAULT_CONTROLLER, + "createVault(VaultConfig,InstitutionalConfig,RiskConfig,string,string,string)", + bsctestnet.NORMAL_TIMELOCK, + ); + + // Point the controller at a stub oracle so createVault can price vceBTC during the + // time-advanced execute() (see the STUB_ORACLE_BYTECODE comment). Restored in the callback. + 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(); + await controller.connect(timelock).setOracle(stubOracle.address); + + vceBTC = await ethers.getContractAt(ERC20_ABI, VCEBTC); + }); + + describe("Pre-VIP behavior", () => { + it("vceBTC is deployed with correct ACM, name, symbol, and decimals", async () => { + expect(await vceBTC.accessControlManager()).to.equal(bsctestnet.ACCESS_CONTROL_MANAGER); + expect(await vceBTC.name()).to.equal("Ceffu Custody BTC for Venus"); + expect(await vceBTC.symbol()).to.equal("vceBTC"); + expect(await vceBTC.decimals()).to.equal(18); + expect(await vceBTC.totalSupply()).to.equal(0); + }); + + it("ownership has been offered to the Normal Timelock but not yet accepted", async () => { + expect(await vceBTC.pendingOwner()).to.equal(bsctestnet.NORMAL_TIMELOCK); + expect(await vceBTC.owner()).to.not.equal(bsctestnet.NORMAL_TIMELOCK); + }); + + it("vceBTC has no oracle config yet (getPrice reverts)", async () => { + await expect(oracle.getPrice(VCEBTC)).to.be.reverted; + }); + + it("Timelock and Guardian cannot yet mint/burn vceBTC", async () => { + const vceBtcAsCaller = await initMainnetUser(VCEBTC, parseUnits("1")); + expect( + await acm.connect(vceBtcAsCaller).isAllowedToCall(bsctestnet.NORMAL_TIMELOCK, "mint(address,uint256)"), + ).to.equal(false); + expect(await acm.connect(vceBtcAsCaller).isAllowedToCall(bsctestnet.GUARDIAN, "mint(address,uint256)")).to.equal( + false, + ); + }); + }); + + testVip("VIP-999 Create Ceffu Custody BTC Fixed Rate Vault (Testnet)", await vip999(), { + callbackAfterExecution: async () => { + // vceBTC's oracle config (Chainlink main only) is only created by the VIP itself, so its + // stale period can only be bumped now — needed so the price survives the ~37-day time + // travel in the vault lifecycle test below. + await setMaxStalePeriodInChainlinkOracle( + CHAINLINK_ORACLE, + VCEBTC, + BTCB_FEED_MAIN, + bsctestnet.NORMAL_TIMELOCK, + ONE_YEAR, + ); + // Restore the real ResilientOracle now that vceBTC is priceable with a bumped stale period. + await controller.connect(timelock).setOracle(bsctestnet.RESILIENT_ORACLE); + }, + }); + + describe("Post-VIP behavior", () => { + it("vceBTC is priced from the Chainlink feed cloned from BTCB's config", async () => { + const chainlink = await ethers.getContractAt(CHAINLINK_ORACLE_GETPRICE_ABI, CHAINLINK_ORACLE); + const vceBtcPrice = await oracle.getPrice(VCEBTC); + expect(vceBtcPrice).to.be.gt(0); + // ResilientOracle proxies to the Chainlink main sub-oracle, which reads the cloned feed. + expect(vceBtcPrice).to.equal(await chainlink.getPrice(VCEBTC)); + }); + + it("the Normal Timelock accepted ownership of vceBTC", async () => { + expect(await vceBTC.owner()).to.equal(bsctestnet.NORMAL_TIMELOCK); + expect(await vceBTC.pendingOwner()).to.equal(ethers.constants.AddressZero); + }); + + it("Timelock and Guardian can mint/burn vceBTC", async () => { + const vceBtcAsCaller = await initMainnetUser(VCEBTC, parseUnits("1")); + expect( + await acm.connect(vceBtcAsCaller).isAllowedToCall(bsctestnet.NORMAL_TIMELOCK, "mint(address,uint256)"), + ).to.equal(true); + expect(await acm.connect(vceBtcAsCaller).isAllowedToCall(bsctestnet.GUARDIAN, "mint(address,uint256)")).to.equal( + true, + ); + expect( + await acm.connect(vceBtcAsCaller).isAllowedToCall(bsctestnet.NORMAL_TIMELOCK, "burn(address,uint256)"), + ).to.equal(true); + expect(await acm.connect(vceBtcAsCaller).isAllowedToCall(bsctestnet.GUARDIAN, "burn(address,uint256)")).to.equal( + true, + ); + }); + + it("initial vceBTC collateral was minted to the Venus Treasury", async () => { + expect(await vceBTC.totalSupply()).to.equal(VCEBTC_INITIAL_SUPPLY); + expect(await vceBTC.balanceOf(bsctestnet.VTREASURY)).to.equal(VCEBTC_INITIAL_SUPPLY); + }); + + it("a Fixed Rate Vault backed by vceBTC was created", async () => { + expect(await controller.allVaultsLength()).to.equal(vaultsBefore.add(1)); + const vaultAddress = await controller.allVaults(vaultsBefore); + expect(await controller.isRegistered(vaultAddress)).to.equal(true); + + const vault = await ethers.getContractAt(VAULT_ABI, vaultAddress); + expect((await vault.config()).supplyAsset).to.equal(SUPPLY_ASSET); + expect((await vault.institutionalConfig()).collateralAsset).to.equal(VCEBTC); + expect((await vault.institutionalConfig()).institutionOperator).to.equal(INSTITUTION_OPERATOR); + }); + }); + + describe("Oracle configuration for BTCB and vceBTC", () => { + it("BTCB price is available from ResilientOracle", async () => { + const btcbPrice = await oracle.getPrice(BTCB); + expect(btcbPrice).to.be.gt(0); + }); + + it("vceBTC price is available from ResilientOracle", async () => { + const vceBtcPrice = await oracle.getPrice(VCEBTC); + expect(vceBtcPrice).to.be.gt(0); + }); + + it("vceBTC price equals the value derived from its cloned Chainlink feed", async () => { + const chainlink = await ethers.getContractAt(CHAINLINK_ORACLE_GETPRICE_ABI, CHAINLINK_ORACLE); + const vceBtcPrice = await oracle.getPrice(VCEBTC); + expect(vceBtcPrice).to.equal(await chainlink.getPrice(VCEBTC)); + }); + }); + + describe("Vault lifecycle", () => { + let vault: Contract; + let institution: any; + let lender: any; + + const idealCollateralAmount = BigNumber.from(instConfig[1]); + const marginRate = BigNumber.from(instConfig[2]); + const minBorrowCap = BigNumber.from(vaultConfig[3]); + const openDuration = Number(vaultConfig[6]); + const lockDuration = Number(vaultConfig[7]); + const marginAmount = idealCollateralAmount.mul(marginRate).div(parseUnits("1", 18)); + const lenderDepositAmount = minBorrowCap.mul(2); + + before(async () => { + const vaultAddress = await controller.allVaults(vaultsBefore); + vault = await ethers.getContractAt(VAULT_ABI, vaultAddress); + + institution = await initMainnetUser(INSTITUTION_OPERATOR, parseUnits("1")); + lender = await initMainnetUser(LENDER, parseUnits("1")); + + // Fund the institution with vceBTC collateral and both parties with the supply asset (USDT). + await vceBTC.connect(timelock).mint(INSTITUTION_OPERATOR, idealCollateralAmount); + const [deployer] = await ethers.getSigners(); + const usdtFaucet = await ethers.getContractAt(USDT_FAUCET_ABI, SUPPLY_ASSET); + await usdtFaucet.connect(deployer).allocateTo(LENDER, lenderDepositAmount); + await usdtFaucet.connect(deployer).allocateTo(INSTITUTION_OPERATOR, lenderDepositAmount.div(5)); // repay principal + interest + }); + + it("institution deposits margin collateral (WaitingForMargin -> MarginDeposited)", async () => { + expect(await vault.state()).to.equal(VaultState.WaitingForMargin); + await vceBTC.connect(institution).approve(vault.address, idealCollateralAmount); + await vault.connect(institution).depositCollateral(marginAmount); + expect(await vault.state()).to.equal(VaultState.MarginDeposited); + }); + + it("controller opens the vault (MarginDeposited -> Fundraising)", async () => { + await controller.connect(timelock).openVault(vault.address); + expect(await vault.state()).to.equal(VaultState.Fundraising); + }); + + it("institution tops up collateral to the full ideal amount", async () => { + await vault.connect(institution).depositCollateral(idealCollateralAmount.sub(marginAmount)); + expect(await vceBTC.balanceOf(vault.address)).to.equal(idealCollateralAmount); + }); + + it("a lender deposits the supply asset", async () => { + await usdt.connect(lender).approve(vault.address, lenderDepositAmount); + await vault.connect(lender).deposit(lenderDepositAmount, LENDER); + expect(await vault.balanceOf(LENDER)).to.be.gt(0); + }); + + it("the open window elapses and the vault locks (Fundraising -> Lock)", async () => { + await time.increase(openDuration + 1); + await vault.updateVaultState(); + expect(await vault.state()).to.equal(VaultState.Lock); + }); + + it("the institution claims the raised funds", async () => { + const usdtBefore = await usdt.balanceOf(INSTITUTION_OPERATOR); + await vault.connect(institution).claimRaisedFunds(); + expect(await usdt.balanceOf(INSTITUTION_OPERATOR)).to.equal(usdtBefore.add(lenderDepositAmount)); + expect(await vault.outstandingDebt()).to.be.gt(lenderDepositAmount); // principal + accrued interest + }); + + it("the institution repays in full after the lock period (Lock -> PendingSettlement -> Matured)", async () => { + await time.increase(lockDuration + 1); + await vault.updateVaultState(); + expect(await vault.state()).to.equal(VaultState.PendingSettlement); + + const owed = await vault.outstandingDebt(); + await usdt.connect(institution).approve(vault.address, owed); + await vault.connect(institution).repay(owed); + await vault.updateVaultState(); + expect(await vault.state()).to.equal(VaultState.Matured); + expect(await vault.outstandingDebt()).to.equal(0); + }); + + it("the lender redeems principal plus interest", async () => { + const shares = await vault.balanceOf(LENDER); + const expectedAssets = await vault.previewRedeem(shares); + expect(expectedAssets).to.be.gt(lenderDepositAmount); // net of the reserve-factor cut + + const usdtBefore = await usdt.balanceOf(LENDER); + await vault.connect(lender).redeem(shares, LENDER, LENDER); + expect(await usdt.balanceOf(LENDER)).to.equal(usdtBefore.add(expectedAssets)); + expect(await vault.balanceOf(LENDER)).to.equal(0); + }); + + it("the institution withdraws its collateral", async () => { + const collateral = await vceBTC.balanceOf(vault.address); + expect(collateral).to.equal(idealCollateralAmount); + + const before = await vceBTC.balanceOf(INSTITUTION_OPERATOR); + await vault.connect(institution).withdrawCollateral(collateral); + expect(await vceBTC.balanceOf(INSTITUTION_OPERATOR)).to.equal(before.add(collateral)); + expect(await vceBTC.balanceOf(vault.address)).to.equal(0); + }); + }); +}); diff --git a/vips/vip-999/bscmainnet.ts b/vips/vip-999/bscmainnet.ts index b631a3365..0e73cac12 100644 --- a/vips/vip-999/bscmainnet.ts +++ b/vips/vip-999/bscmainnet.ts @@ -80,7 +80,7 @@ This proposal onboards a new custody-mirror collateral token, **vceBTC ("Ceffu C 1. **Oracle** — price vceBTC identically to BTCB by cloning BTCB's full oracle configuration: the main / pivot / fallback sub-oracle feeds, the BoundValidator bounds, and the ResilientOracle token config. 2. **Ownership** — accept ownership of vceBTC (already transferred to the Normal Timelock by the deployer). -3. **Access control** — grant \`mint(address,uint256)\` and \`burn(address,uint256)\` on vceBTC to the Normal Timelock and the Guardian, and grant \`createVault(...)\` on the InstitutionalVaultController to the Normal Timelock. +3. **Access control** — grant \`mint(address,uint256)\` and \`burn(address,uint256)\` on vceBTC to the Normal Timelock and the Guardian. (The \`createVault(...)\` permission on the InstitutionalVaultController is granted by the separate controller/vault-upgrade VIP.) 4. **Initial supply** — mint the initial vceBTC collateral to the Venus Treasury. 5. **Vault creation** — create the Fixed Rate Vault with vceBTC as collateral.`, forDescription: "I agree that Venus Protocol should proceed with this proposal", @@ -129,7 +129,7 @@ This proposal onboards a new custody-mirror collateral token, **vceBTC ("Ceffu C }, // ────────────────────────────────────────────────────────────────────── - // 3. Access control — vceBTC mint/burn + createVault + // 3. Access control — vceBTC mint/burn // ────────────────────────────────────────────────────────────────────── { target: bscmainnet.ACCESS_CONTROL_MANAGER, @@ -151,15 +151,6 @@ This proposal onboards a new custody-mirror collateral token, **vceBTC ("Ceffu C signature: "giveCallPermission(address,string,address)", params: [VCEBTC, "burn(address,uint256)", bscmainnet.CRITICAL_GUARDIAN], }, - { - target: bscmainnet.ACCESS_CONTROL_MANAGER, - signature: "giveCallPermission(address,string,address)", - params: [ - FIXED_RATE_VAULT_CONTROLLER, - "createVault(VaultConfig,InstitutionalConfig,RiskConfig,string,string,string)", - bscmainnet.NORMAL_TIMELOCK, - ], - }, // ────────────────────────────────────────────────────────────────────── // 4. Mint initial vceBTC collateral diff --git a/vips/vip-999/bsctestnet.ts b/vips/vip-999/bsctestnet.ts new file mode 100644 index 000000000..bab1da78e --- /dev/null +++ b/vips/vip-999/bsctestnet.ts @@ -0,0 +1,155 @@ +import { parseUnits } from "ethers/lib/utils"; +import { NETWORK_ADDRESSES } from "src/networkAddresses"; +import { ProposalType } from "src/types"; +import { makeProposal } from "src/utils"; + +const { bsctestnet } = NETWORK_ADDRESSES; + +// Deployed vceBTC (testnet) +export const VCEBTC = "0x3C5Fc884BF6d1Ec8957A75EF6436b3B5750A57da"; + +export const FIXED_RATE_VAULT_CONTROLLER = "0x36bA78812Ffff64B9ec060a1F07FcFa2012f6F89"; +export const BTCB = "0xA808e341e8e723DC6BA0Bb5204Bafc2330d7B8e4"; + +// BTCB's oracle config on testnet is Chainlink main only +export const CHAINLINK_ORACLE = bsctestnet.CHAINLINK_ORACLE; +export const ADDRESS_ZERO = "0x0000000000000000000000000000000000000000"; +export const BTCB_FEED_MAIN = "0x5741306c21795FdCBb9b265Ea0255F499DFe515C"; +export const BTCB_STALE_MAIN = 86400; + +// Supply asset for the loan market — testnet USDT (6 decimals) +export const SUPPLY_ASSET = "0xA11c8D9DC9b66E209Ef60F0C8D969D3CD988782c"; + +// Test institution operator (placeholder EOA on testnet) +export const INSTITUTION_OPERATOR = "0x1111111111111111111111111111111111111111"; + +// Initial vceBTC collateral supply +export const VCEBTC_INITIAL_SUPPLY = parseUnits("2000", 18); + +// Fixed Rate Vault configuration. +// VaultConfig: [supplyAsset, fixedAPY(bps), reserveFactor(1e18), minBorrowCap, +// maxBorrowCap, minSupplierDeposit, openDuration, lockDuration, settlementWindow] +// NB: borrow caps / minSupplierDeposit are denominated in the supply asset (USDT, 6 decimals). +export const vaultConfig = [ + SUPPLY_ASSET, + 800, // fixedAPY = 8% + parseUnits("0.1", 18), // reserveFactor = 10% + parseUnits("1000000", 6), // minBorrowCap = 1M USDT + parseUnits("50000000", 6), // maxBorrowCap = 50M USDT loan market size + 0, // minSupplierDeposit + 7 * 24 * 60 * 60, // openDuration = 7 days + 30 * 24 * 60 * 60, // lockDuration = 30 days + 30 * 24 * 60 * 60, // settlementWindow = 30 days +]; + +// InstitutionalConfig: [collateralAsset, idealCollateralAmount, marginRate(1e18), +// institutionOperator, positionTokenId] +export const instConfig = [ + VCEBTC, // collateral = vceBTC (18 decimals) + parseUnits("2000", 18), // idealCollateralAmount + parseUnits("0.1", 18), // marginRate = 10% + INSTITUTION_OPERATOR, + 0, // positionTokenId assigned by the controller +]; + +// RiskConfig: [liquidationThreshold(1e18), liquidationIncentive(1e18), latePenaltyRate(1e18)] +export const riskConfig = [parseUnits("0.85", 18), parseUnits("1.1", 18), parseUnits("1.1", 18)]; + +export const VAULT_SHARE_NAME = "Venus Ceffu Fixed Rate Vault"; +export const VAULT_SHARE_SYMBOL = "vceFRV"; +export const INSTITUTION_NAME = "Ceffu"; + +export const vip999 = () => { + const meta = { + version: "v2", + title: "VIP-999 [BNB Chain Testnet] Create Ceffu Custody BTC Fixed Rate Vault (DRAFT)", + description: `#### Summary + +This proposal onboards a new custody-mirror collateral token, **vceBTC ("Ceffu Custody BTC for Venus")**, and creates a new Venus **Fixed Rate Vault** (Institutional Loan Vault) that uses vceBTC as collateral. vceBTC is an accounting mirror for BTC held in custody by Ceffu; its supply is controlled by Venus governance and priced identically to BTCB. + +#### Actions + +1. **Oracle** — price vceBTC identically to BTCB by cloning BTCB's oracle configuration: the Chainlink main sub-oracle feed and the ResilientOracle token config (pivot / fallback disabled, no bound validation — matching BTCB on testnet). +2. **Ownership** — accept ownership of vceBTC (already transferred to the Normal Timelock by the deployer). +3. **Access control** — grant \`mint(address,uint256)\` and \`burn(address,uint256)\` on vceBTC to the Normal Timelock and the Guardian. (The \`createVault(...)\` permission on the InstitutionalVaultController is granted by the separate controller/vault-upgrade VIP.) +4. **Initial supply** — mint the initial vceBTC collateral to the Venus Treasury. +5. **Vault creation** — create the Fixed Rate Vault with vceBTC as collateral.`, + forDescription: "I agree that Venus Protocol should proceed with this proposal", + againstDescription: "I do not think that Venus Protocol should proceed with this proposal", + abstainDescription: "I am indifferent to whether Venus Protocol proceeds or not", + }; + + return makeProposal( + [ + // ────────────────────────────────────────────────────────────────────── + // 1. Oracle configuration identically to BTCB (Chainlink main only) + // ────────────────────────────────────────────────────────────────────── + { + target: CHAINLINK_ORACLE, + signature: "setTokenConfig((address,address,uint256))", + params: [[VCEBTC, BTCB_FEED_MAIN, BTCB_STALE_MAIN]], + }, + { + target: bsctestnet.RESILIENT_ORACLE, + signature: "setTokenConfig((address,address[3],bool[3],bool))", + params: [[VCEBTC, [CHAINLINK_ORACLE, ADDRESS_ZERO, ADDRESS_ZERO], [true, false, false], false]], + }, + + // ────────────────────────────────────────────────────────────────────── + // 2. Accept ownership of vceBTC + // ────────────────────────────────────────────────────────────────────── + { + target: VCEBTC, + signature: "acceptOwnership()", + params: [], + }, + + // ────────────────────────────────────────────────────────────────────── + // 3. Access control — vceBTC mint/burn + // ────────────────────────────────────────────────────────────────────── + { + target: bsctestnet.ACCESS_CONTROL_MANAGER, + signature: "giveCallPermission(address,string,address)", + params: [VCEBTC, "mint(address,uint256)", bsctestnet.NORMAL_TIMELOCK], + }, + { + target: bsctestnet.ACCESS_CONTROL_MANAGER, + signature: "giveCallPermission(address,string,address)", + params: [VCEBTC, "mint(address,uint256)", bsctestnet.GUARDIAN], + }, + { + target: bsctestnet.ACCESS_CONTROL_MANAGER, + signature: "giveCallPermission(address,string,address)", + params: [VCEBTC, "burn(address,uint256)", bsctestnet.NORMAL_TIMELOCK], + }, + { + target: bsctestnet.ACCESS_CONTROL_MANAGER, + signature: "giveCallPermission(address,string,address)", + params: [VCEBTC, "burn(address,uint256)", bsctestnet.GUARDIAN], + }, + + // ────────────────────────────────────────────────────────────────────── + // 4. Mint initial vceBTC collateral + // ────────────────────────────────────────────────────────────────────── + { + target: VCEBTC, + signature: "mint(address,uint256)", + params: [bsctestnet.VTREASURY, VCEBTC_INITIAL_SUPPLY], + }, + + // ────────────────────────────────────────────────────────────────────── + // 5. Create the Fixed Rate Vault (vceBTC as collateral) + // ────────────────────────────────────────────────────────────────────── + { + target: FIXED_RATE_VAULT_CONTROLLER, + signature: + "createVault((address,uint256,uint256,uint256,uint256,uint256,uint40,uint40,uint40),(address,uint256,uint256,address,uint256),(uint256,uint256,uint256),string,string,string)", + params: [vaultConfig, instConfig, riskConfig, VAULT_SHARE_NAME, VAULT_SHARE_SYMBOL, INSTITUTION_NAME], + }, + ], + meta, + ProposalType.REGULAR, + ); +}; + +export default vip999; From 80e19e5ab969e001f933c296b40ecfcb800542e5 Mon Sep 17 00:00:00 2001 From: GitGuru7 Date: Mon, 6 Jul 2026 20:26:26 +0530 Subject: [PATCH 07/15] refactor: repoint vault to live controller, pretend-execute upgrade in sim, fix price --- .../vip-999/abi/InstitutionalLoanVault.json | 50 ++++++ simulations/vip-999/bsctestnet.ts | 158 ++++++++---------- vips/vip-999/bsctestnet.ts | 43 ++--- 3 files changed, 143 insertions(+), 108 deletions(-) diff --git a/simulations/vip-999/abi/InstitutionalLoanVault.json b/simulations/vip-999/abi/InstitutionalLoanVault.json index 21b72547f..5a0cac9a0 100644 --- a/simulations/vip-999/abi/InstitutionalLoanVault.json +++ b/simulations/vip-999/abi/InstitutionalLoanVault.json @@ -568,11 +568,29 @@ "name": "symbol_", "type": "string", "internalType": "string" + }, + { + "name": "institutionName_", + "type": "string", + "internalType": "string" } ], "outputs": [], "stateMutability": "nonpayable" }, + { + "type": "function", + "name": "institutionName", + "inputs": [], + "outputs": [ + { + "name": "", + "type": "string", + "internalType": "string" + } + ], + "stateMutability": "view" + }, { "type": "function", "name": "institutionalConfig", @@ -1083,6 +1101,19 @@ ], "stateMutability": "view" }, + { + "type": "function", + "name": "setInstitutionName", + "inputs": [ + { + "name": "newName", + "type": "string", + "internalType": "string" + } + ], + "outputs": [], + "stateMutability": "nonpayable" + }, { "type": "function", "name": "setLatePenaltyRate", @@ -1422,6 +1453,25 @@ ], "anonymous": false }, + { + "type": "event", + "name": "InstitutionNameUpdated", + "inputs": [ + { + "name": "oldName", + "type": "string", + "indexed": false, + "internalType": "string" + }, + { + "name": "newName", + "type": "string", + "indexed": false, + "internalType": "string" + } + ], + "anonymous": false + }, { "type": "event", "name": "LatePenaltyRateUpdated", diff --git a/simulations/vip-999/bsctestnet.ts b/simulations/vip-999/bsctestnet.ts index a7c33e0de..79117a401 100644 --- a/simulations/vip-999/bsctestnet.ts +++ b/simulations/vip-999/bsctestnet.ts @@ -4,48 +4,38 @@ import { BigNumber, Contract } from "ethers"; import { parseUnits } from "ethers/lib/utils"; import { ethers } from "hardhat"; import { NETWORK_ADDRESSES } from "src/networkAddresses"; -import { initMainnetUser, setMaxStalePeriodForAllAssets, setMaxStalePeriodInChainlinkOracle } from "src/utils"; -import { forking, testVip } from "src/vip-framework"; +import { initMainnetUser } from "src/utils"; +import { forking, pretendExecutingVip, testVip } from "src/vip-framework"; +import vip665Addendum from "../../vips/vip-665/bsctestnet-addendum"; import vip999, { - BTCB, - BTCB_FEED_MAIN, CHAINLINK_ORACLE, FIXED_RATE_VAULT_CONTROLLER, + INSTITUTION_NAME, INSTITUTION_OPERATOR, SUPPLY_ASSET, + VAULT_SHARE_NAME, + VAULT_SHARE_SYMBOL, VCEBTC, + VCEBTC_DIRECT_PRICE, VCEBTC_INITIAL_SUPPLY, instConfig, + riskConfig, vaultConfig, } from "../../vips/vip-999/bsctestnet"; import ACM_ABI from "./abi/AccessControlManager.json"; import VAULT_ABI from "./abi/InstitutionalLoanVault.json"; import CONTROLLER_ABI from "./abi/InstitutionalVaultController.json"; -import PROXY_ADMIN_ABI from "./abi/ProxyAdmin.json"; import ORACLE_ABI from "./abi/ResilientOracle.json"; import ERC20_ABI from "./abi/VenusERC20.json"; const { bsctestnet } = NETWORK_ADDRESSES; const FORK_BLOCK = 117554000; -const ONE_YEAR = 31536000; - -// A separate, not-yet-executed VIP upgrades the controller/vault implementations to the -// versions supporting the 6-argument createVault (with institution name). Reproduced here. -const PROXY_ADMIN = "0x7877ffd62649b6a1557b55d4c20fcbab17344c91"; -const NEW_CONTROLLER_IMPLEMENTATION = "0xC36dFaCc7a125859C106F29b9F2d874CCF29A55A"; -const NEW_VAULT_IMPLEMENTATION = "0x97421799419Eb782628e73e7220d8E0A207469a3"; const USDT_FAUCET_ABI = ["function allocateTo(address to, uint256 amount) external"]; -const LENDER = "0x2222222222222222222222222222222222222222"; // dummy test lender - const CHAINLINK_ORACLE_GETPRICE_ABI = ["function getPrice(address) external view returns (uint256)"]; -const STUB_ORACLE_BYTECODE = - "0x6080604052348015600e575f80fd5b5061015e8061001c5f395ff3fe608060405234801561000f575f80fd5b5060043610610029575f3560e01c806341976e091461002d575b5f80fd5b610047600480360381019061004291906100cc565b61005d565b604051610054919061010f565b60405180910390f35b5f670de0b6b3a76400009050919050565b5f80fd5b5f73ffffffffffffffffffffffffffffffffffffffff82169050919050565b5f61009b82610072565b9050919050565b6100ab81610091565b81146100b5575f80fd5b50565b5f813590506100c6816100a2565b92915050565b5f602082840312156100e1576100e061006e565b5b5f6100ee848285016100b8565b91505092915050565b5f819050919050565b610109816100f7565b82525050565b5f6020820190506101225f830184610100565b9291505056fea26469706673582212209282f7f2d85233912d0088d6dc45ce2459097d2866597e41f0a286059758c12c64736f6c63430008190033"; -const STUB_ORACLE_ABI = ["function getPrice(address) external pure returns (uint256)"]; - // IVaultTypes.VaultState const VaultState = { WaitingForMargin: 0, @@ -61,7 +51,6 @@ forking(FORK_BLOCK, async () => { let acm: Contract; let controller: Contract; let vceBTC: Contract; - let btcb: Contract; let usdt: Contract; let timelock: any; let vaultsBefore: any; @@ -70,36 +59,14 @@ forking(FORK_BLOCK, async () => { oracle = await ethers.getContractAt(ORACLE_ABI, bsctestnet.RESILIENT_ORACLE); acm = await ethers.getContractAt(ACM_ABI, bsctestnet.ACCESS_CONTROL_MANAGER); controller = await ethers.getContractAt(CONTROLLER_ABI, FIXED_RATE_VAULT_CONTROLLER); - btcb = await ethers.getContractAt(ERC20_ABI, BTCB); usdt = await ethers.getContractAt(ERC20_ABI, SUPPLY_ASSET); timelock = await initMainnetUser(bsctestnet.NORMAL_TIMELOCK, parseUnits("40")); vaultsBefore = await controller.allVaultsLength(); - - // Bump the stale period for the pre-existing assets so their prices remain readable on the fork. - await setMaxStalePeriodForAllAssets(oracle, [btcb, usdt]); - - // --- Reproduce the separate controller/vault-upgrade VIP's end state --- - const proxyAdmin = await ethers.getContractAt(PROXY_ADMIN_ABI, PROXY_ADMIN); - const proxyAdminOwner = await initMainnetUser(await proxyAdmin.owner(), parseUnits("1")); - await proxyAdmin.connect(proxyAdminOwner).upgrade(FIXED_RATE_VAULT_CONTROLLER, NEW_CONTROLLER_IMPLEMENTATION); - await controller.connect(timelock).setVaultImplementation(NEW_VAULT_IMPLEMENTATION); - await acm - .connect(timelock) - .giveCallPermission( - FIXED_RATE_VAULT_CONTROLLER, - "createVault(VaultConfig,InstitutionalConfig,RiskConfig,string,string,string)", - bsctestnet.NORMAL_TIMELOCK, - ); - - // Point the controller at a stub oracle so createVault can price vceBTC during the - // time-advanced execute() (see the STUB_ORACLE_BYTECODE comment). Restored in the callback. - 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(); - await controller.connect(timelock).setOracle(stubOracle.address); - vceBTC = await ethers.getContractAt(ERC20_ABI, VCEBTC); + + // Prerequisite: the VIP-665 addendum re-upgrades the InstitutionalVaultController before this + // VIP deploys a new vault on it. Execute it first, as the Normal Timelock. + await pretendExecutingVip(await vip665Addendum(), bsctestnet.NORMAL_TIMELOCK); }); describe("Pre-VIP behavior", () => { @@ -131,29 +98,14 @@ forking(FORK_BLOCK, async () => { }); }); - testVip("VIP-999 Create Ceffu Custody BTC Fixed Rate Vault (Testnet)", await vip999(), { - callbackAfterExecution: async () => { - // vceBTC's oracle config (Chainlink main only) is only created by the VIP itself, so its - // stale period can only be bumped now — needed so the price survives the ~37-day time - // travel in the vault lifecycle test below. - await setMaxStalePeriodInChainlinkOracle( - CHAINLINK_ORACLE, - VCEBTC, - BTCB_FEED_MAIN, - bsctestnet.NORMAL_TIMELOCK, - ONE_YEAR, - ); - // Restore the real ResilientOracle now that vceBTC is priceable with a bumped stale period. - await controller.connect(timelock).setOracle(bsctestnet.RESILIENT_ORACLE); - }, - }); + testVip("VIP-999 Create Ceffu Custody BTC Fixed Rate Vault (Testnet)", await vip999()); describe("Post-VIP behavior", () => { - it("vceBTC is priced from the Chainlink feed cloned from BTCB's config", async () => { + it("vceBTC is priced at the fixed direct price set on the Chainlink sub-oracle", async () => { const chainlink = await ethers.getContractAt(CHAINLINK_ORACLE_GETPRICE_ABI, CHAINLINK_ORACLE); const vceBtcPrice = await oracle.getPrice(VCEBTC); - expect(vceBtcPrice).to.be.gt(0); - // ResilientOracle proxies to the Chainlink main sub-oracle, which reads the cloned feed. + expect(vceBtcPrice).to.equal(VCEBTC_DIRECT_PRICE); + // ResilientOracle proxies to the Chainlink main sub-oracle, which returns the direct price. expect(vceBtcPrice).to.equal(await chainlink.getPrice(VCEBTC)); }); @@ -178,38 +130,64 @@ forking(FORK_BLOCK, async () => { ); }); + it("the Normal Timelock can mint and burn vceBTC", async () => { + const recipient = "0x000000000000000000000000000000000000dEaD"; + const amount = parseUnits("1", 18); + + const supplyBefore = await vceBTC.totalSupply(); + const balanceBefore = await vceBTC.balanceOf(recipient); + + await vceBTC.connect(timelock).mint(recipient, amount); + expect(await vceBTC.balanceOf(recipient)).to.equal(balanceBefore.add(amount)); + expect(await vceBTC.totalSupply()).to.equal(supplyBefore.add(amount)); + + await vceBTC.connect(timelock).burn(recipient, amount); + expect(await vceBTC.balanceOf(recipient)).to.equal(balanceBefore); + expect(await vceBTC.totalSupply()).to.equal(supplyBefore); + }); + it("initial vceBTC collateral was minted to the Venus Treasury", async () => { expect(await vceBTC.totalSupply()).to.equal(VCEBTC_INITIAL_SUPPLY); expect(await vceBTC.balanceOf(bsctestnet.VTREASURY)).to.equal(VCEBTC_INITIAL_SUPPLY); }); - it("a Fixed Rate Vault backed by vceBTC was created", async () => { + it("a Fixed Rate Vault backed by vceBTC was created with the configured parameters and names", async () => { expect(await controller.allVaultsLength()).to.equal(vaultsBefore.add(1)); const vaultAddress = await controller.allVaults(vaultsBefore); expect(await controller.isRegistered(vaultAddress)).to.equal(true); const vault = await ethers.getContractAt(VAULT_ABI, vaultAddress); - expect((await vault.config()).supplyAsset).to.equal(SUPPLY_ASSET); - expect((await vault.institutionalConfig()).collateralAsset).to.equal(VCEBTC); - expect((await vault.institutionalConfig()).institutionOperator).to.equal(INSTITUTION_OPERATOR); - }); - }); - describe("Oracle configuration for BTCB and vceBTC", () => { - it("BTCB price is available from ResilientOracle", async () => { - const btcbPrice = await oracle.getPrice(BTCB); - expect(btcbPrice).to.be.gt(0); - }); - - it("vceBTC price is available from ResilientOracle", async () => { - const vceBtcPrice = await oracle.getPrice(VCEBTC); - expect(vceBtcPrice).to.be.gt(0); - }); - - it("vceBTC price equals the value derived from its cloned Chainlink feed", async () => { - const chainlink = await ethers.getContractAt(CHAINLINK_ORACLE_GETPRICE_ABI, CHAINLINK_ORACLE); - const vceBtcPrice = await oracle.getPrice(VCEBTC); - expect(vceBtcPrice).to.equal(await chainlink.getPrice(VCEBTC)); + // VaultConfig + const config = await vault.config(); + expect(config.supplyAsset).to.equal(vaultConfig[0]); + expect(config.fixedAPY).to.equal(vaultConfig[1]); + expect(config.reserveFactor).to.equal(vaultConfig[2]); + expect(config.minBorrowCap).to.equal(vaultConfig[3]); + expect(config.maxBorrowCap).to.equal(vaultConfig[4]); + expect(config.minSupplierDeposit).to.equal(vaultConfig[5]); + expect(config.openDuration).to.equal(vaultConfig[6]); + expect(config.lockDuration).to.equal(vaultConfig[7]); + expect(config.settlementWindow).to.equal(vaultConfig[8]); + + // InstitutionalConfig (positionTokenId is assigned by the controller) + const inst = await vault.institutionalConfig(); + expect(inst.collateralAsset).to.equal(instConfig[0]); + expect(inst.idealCollateralAmount).to.equal(instConfig[1]); + expect(inst.marginRate).to.equal(instConfig[2]); + expect(inst.institutionOperator).to.equal(instConfig[3]); + expect(inst.positionTokenId).to.be.gt(0); + + // RiskConfig + const risk = await vault.riskConfig(); + expect(risk.liquidationThreshold).to.equal(riskConfig[0]); + expect(risk.liquidationIncentive).to.equal(riskConfig[1]); + expect(risk.latePenaltyRate).to.equal(riskConfig[2]); + + // Share token names + institution name + expect(await vault.name()).to.equal(VAULT_SHARE_NAME); + expect(await vault.symbol()).to.equal(VAULT_SHARE_SYMBOL); + expect(await vault.institutionName()).to.equal(INSTITUTION_NAME); }); }); @@ -217,6 +195,7 @@ forking(FORK_BLOCK, async () => { let vault: Contract; let institution: any; let lender: any; + let LENDER: string; const idealCollateralAmount = BigNumber.from(instConfig[1]); const marginRate = BigNumber.from(instConfig[2]); @@ -231,14 +210,17 @@ forking(FORK_BLOCK, async () => { vault = await ethers.getContractAt(VAULT_ABI, vaultAddress); institution = await initMainnetUser(INSTITUTION_OPERATOR, parseUnits("1")); - lender = await initMainnetUser(LENDER, parseUnits("1")); + + // Lender is a pre-funded Hardhat signer (account #1), so no impersonation is needed. + const [deployer, lenderSigner] = await ethers.getSigners(); + lender = lenderSigner; + LENDER = await lenderSigner.getAddress(); // Fund the institution with vceBTC collateral and both parties with the supply asset (USDT). await vceBTC.connect(timelock).mint(INSTITUTION_OPERATOR, idealCollateralAmount); - const [deployer] = await ethers.getSigners(); const usdtFaucet = await ethers.getContractAt(USDT_FAUCET_ABI, SUPPLY_ASSET); await usdtFaucet.connect(deployer).allocateTo(LENDER, lenderDepositAmount); - await usdtFaucet.connect(deployer).allocateTo(INSTITUTION_OPERATOR, lenderDepositAmount.div(5)); // repay principal + interest + await usdtFaucet.connect(deployer).allocateTo(INSTITUTION_OPERATOR, lenderDepositAmount.div(5)); // buffer to cover interest on repay (principal comes from claimed funds) }); it("institution deposits margin collateral (WaitingForMargin -> MarginDeposited)", async () => { diff --git a/vips/vip-999/bsctestnet.ts b/vips/vip-999/bsctestnet.ts index bab1da78e..65c23938a 100644 --- a/vips/vip-999/bsctestnet.ts +++ b/vips/vip-999/bsctestnet.ts @@ -1,3 +1,4 @@ +import { ethers } from "ethers"; import { parseUnits } from "ethers/lib/utils"; import { NETWORK_ADDRESSES } from "src/networkAddresses"; import { ProposalType } from "src/types"; @@ -5,31 +6,26 @@ import { makeProposal } from "src/utils"; const { bsctestnet } = NETWORK_ADDRESSES; -// Deployed vceBTC (testnet) export const VCEBTC = "0x3C5Fc884BF6d1Ec8957A75EF6436b3B5750A57da"; - -export const FIXED_RATE_VAULT_CONTROLLER = "0x36bA78812Ffff64B9ec060a1F07FcFa2012f6F89"; +export const FIXED_RATE_VAULT_CONTROLLER = "0xf77dED2A00F94e33C392126238360D4642c16Ba2"; export const BTCB = "0xA808e341e8e723DC6BA0Bb5204Bafc2330d7B8e4"; - -// BTCB's oracle config on testnet is Chainlink main only export const CHAINLINK_ORACLE = bsctestnet.CHAINLINK_ORACLE; -export const ADDRESS_ZERO = "0x0000000000000000000000000000000000000000"; -export const BTCB_FEED_MAIN = "0x5741306c21795FdCBb9b265Ea0255F499DFe515C"; -export const BTCB_STALE_MAIN = 86400; -// Supply asset for the loan market — testnet USDT (6 decimals) +// Fixed direct price for vceBTC set on the Chainlink sub-oracle (18-decimal scale). +export const VCEBTC_DIRECT_PRICE = "2100000000000000000000000"; + +// Loan market supply asset — testnet USDT (6 decimals) export const SUPPLY_ASSET = "0xA11c8D9DC9b66E209Ef60F0C8D969D3CD988782c"; -// Test institution operator (placeholder EOA on testnet) -export const INSTITUTION_OPERATOR = "0x1111111111111111111111111111111111111111"; +// Test institution operator +export const INSTITUTION_OPERATOR = "0x4cD6300F5cb8D6BbA5E646131c3522664C10dF11"; // Initial vceBTC collateral supply export const VCEBTC_INITIAL_SUPPLY = parseUnits("2000", 18); -// Fixed Rate Vault configuration. // VaultConfig: [supplyAsset, fixedAPY(bps), reserveFactor(1e18), minBorrowCap, -// maxBorrowCap, minSupplierDeposit, openDuration, lockDuration, settlementWindow] -// NB: borrow caps / minSupplierDeposit are denominated in the supply asset (USDT, 6 decimals). +// maxBorrowCap, minSupplierDeposit, openDuration, lockDuration, settlementWindow] +// Caps / minSupplierDeposit are in the supply asset (USDT, 6 decimals). export const vaultConfig = [ SUPPLY_ASSET, 800, // fixedAPY = 8% @@ -39,7 +35,7 @@ export const vaultConfig = [ 0, // minSupplierDeposit 7 * 24 * 60 * 60, // openDuration = 7 days 30 * 24 * 60 * 60, // lockDuration = 30 days - 30 * 24 * 60 * 60, // settlementWindow = 30 days + 10 * 24 * 60 * 60, // settlementWindow = 10 days ]; // InstitutionalConfig: [collateralAsset, idealCollateralAmount, marginRate(1e18), @@ -69,7 +65,7 @@ This proposal onboards a new custody-mirror collateral token, **vceBTC ("Ceffu C #### Actions -1. **Oracle** — price vceBTC identically to BTCB by cloning BTCB's oracle configuration: the Chainlink main sub-oracle feed and the ResilientOracle token config (pivot / fallback disabled, no bound validation — matching BTCB on testnet). +1. **Oracle** — price vceBTC identically to BTCB by mirroring BTCB's oracle setup: set a fixed direct price on the Chainlink main sub-oracle (equal to BTCB's price) and point the ResilientOracle token config at that sub-oracle (main only; pivot / fallback disabled, no bound validation — matching BTCB on testnet). 2. **Ownership** — accept ownership of vceBTC (already transferred to the Normal Timelock by the deployer). 3. **Access control** — grant \`mint(address,uint256)\` and \`burn(address,uint256)\` on vceBTC to the Normal Timelock and the Guardian. (The \`createVault(...)\` permission on the InstitutionalVaultController is granted by the separate controller/vault-upgrade VIP.) 4. **Initial supply** — mint the initial vceBTC collateral to the Venus Treasury. @@ -82,17 +78,24 @@ This proposal onboards a new custody-mirror collateral token, **vceBTC ("Ceffu C return makeProposal( [ // ────────────────────────────────────────────────────────────────────── - // 1. Oracle configuration identically to BTCB (Chainlink main only) + // 1. Oracle configuration — fixed direct price on the Chainlink sub-oracle // ────────────────────────────────────────────────────────────────────── { target: CHAINLINK_ORACLE, - signature: "setTokenConfig((address,address,uint256))", - params: [[VCEBTC, BTCB_FEED_MAIN, BTCB_STALE_MAIN]], + signature: "setDirectPrice(address,uint256)", + params: [VCEBTC, VCEBTC_DIRECT_PRICE], }, { target: bsctestnet.RESILIENT_ORACLE, signature: "setTokenConfig((address,address[3],bool[3],bool))", - params: [[VCEBTC, [CHAINLINK_ORACLE, ADDRESS_ZERO, ADDRESS_ZERO], [true, false, false], false]], + params: [ + [ + VCEBTC, + [CHAINLINK_ORACLE, ethers.constants.AddressZero, ethers.constants.AddressZero], + [true, false, false], + false, + ], + ], }, // ────────────────────────────────────────────────────────────────────── From eccaca7e6617b440b09cda37b6b9b6cf903c65a4 Mon Sep 17 00:00:00 2001 From: GitGuru7 Date: Mon, 6 Jul 2026 21:05:32 +0530 Subject: [PATCH 08/15] refactor: drop VIP-665 addendum prereq, fork at upgraded controller block --- simulations/vip-999/bsctestnet.ts | 9 ++------- 1 file changed, 2 insertions(+), 7 deletions(-) diff --git a/simulations/vip-999/bsctestnet.ts b/simulations/vip-999/bsctestnet.ts index 79117a401..ea090de33 100644 --- a/simulations/vip-999/bsctestnet.ts +++ b/simulations/vip-999/bsctestnet.ts @@ -5,9 +5,8 @@ import { parseUnits } from "ethers/lib/utils"; import { ethers } from "hardhat"; import { NETWORK_ADDRESSES } from "src/networkAddresses"; import { initMainnetUser } from "src/utils"; -import { forking, pretendExecutingVip, testVip } from "src/vip-framework"; +import { forking, testVip } from "src/vip-framework"; -import vip665Addendum from "../../vips/vip-665/bsctestnet-addendum"; import vip999, { CHAINLINK_ORACLE, FIXED_RATE_VAULT_CONTROLLER, @@ -31,7 +30,7 @@ import ERC20_ABI from "./abi/VenusERC20.json"; const { bsctestnet } = NETWORK_ADDRESSES; -const FORK_BLOCK = 117554000; +const FORK_BLOCK = 117573000; const USDT_FAUCET_ABI = ["function allocateTo(address to, uint256 amount) external"]; const CHAINLINK_ORACLE_GETPRICE_ABI = ["function getPrice(address) external view returns (uint256)"]; @@ -63,10 +62,6 @@ forking(FORK_BLOCK, async () => { timelock = await initMainnetUser(bsctestnet.NORMAL_TIMELOCK, parseUnits("40")); vaultsBefore = await controller.allVaultsLength(); vceBTC = await ethers.getContractAt(ERC20_ABI, VCEBTC); - - // Prerequisite: the VIP-665 addendum re-upgrades the InstitutionalVaultController before this - // VIP deploys a new vault on it. Execute it first, as the Normal Timelock. - await pretendExecutingVip(await vip665Addendum(), bsctestnet.NORMAL_TIMELOCK); }); describe("Pre-VIP behavior", () => { From dc743023c4c2c71a8e68afea3296cc59fd4178f4 Mon Sep 17 00:00:00 2001 From: GitGuru7 Date: Tue, 7 Jul 2026 12:02:37 +0530 Subject: [PATCH 09/15] refactor: reproduce VIP-640 prereq via pretendExecutingVip in mainnet sim --- simulations/vip-999/bscmainnet.ts | 28 +++++----------------------- 1 file changed, 5 insertions(+), 23 deletions(-) diff --git a/simulations/vip-999/bscmainnet.ts b/simulations/vip-999/bscmainnet.ts index 9a3bef6cb..7bd672496 100644 --- a/simulations/vip-999/bscmainnet.ts +++ b/simulations/vip-999/bscmainnet.ts @@ -5,8 +5,9 @@ import { parseUnits } from "ethers/lib/utils"; import { ethers } from "hardhat"; import { NETWORK_ADDRESSES } from "src/networkAddresses"; import { initMainnetUser, setMaxStalePeriodForAllAssets, setMaxStalePeriodInChainlinkOracle } from "src/utils"; -import { forking, testVip } from "src/vip-framework"; +import { forking, pretendExecutingVip, testVip } from "src/vip-framework"; +import vip640 from "../../vips/vip-640/bscmainnet"; import vip999, { ATLAS_ORACLE, BOUND_VALIDATOR, @@ -28,7 +29,6 @@ import vip999, { import ACM_ABI from "./abi/AccessControlManager.json"; import VAULT_ABI from "./abi/InstitutionalLoanVault.json"; import CONTROLLER_ABI from "./abi/InstitutionalVaultController.json"; -import PROXY_ADMIN_ABI from "./abi/ProxyAdmin.json"; import ORACLE_ABI from "./abi/ResilientOracle.json"; import ERC20_ABI from "./abi/VenusERC20.json"; @@ -39,12 +39,6 @@ const { bscmainnet } = NETWORK_ADDRESSES; const FORK_BLOCK = 108402150; const ONE_YEAR = 31536000; -// A separate, not-yet-executed VIP upgrades the controller/vault implementations and -// grants createVault. -const PROXY_ADMIN = "0x6beb6D2695B67FEb73ad4f172E8E2975497187e4"; -const NEW_CONTROLLER_IMPLEMENTATION = "0xBD9df626c642591cef3612586CC5e45E9767360f"; -const NEW_VAULT_IMPLEMENTATION = "0xC25b2B657D24380eDd1a1Cff5296385541e85204"; - const USDT_WHALE = "0xF977814e90dA44bFA03b6295A0616a897441aceC"; // Binance Hot Wallet const LENDER = "0x2222222222222222222222222222222222222222"; // dummy test lender @@ -92,21 +86,9 @@ forking(FORK_BLOCK, async () => { await redstoneOracleForExisting.connect(timelock).setDirectPrice(BTCB, btcbPriceAtFork); await redstoneOracleForExisting.connect(timelock).setDirectPrice(SUPPLY_ASSET, usdtPriceAtFork); - // --- Reproduce the separate controller/vault-upgrade VIP's end state --- - const proxyAdmin = await ethers.getContractAt(PROXY_ADMIN_ABI, PROXY_ADMIN); - const proxyAdminOwner = await initMainnetUser(await proxyAdmin.owner(), parseUnits("1")); - await proxyAdmin.connect(proxyAdminOwner).upgrade(FIXED_RATE_VAULT_CONTROLLER, NEW_CONTROLLER_IMPLEMENTATION); - await controller.connect(timelock).setVaultImplementation(NEW_VAULT_IMPLEMENTATION); - await acm - .connect(timelock) - .giveCallPermission( - FIXED_RATE_VAULT_CONTROLLER, - "createVault(VaultConfig,InstitutionalConfig,RiskConfig,string,string,string)", - bscmainnet.NORMAL_TIMELOCK, - ); - await acm - .connect(timelock) - .giveCallPermission(FIXED_RATE_VAULT_CONTROLLER, "openVault(address)", bscmainnet.NORMAL_TIMELOCK); + // Reproduce the prerequisite controller/vault-upgrade VIP (VIP-640), which upgrades the + // controller/vault implementations and re-grants the 6-arg createVault permission. + await pretendExecutingVip(await vip640(), bscmainnet.NORMAL_TIMELOCK); const [deployer] = await ethers.getSigners(); const stubFactory = new ethers.ContractFactory(STUB_ORACLE_ABI, STUB_ORACLE_BYTECODE, deployer); From ee359b70fe3816abf5262835f4b37774c0e59753 Mon Sep 17 00:00:00 2001 From: GitGuru7 Date: Wed, 8 Jul 2026 15:47:00 +0530 Subject: [PATCH 10/15] refactor: VIP-999 mint recipients and BoundValidator ABI file --- simulations/vip-999/abi/BoundValidator.json | 333 ++++++++++++++++++++ simulations/vip-999/abi/ProxyAdmin.json | 151 --------- simulations/vip-999/bscmainnet.ts | 17 +- simulations/vip-999/bsctestnet.ts | 4 +- vips/vip-999/bscmainnet.ts | 7 +- vips/vip-999/bsctestnet.ts | 4 +- 6 files changed, 353 insertions(+), 163 deletions(-) create mode 100644 simulations/vip-999/abi/BoundValidator.json delete mode 100644 simulations/vip-999/abi/ProxyAdmin.json diff --git a/simulations/vip-999/abi/BoundValidator.json b/simulations/vip-999/abi/BoundValidator.json new file mode 100644 index 000000000..6fa133e31 --- /dev/null +++ b/simulations/vip-999/abi/BoundValidator.json @@ -0,0 +1,333 @@ +[ + { + "inputs": [], + "stateMutability": "nonpayable", + "type": "constructor" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "sender", + "type": "address" + }, + { + "internalType": "address", + "name": "calledContract", + "type": "address" + }, + { + "internalType": "string", + "name": "methodSignature", + "type": "string" + } + ], + "name": "Unauthorized", + "type": "error" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "uint8", + "name": "version", + "type": "uint8" + } + ], + "name": "Initialized", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "address", + "name": "oldAccessControlManager", + "type": "address" + }, + { + "indexed": false, + "internalType": "address", + "name": "newAccessControlManager", + "type": "address" + } + ], + "name": "NewAccessControlManager", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "previousOwner", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "newOwner", + "type": "address" + } + ], + "name": "OwnershipTransferStarted", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "previousOwner", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "newOwner", + "type": "address" + } + ], + "name": "OwnershipTransferred", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "asset", + "type": "address" + }, + { + "indexed": true, + "internalType": "uint256", + "name": "upperBound", + "type": "uint256" + }, + { + "indexed": true, + "internalType": "uint256", + "name": "lowerBound", + "type": "uint256" + } + ], + "name": "ValidateConfigAdded", + "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": [ + { + "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": [ + { + "components": [ + { + "internalType": "address", + "name": "asset", + "type": "address" + }, + { + "internalType": "uint256", + "name": "upperBoundRatio", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "lowerBoundRatio", + "type": "uint256" + } + ], + "internalType": "struct BoundValidator.ValidateConfig", + "name": "config", + "type": "tuple" + } + ], + "name": "setValidateConfig", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "components": [ + { + "internalType": "address", + "name": "asset", + "type": "address" + }, + { + "internalType": "uint256", + "name": "upperBoundRatio", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "lowerBoundRatio", + "type": "uint256" + } + ], + "internalType": "struct BoundValidator.ValidateConfig[]", + "name": "configs", + "type": "tuple[]" + } + ], + "name": "setValidateConfigs", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "newOwner", + "type": "address" + } + ], + "name": "transferOwnership", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "name": "validateConfigs", + "outputs": [ + { + "internalType": "address", + "name": "asset", + "type": "address" + }, + { + "internalType": "uint256", + "name": "upperBoundRatio", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "lowerBoundRatio", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "asset", + "type": "address" + }, + { + "internalType": "uint256", + "name": "reportedPrice", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "anchorPrice", + "type": "uint256" + } + ], + "name": "validatePriceWithAnchorPrice", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "view", + "type": "function" + } +] diff --git a/simulations/vip-999/abi/ProxyAdmin.json b/simulations/vip-999/abi/ProxyAdmin.json deleted file mode 100644 index b4c51d8df..000000000 --- a/simulations/vip-999/abi/ProxyAdmin.json +++ /dev/null @@ -1,151 +0,0 @@ -[ - { - "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-999/bscmainnet.ts b/simulations/vip-999/bscmainnet.ts index 7bd672496..7ff6e5e47 100644 --- a/simulations/vip-999/bscmainnet.ts +++ b/simulations/vip-999/bscmainnet.ts @@ -15,9 +15,11 @@ import vip999, { BTCB_FEED_FALLBACK, BTCB_FEED_MAIN, BTCB_FEED_PIVOT, + BTCB_LOWER_BOUND, BTCB_UPPER_BOUND, CHAINLINK_ORACLE, FIXED_RATE_VAULT_CONTROLLER, + INITIAL_SUPPLY_RECIPIENT, INSTITUTION_OPERATOR, REDSTONE_ORACLE, SUPPLY_ASSET, @@ -27,13 +29,12 @@ import vip999, { vaultConfig, } from "../../vips/vip-999/bscmainnet"; import ACM_ABI from "./abi/AccessControlManager.json"; +import BOUND_VALIDATOR_ABI from "./abi/BoundValidator.json"; import VAULT_ABI from "./abi/InstitutionalLoanVault.json"; import CONTROLLER_ABI from "./abi/InstitutionalVaultController.json"; import ORACLE_ABI from "./abi/ResilientOracle.json"; import ERC20_ABI from "./abi/VenusERC20.json"; -const BOUND_VALIDATOR_ABI = ["function validateConfigs(address) external view returns (uint256, uint256)"]; - const { bscmainnet } = NETWORK_ADDRESSES; const FORK_BLOCK = 108402150; @@ -176,9 +177,9 @@ forking(FORK_BLOCK, async () => { ).to.equal(true); }); - it("initial vceBTC collateral was minted to the Venus Treasury", async () => { + it("initial vceBTC collateral was minted to the Ceffu multisig", async () => { expect(await vceBTC.totalSupply()).to.equal(VCEBTC_INITIAL_SUPPLY); - expect(await vceBTC.balanceOf(bscmainnet.VTREASURY)).to.equal(VCEBTC_INITIAL_SUPPLY); + expect(await vceBTC.balanceOf(INITIAL_SUPPLY_RECIPIENT)).to.equal(VCEBTC_INITIAL_SUPPLY); }); it("a Fixed Rate Vault backed by vceBTC was created", async () => { @@ -213,13 +214,17 @@ forking(FORK_BLOCK, async () => { it("BTCB bounds are configured correctly in BoundValidator", async () => { const boundValidator = await ethers.getContractAt(BOUND_VALIDATOR_ABI, BOUND_VALIDATOR); const btcbBounds = await boundValidator.validateConfigs(BTCB); - expect(btcbBounds[1]).to.equal(BTCB_UPPER_BOUND); + expect(btcbBounds.asset).to.equal(BTCB); + expect(btcbBounds.upperBoundRatio).to.equal(BTCB_UPPER_BOUND); + expect(btcbBounds.lowerBoundRatio).to.equal(BTCB_LOWER_BOUND); }); it("vceBTC bounds are configured correctly in BoundValidator (cloned from BTCB)", async () => { const boundValidator = await ethers.getContractAt(BOUND_VALIDATOR_ABI, BOUND_VALIDATOR); const vceBtcBounds = await boundValidator.validateConfigs(VCEBTC); - expect(vceBtcBounds[1]).to.equal(BTCB_UPPER_BOUND); + expect(vceBtcBounds.asset).to.equal(VCEBTC); + expect(vceBtcBounds.upperBoundRatio).to.equal(BTCB_UPPER_BOUND); + expect(vceBtcBounds.lowerBoundRatio).to.equal(BTCB_LOWER_BOUND); }); }); diff --git a/simulations/vip-999/bsctestnet.ts b/simulations/vip-999/bsctestnet.ts index ea090de33..d1a94e922 100644 --- a/simulations/vip-999/bsctestnet.ts +++ b/simulations/vip-999/bsctestnet.ts @@ -141,9 +141,9 @@ forking(FORK_BLOCK, async () => { expect(await vceBTC.totalSupply()).to.equal(supplyBefore); }); - it("initial vceBTC collateral was minted to the Venus Treasury", async () => { + it("initial vceBTC collateral was minted to the Guardian", async () => { expect(await vceBTC.totalSupply()).to.equal(VCEBTC_INITIAL_SUPPLY); - expect(await vceBTC.balanceOf(bsctestnet.VTREASURY)).to.equal(VCEBTC_INITIAL_SUPPLY); + expect(await vceBTC.balanceOf(bsctestnet.GUARDIAN)).to.equal(VCEBTC_INITIAL_SUPPLY); }); it("a Fixed Rate Vault backed by vceBTC was created with the configured parameters and names", async () => { diff --git a/vips/vip-999/bscmainnet.ts b/vips/vip-999/bscmainnet.ts index 0e73cac12..843086d32 100644 --- a/vips/vip-999/bscmainnet.ts +++ b/vips/vip-999/bscmainnet.ts @@ -35,6 +35,9 @@ export const INSTITUTION_OPERATOR = "0x1111111111111111111111111111111111111111" // Initial vceBTC collateral supply export const VCEBTC_INITIAL_SUPPLY = parseUnits("2000", 18); +// Recipient of the initial vceBTC collateral mint (multisig) +export const INITIAL_SUPPLY_RECIPIENT = "0x5D1507d5Cfb3d031C3209e9FB8e2644e4094Ea01"; + // Fixed Rate Vault configuration. TODO: finalize values. // VaultConfig: [supplyAsset, fixedAPY(bps), reserveFactor(1e18), minBorrowCap, // maxBorrowCap, minSupplierDeposit, openDuration, lockDuration, settlementWindow] @@ -81,7 +84,7 @@ This proposal onboards a new custody-mirror collateral token, **vceBTC ("Ceffu C 1. **Oracle** — price vceBTC identically to BTCB by cloning BTCB's full oracle configuration: the main / pivot / fallback sub-oracle feeds, the BoundValidator bounds, and the ResilientOracle token config. 2. **Ownership** — accept ownership of vceBTC (already transferred to the Normal Timelock by the deployer). 3. **Access control** — grant \`mint(address,uint256)\` and \`burn(address,uint256)\` on vceBTC to the Normal Timelock and the Guardian. (The \`createVault(...)\` permission on the InstitutionalVaultController is granted by the separate controller/vault-upgrade VIP.) -4. **Initial supply** — mint the initial vceBTC collateral to the Venus Treasury. +4. **Initial supply** — mint the initial vceBTC collateral to the Ceffu multisig. 5. **Vault creation** — create the Fixed Rate Vault with vceBTC as collateral.`, forDescription: "I agree that Venus Protocol should proceed with this proposal", againstDescription: "I do not think that Venus Protocol should proceed with this proposal", @@ -158,7 +161,7 @@ This proposal onboards a new custody-mirror collateral token, **vceBTC ("Ceffu C { target: VCEBTC, signature: "mint(address,uint256)", - params: [bscmainnet.VTREASURY, VCEBTC_INITIAL_SUPPLY], + params: [INITIAL_SUPPLY_RECIPIENT, VCEBTC_INITIAL_SUPPLY], }, // ────────────────────────────────────────────────────────────────────── diff --git a/vips/vip-999/bsctestnet.ts b/vips/vip-999/bsctestnet.ts index 65c23938a..4c81d47f9 100644 --- a/vips/vip-999/bsctestnet.ts +++ b/vips/vip-999/bsctestnet.ts @@ -68,7 +68,7 @@ This proposal onboards a new custody-mirror collateral token, **vceBTC ("Ceffu C 1. **Oracle** — price vceBTC identically to BTCB by mirroring BTCB's oracle setup: set a fixed direct price on the Chainlink main sub-oracle (equal to BTCB's price) and point the ResilientOracle token config at that sub-oracle (main only; pivot / fallback disabled, no bound validation — matching BTCB on testnet). 2. **Ownership** — accept ownership of vceBTC (already transferred to the Normal Timelock by the deployer). 3. **Access control** — grant \`mint(address,uint256)\` and \`burn(address,uint256)\` on vceBTC to the Normal Timelock and the Guardian. (The \`createVault(...)\` permission on the InstitutionalVaultController is granted by the separate controller/vault-upgrade VIP.) -4. **Initial supply** — mint the initial vceBTC collateral to the Venus Treasury. +4. **Initial supply** — mint the initial vceBTC collateral to the Guardian. 5. **Vault creation** — create the Fixed Rate Vault with vceBTC as collateral.`, forDescription: "I agree that Venus Protocol should proceed with this proposal", againstDescription: "I do not think that Venus Protocol should proceed with this proposal", @@ -137,7 +137,7 @@ This proposal onboards a new custody-mirror collateral token, **vceBTC ("Ceffu C { target: VCEBTC, signature: "mint(address,uint256)", - params: [bsctestnet.VTREASURY, VCEBTC_INITIAL_SUPPLY], + params: [bsctestnet.GUARDIAN, VCEBTC_INITIAL_SUPPLY], }, // ────────────────────────────────────────────────────────────────────── From 5f1ba63de288b682e22520712d31d926a29a569b Mon Sep 17 00:00:00 2001 From: GitGuru7 Date: Wed, 8 Jul 2026 16:10:07 +0530 Subject: [PATCH 11/15] test: assert full vault config and add mint/burn behavioral check --- simulations/vip-999/bscmainnet.ts | 56 ++++++++++++++++++++++++++++--- 1 file changed, 52 insertions(+), 4 deletions(-) diff --git a/simulations/vip-999/bscmainnet.ts b/simulations/vip-999/bscmainnet.ts index 7ff6e5e47..c25d4017e 100644 --- a/simulations/vip-999/bscmainnet.ts +++ b/simulations/vip-999/bscmainnet.ts @@ -20,12 +20,16 @@ import vip999, { CHAINLINK_ORACLE, FIXED_RATE_VAULT_CONTROLLER, INITIAL_SUPPLY_RECIPIENT, + INSTITUTION_NAME, INSTITUTION_OPERATOR, REDSTONE_ORACLE, SUPPLY_ASSET, + VAULT_SHARE_NAME, + VAULT_SHARE_SYMBOL, VCEBTC, VCEBTC_INITIAL_SUPPLY, instConfig, + riskConfig, vaultConfig, } from "../../vips/vip-999/bscmainnet"; import ACM_ABI from "./abi/AccessControlManager.json"; @@ -177,20 +181,64 @@ forking(FORK_BLOCK, async () => { ).to.equal(true); }); + it("the Normal Timelock can mint and burn vceBTC", async () => { + const recipient = "0x000000000000000000000000000000000000dEaD"; + const amount = parseUnits("1", 18); + + const supplyBefore = await vceBTC.totalSupply(); + const balanceBefore = await vceBTC.balanceOf(recipient); + + await vceBTC.connect(timelock).mint(recipient, amount); + expect(await vceBTC.balanceOf(recipient)).to.equal(balanceBefore.add(amount)); + expect(await vceBTC.totalSupply()).to.equal(supplyBefore.add(amount)); + + await vceBTC.connect(timelock).burn(recipient, amount); + expect(await vceBTC.balanceOf(recipient)).to.equal(balanceBefore); + expect(await vceBTC.totalSupply()).to.equal(supplyBefore); + }); + it("initial vceBTC collateral was minted to the Ceffu multisig", async () => { expect(await vceBTC.totalSupply()).to.equal(VCEBTC_INITIAL_SUPPLY); expect(await vceBTC.balanceOf(INITIAL_SUPPLY_RECIPIENT)).to.equal(VCEBTC_INITIAL_SUPPLY); }); - it("a Fixed Rate Vault backed by vceBTC was created", async () => { + it("a Fixed Rate Vault backed by vceBTC was created with the configured parameters and names", async () => { expect(await controller.allVaultsLength()).to.equal(vaultsBefore.add(1)); const vaultAddress = await controller.allVaults(vaultsBefore); expect(await controller.isRegistered(vaultAddress)).to.equal(true); const vault = await ethers.getContractAt(VAULT_ABI, vaultAddress); - expect((await vault.config()).supplyAsset).to.equal(SUPPLY_ASSET); - expect((await vault.institutionalConfig()).collateralAsset).to.equal(VCEBTC); - expect((await vault.institutionalConfig()).institutionOperator).to.equal(INSTITUTION_OPERATOR); + + // VaultConfig + const config = await vault.config(); + expect(config.supplyAsset).to.equal(vaultConfig[0]); + expect(config.fixedAPY).to.equal(vaultConfig[1]); + expect(config.reserveFactor).to.equal(vaultConfig[2]); + expect(config.minBorrowCap).to.equal(vaultConfig[3]); + expect(config.maxBorrowCap).to.equal(vaultConfig[4]); + expect(config.minSupplierDeposit).to.equal(vaultConfig[5]); + expect(config.openDuration).to.equal(vaultConfig[6]); + expect(config.lockDuration).to.equal(vaultConfig[7]); + expect(config.settlementWindow).to.equal(vaultConfig[8]); + + // InstitutionalConfig (positionTokenId is assigned by the controller) + const inst = await vault.institutionalConfig(); + expect(inst.collateralAsset).to.equal(instConfig[0]); + expect(inst.idealCollateralAmount).to.equal(instConfig[1]); + expect(inst.marginRate).to.equal(instConfig[2]); + expect(inst.institutionOperator).to.equal(instConfig[3]); + expect(inst.positionTokenId).to.be.gt(0); + + // RiskConfig + const risk = await vault.riskConfig(); + expect(risk.liquidationThreshold).to.equal(riskConfig[0]); + expect(risk.liquidationIncentive).to.equal(riskConfig[1]); + expect(risk.latePenaltyRate).to.equal(riskConfig[2]); + + // Share token names + institution name + expect(await vault.name()).to.equal(VAULT_SHARE_NAME); + expect(await vault.symbol()).to.equal(VAULT_SHARE_SYMBOL); + expect(await vault.institutionName()).to.equal(INSTITUTION_NAME); }); }); From cc7f9c2078eb0245677745ec84b7e54c73dcd068 Mon Sep 17 00:00:00 2001 From: GitGuru7 Date: Thu, 9 Jul 2026 12:50:15 +0530 Subject: [PATCH 12/15] feat: update Ceffu fixed rate vault params --- simulations/vip-999/bscmainnet.ts | 3 ++- simulations/vip-999/bsctestnet.ts | 3 ++- vips/vip-999/bscmainnet.ts | 22 +++++++++++----------- vips/vip-999/bsctestnet.ts | 18 +++++++++--------- 4 files changed, 24 insertions(+), 22 deletions(-) diff --git a/simulations/vip-999/bscmainnet.ts b/simulations/vip-999/bscmainnet.ts index c25d4017e..b15b83179 100644 --- a/simulations/vip-999/bscmainnet.ts +++ b/simulations/vip-999/bscmainnet.ts @@ -284,10 +284,11 @@ forking(FORK_BLOCK, async () => { const idealCollateralAmount = BigNumber.from(instConfig[1]); const marginRate = BigNumber.from(instConfig[2]); const minBorrowCap = BigNumber.from(vaultConfig[3]); + const maxBorrowCap = BigNumber.from(vaultConfig[4]); const openDuration = Number(vaultConfig[6]); const lockDuration = Number(vaultConfig[7]); const marginAmount = idealCollateralAmount.mul(marginRate).div(parseUnits("1", 18)); - const lenderDepositAmount = minBorrowCap.mul(2); + const lenderDepositAmount = minBorrowCap.add(maxBorrowCap).div(2); before(async () => { const vaultAddress = await controller.allVaults(vaultsBefore); diff --git a/simulations/vip-999/bsctestnet.ts b/simulations/vip-999/bsctestnet.ts index d1a94e922..8131700d6 100644 --- a/simulations/vip-999/bsctestnet.ts +++ b/simulations/vip-999/bsctestnet.ts @@ -195,10 +195,11 @@ forking(FORK_BLOCK, async () => { const idealCollateralAmount = BigNumber.from(instConfig[1]); const marginRate = BigNumber.from(instConfig[2]); const minBorrowCap = BigNumber.from(vaultConfig[3]); + const maxBorrowCap = BigNumber.from(vaultConfig[4]); const openDuration = Number(vaultConfig[6]); const lockDuration = Number(vaultConfig[7]); const marginAmount = idealCollateralAmount.mul(marginRate).div(parseUnits("1", 18)); - const lenderDepositAmount = minBorrowCap.mul(2); + const lenderDepositAmount = minBorrowCap.add(maxBorrowCap).div(2); before(async () => { const vaultAddress = await controller.allVaults(vaultsBefore); diff --git a/vips/vip-999/bscmainnet.ts b/vips/vip-999/bscmainnet.ts index 843086d32..0bb5248c3 100644 --- a/vips/vip-999/bscmainnet.ts +++ b/vips/vip-999/bscmainnet.ts @@ -26,45 +26,45 @@ export const BOUND_VALIDATOR = "0x6E332fF0bB52475304494E4AE5063c1051c7d735"; export const BTCB_UPPER_BOUND = parseUnits("1.01", 18); export const BTCB_LOWER_BOUND = parseUnits("0.99", 18); -// TODO: supply asset +// USDT export const SUPPLY_ASSET = "0x55d398326f99059fF775485246999027B3197955"; // TODO: replace with the real Ceffu operator address before proposal export const INSTITUTION_OPERATOR = "0x1111111111111111111111111111111111111111"; // Initial vceBTC collateral supply -export const VCEBTC_INITIAL_SUPPLY = parseUnits("2000", 18); +export const VCEBTC_INITIAL_SUPPLY = parseUnits("21.92", 18); // Recipient of the initial vceBTC collateral mint (multisig) export const INITIAL_SUPPLY_RECIPIENT = "0x5D1507d5Cfb3d031C3209e9FB8e2644e4094Ea01"; -// Fixed Rate Vault configuration. TODO: finalize values. +// Fixed Rate Vault configuration. // VaultConfig: [supplyAsset, fixedAPY(bps), reserveFactor(1e18), minBorrowCap, // maxBorrowCap, minSupplierDeposit, openDuration, lockDuration, settlementWindow] export const vaultConfig = [ SUPPLY_ASSET, - 800, // fixedAPY = 8% + 600, // fixedAPY = 6% parseUnits("0.1", 18), // reserveFactor = 10% - parseUnits("1000000", 18), // minBorrowCap = 1M - parseUnits("50000000", 18), // maxBorrowCap = 50M loan market size + parseUnits("900000", 18), // minBorrowCap = 900K + parseUnits("1000000", 18), // maxBorrowCap = 1M 0, // minSupplierDeposit 7 * 24 * 60 * 60, // openDuration = 7 days - 30 * 24 * 60 * 60, // lockDuration = 30 days - 30 * 24 * 60 * 60, // settlementWindow = 30 days + 30 * 24 * 60 * 60, // lockDuration = 1 month + 3 * 24 * 60 * 60, // settlementWindow = 3 days ]; // InstitutionalConfig: [collateralAsset, idealCollateralAmount, marginRate(1e18), // institutionOperator, positionTokenId] export const instConfig = [ VCEBTC, // collateral = vceBTC - parseUnits("2000", 18), // idealCollateralAmount (sized off-chain from 50% CF + 20% buffer) - parseUnits("0.1", 18), // marginRate = 10% + parseUnits("21.92", 18), // idealCollateralAmount = 21.92 BTCB + parseUnits("0.05", 18), // marginRate = 5% (must be > 0; createVault reverts InvalidConfig if 0) INSTITUTION_OPERATOR, 0, // positionTokenId assigned by the controller ]; // RiskConfig: [liquidationThreshold(1e18), liquidationIncentive(1e18), latePenaltyRate(1e18)] -export const riskConfig = [parseUnits("0.85", 18), parseUnits("1.1", 18), parseUnits("1.1", 18)]; +export const riskConfig = [parseUnits("0.9", 18), parseUnits("1.1", 18), parseUnits("1.1", 18)]; // TODO: finalize values export const VAULT_SHARE_NAME = "Venus Ceffu Fixed Rate Vault"; diff --git a/vips/vip-999/bsctestnet.ts b/vips/vip-999/bsctestnet.ts index 4c81d47f9..3931097b6 100644 --- a/vips/vip-999/bsctestnet.ts +++ b/vips/vip-999/bsctestnet.ts @@ -21,35 +21,35 @@ export const SUPPLY_ASSET = "0xA11c8D9DC9b66E209Ef60F0C8D969D3CD988782c"; export const INSTITUTION_OPERATOR = "0x4cD6300F5cb8D6BbA5E646131c3522664C10dF11"; // Initial vceBTC collateral supply -export const VCEBTC_INITIAL_SUPPLY = parseUnits("2000", 18); +export const VCEBTC_INITIAL_SUPPLY = parseUnits("21.92", 18); // VaultConfig: [supplyAsset, fixedAPY(bps), reserveFactor(1e18), minBorrowCap, // maxBorrowCap, minSupplierDeposit, openDuration, lockDuration, settlementWindow] // Caps / minSupplierDeposit are in the supply asset (USDT, 6 decimals). export const vaultConfig = [ SUPPLY_ASSET, - 800, // fixedAPY = 8% + 600, // fixedAPY = 6% parseUnits("0.1", 18), // reserveFactor = 10% - parseUnits("1000000", 6), // minBorrowCap = 1M USDT - parseUnits("50000000", 6), // maxBorrowCap = 50M USDT loan market size + parseUnits("900000", 6), // minBorrowCap = 900K USDT + parseUnits("1000000", 6), // maxBorrowCap = 1M USDT 0, // minSupplierDeposit 7 * 24 * 60 * 60, // openDuration = 7 days - 30 * 24 * 60 * 60, // lockDuration = 30 days - 10 * 24 * 60 * 60, // settlementWindow = 10 days + 30 * 24 * 60 * 60, // lockDuration = 1 month + 3 * 24 * 60 * 60, // settlementWindow = 3 days ]; // InstitutionalConfig: [collateralAsset, idealCollateralAmount, marginRate(1e18), // institutionOperator, positionTokenId] export const instConfig = [ VCEBTC, // collateral = vceBTC (18 decimals) - parseUnits("2000", 18), // idealCollateralAmount - parseUnits("0.1", 18), // marginRate = 10% + parseUnits("21.92", 18), // idealCollateralAmount = 21.92 BTCB + parseUnits("0.05", 18), // marginRate = 5% (must be > 0; createVault reverts InvalidConfig if 0) INSTITUTION_OPERATOR, 0, // positionTokenId assigned by the controller ]; // RiskConfig: [liquidationThreshold(1e18), liquidationIncentive(1e18), latePenaltyRate(1e18)] -export const riskConfig = [parseUnits("0.85", 18), parseUnits("1.1", 18), parseUnits("1.1", 18)]; +export const riskConfig = [parseUnits("0.9", 18), parseUnits("1.1", 18), parseUnits("1.1", 18)]; export const VAULT_SHARE_NAME = "Venus Ceffu Fixed Rate Vault"; export const VAULT_SHARE_SYMBOL = "vceFRV"; From 443ee87342ed045edd6a1b64da8551bb8bca569a Mon Sep 17 00:00:00 2001 From: GitGuru7 Date: Thu, 9 Jul 2026 13:11:35 +0530 Subject: [PATCH 13/15] feat: reduce Ceffu vault margin rate to 0.5% --- vips/vip-999/bscmainnet.ts | 2 +- vips/vip-999/bsctestnet.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/vips/vip-999/bscmainnet.ts b/vips/vip-999/bscmainnet.ts index 0bb5248c3..6d3891020 100644 --- a/vips/vip-999/bscmainnet.ts +++ b/vips/vip-999/bscmainnet.ts @@ -58,7 +58,7 @@ export const vaultConfig = [ export const instConfig = [ VCEBTC, // collateral = vceBTC parseUnits("21.92", 18), // idealCollateralAmount = 21.92 BTCB - parseUnits("0.05", 18), // marginRate = 5% (must be > 0; createVault reverts InvalidConfig if 0) + parseUnits("0.005", 18), // marginRate = 0.5% (must be > 0; createVault reverts InvalidConfig if 0) INSTITUTION_OPERATOR, 0, // positionTokenId assigned by the controller ]; diff --git a/vips/vip-999/bsctestnet.ts b/vips/vip-999/bsctestnet.ts index 3931097b6..9c74c2342 100644 --- a/vips/vip-999/bsctestnet.ts +++ b/vips/vip-999/bsctestnet.ts @@ -43,7 +43,7 @@ export const vaultConfig = [ export const instConfig = [ VCEBTC, // collateral = vceBTC (18 decimals) parseUnits("21.92", 18), // idealCollateralAmount = 21.92 BTCB - parseUnits("0.05", 18), // marginRate = 5% (must be > 0; createVault reverts InvalidConfig if 0) + parseUnits("0.005", 18), // marginRate = 0.5% (must be > 0; createVault reverts InvalidConfig if 0) INSTITUTION_OPERATOR, 0, // positionTokenId assigned by the controller ]; From aa22e60e719e4aac013f6d35b9ec2b2bb8e0fc33 Mon Sep 17 00:00:00 2001 From: GitGuru7 Date: Thu, 9 Jul 2026 14:36:16 +0530 Subject: [PATCH 14/15] feat: update Ceffu vault reserve factor, min borrow cap, share name --- vips/vip-999/bscmainnet.ts | 9 ++++----- vips/vip-999/bsctestnet.ts | 8 ++++---- 2 files changed, 8 insertions(+), 9 deletions(-) diff --git a/vips/vip-999/bscmainnet.ts b/vips/vip-999/bscmainnet.ts index 6d3891020..c7598ce12 100644 --- a/vips/vip-999/bscmainnet.ts +++ b/vips/vip-999/bscmainnet.ts @@ -44,8 +44,8 @@ export const INITIAL_SUPPLY_RECIPIENT = "0x5D1507d5Cfb3d031C3209e9FB8e2644e4094E export const vaultConfig = [ SUPPLY_ASSET, 600, // fixedAPY = 6% - parseUnits("0.1", 18), // reserveFactor = 10% - parseUnits("900000", 18), // minBorrowCap = 900K + parseUnits("0.3", 18), // reserveFactor = 30% + parseUnits("10", 18), // minBorrowCap = 10 USDT (must be > 0; createVault reverts if 0) parseUnits("1000000", 18), // maxBorrowCap = 1M 0, // minSupplierDeposit 7 * 24 * 60 * 60, // openDuration = 7 days @@ -66,9 +66,8 @@ export const instConfig = [ // RiskConfig: [liquidationThreshold(1e18), liquidationIncentive(1e18), latePenaltyRate(1e18)] export const riskConfig = [parseUnits("0.9", 18), parseUnits("1.1", 18), parseUnits("1.1", 18)]; -// TODO: finalize values -export const VAULT_SHARE_NAME = "Venus Ceffu Fixed Rate Vault"; -export const VAULT_SHARE_SYMBOL = "vceFRV"; +export const VAULT_SHARE_NAME = "FRV Solv BTCB 24JUL2026 30"; +export const VAULT_SHARE_SYMBOL = "FRV-sv-24JUL2026-30"; export const INSTITUTION_NAME = "Ceffu"; export const vip999 = () => { diff --git a/vips/vip-999/bsctestnet.ts b/vips/vip-999/bsctestnet.ts index 9c74c2342..c372458b2 100644 --- a/vips/vip-999/bsctestnet.ts +++ b/vips/vip-999/bsctestnet.ts @@ -29,8 +29,8 @@ export const VCEBTC_INITIAL_SUPPLY = parseUnits("21.92", 18); export const vaultConfig = [ SUPPLY_ASSET, 600, // fixedAPY = 6% - parseUnits("0.1", 18), // reserveFactor = 10% - parseUnits("900000", 6), // minBorrowCap = 900K USDT + parseUnits("0.3", 18), // reserveFactor = 30% + parseUnits("10", 6), // minBorrowCap = 10 USDT (must be > 0; createVault reverts if 0) parseUnits("1000000", 6), // maxBorrowCap = 1M USDT 0, // minSupplierDeposit 7 * 24 * 60 * 60, // openDuration = 7 days @@ -51,8 +51,8 @@ export const instConfig = [ // RiskConfig: [liquidationThreshold(1e18), liquidationIncentive(1e18), latePenaltyRate(1e18)] export const riskConfig = [parseUnits("0.9", 18), parseUnits("1.1", 18), parseUnits("1.1", 18)]; -export const VAULT_SHARE_NAME = "Venus Ceffu Fixed Rate Vault"; -export const VAULT_SHARE_SYMBOL = "vceFRV"; +export const VAULT_SHARE_NAME = "FRV Solv BTCB 24JUL2026 30"; +export const VAULT_SHARE_SYMBOL = "FRV-sv-24JUL2026-30"; export const INSTITUTION_NAME = "Ceffu"; export const vip999 = () => { From a278dc0fa469df4873532aed84288e27b85a9ee9 Mon Sep 17 00:00:00 2001 From: GitGuru7 Date: Thu, 9 Jul 2026 18:56:38 +0530 Subject: [PATCH 15/15] refactor: assert cloned vceBTC oracle config and simplify vault sim workarounds --- simulations/vip-999/abi/ChainlinkOracle.json | 187 +++++++++++++++++++ simulations/vip-999/bscmainnet.ts | 117 ++++++++---- src/networkAddresses.ts | 2 + src/utils.ts | 4 +- vips/vip-999/bscmainnet.ts | 65 ++++--- vips/vip-999/bsctestnet.ts | 5 + 6 files changed, 319 insertions(+), 61 deletions(-) create mode 100644 simulations/vip-999/abi/ChainlinkOracle.json diff --git a/simulations/vip-999/abi/ChainlinkOracle.json b/simulations/vip-999/abi/ChainlinkOracle.json new file mode 100644 index 000000000..a30575f77 --- /dev/null +++ b/simulations/vip-999/abi/ChainlinkOracle.json @@ -0,0 +1,187 @@ +[ + { "inputs": [], "stateMutability": "nonpayable", "type": "constructor" }, + { + "inputs": [ + { "internalType": "address", "name": "sender", "type": "address" }, + { "internalType": "address", "name": "calledContract", "type": "address" }, + { "internalType": "string", "name": "methodSignature", "type": "string" } + ], + "name": "Unauthorized", + "type": "error" + }, + { + "anonymous": false, + "inputs": [{ "indexed": false, "internalType": "uint8", "name": "version", "type": "uint8" }], + "name": "Initialized", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { "indexed": false, "internalType": "address", "name": "oldAccessControlManager", "type": "address" }, + { "indexed": false, "internalType": "address", "name": "newAccessControlManager", "type": "address" } + ], + "name": "NewAccessControlManager", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { "indexed": true, "internalType": "address", "name": "previousOwner", "type": "address" }, + { "indexed": true, "internalType": "address", "name": "newOwner", "type": "address" } + ], + "name": "OwnershipTransferStarted", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { "indexed": true, "internalType": "address", "name": "previousOwner", "type": "address" }, + { "indexed": true, "internalType": "address", "name": "newOwner", "type": "address" } + ], + "name": "OwnershipTransferred", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { "indexed": true, "internalType": "address", "name": "asset", "type": "address" }, + { "indexed": false, "internalType": "uint256", "name": "previousPriceMantissa", "type": "uint256" }, + { "indexed": false, "internalType": "uint256", "name": "newPriceMantissa", "type": "uint256" } + ], + "name": "PricePosted", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { "indexed": true, "internalType": "address", "name": "asset", "type": "address" }, + { "indexed": false, "internalType": "address", "name": "feed", "type": "address" }, + { "indexed": false, "internalType": "uint256", "name": "maxStalePeriod", "type": "uint256" } + ], + "name": "TokenConfigAdded", + "type": "event" + }, + { + "inputs": [], + "name": "BNB_ADDR", + "outputs": [{ "internalType": "address", "name": "", "type": "address" }], + "stateMutability": "view", + "type": "function" + }, + { "inputs": [], "name": "acceptOwnership", "outputs": [], "stateMutability": "nonpayable", "type": "function" }, + { + "inputs": [], + "name": "accessControlManager", + "outputs": [{ "internalType": "contract IAccessControlManagerV8", "name": "", "type": "address" }], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [{ "internalType": "address", "name": "asset", "type": "address" }], + "name": "getPrice", + "outputs": [{ "internalType": "uint256", "name": "", "type": "uint256" }], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [{ "internalType": "address", "name": "accessControlManager_", "type": "address" }], + "name": "initialize", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "owner", + "outputs": [{ "internalType": "address", "name": "", "type": "address" }], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "pendingOwner", + "outputs": [{ "internalType": "address", "name": "", "type": "address" }], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [{ "internalType": "address", "name": "", "type": "address" }], + "name": "prices", + "outputs": [{ "internalType": "uint256", "name": "", "type": "uint256" }], + "stateMutability": "view", + "type": "function" + }, + { "inputs": [], "name": "renounceOwnership", "outputs": [], "stateMutability": "nonpayable", "type": "function" }, + { + "inputs": [{ "internalType": "address", "name": "accessControlManager_", "type": "address" }], + "name": "setAccessControlManager", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { "internalType": "address", "name": "asset", "type": "address" }, + { "internalType": "uint256", "name": "price", "type": "uint256" } + ], + "name": "setDirectPrice", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "components": [ + { "internalType": "address", "name": "asset", "type": "address" }, + { "internalType": "address", "name": "feed", "type": "address" }, + { "internalType": "uint256", "name": "maxStalePeriod", "type": "uint256" } + ], + "internalType": "struct ChainlinkOracle.TokenConfig", + "name": "tokenConfig", + "type": "tuple" + } + ], + "name": "setTokenConfig", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "components": [ + { "internalType": "address", "name": "asset", "type": "address" }, + { "internalType": "address", "name": "feed", "type": "address" }, + { "internalType": "uint256", "name": "maxStalePeriod", "type": "uint256" } + ], + "internalType": "struct ChainlinkOracle.TokenConfig[]", + "name": "tokenConfigs_", + "type": "tuple[]" + } + ], + "name": "setTokenConfigs", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [{ "internalType": "address", "name": "", "type": "address" }], + "name": "tokenConfigs", + "outputs": [ + { "internalType": "address", "name": "asset", "type": "address" }, + { "internalType": "address", "name": "feed", "type": "address" }, + { "internalType": "uint256", "name": "maxStalePeriod", "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-999/bscmainnet.ts b/simulations/vip-999/bscmainnet.ts index b15b83179..7e24fe516 100644 --- a/simulations/vip-999/bscmainnet.ts +++ b/simulations/vip-999/bscmainnet.ts @@ -4,7 +4,7 @@ import { BigNumber, Contract } from "ethers"; import { parseUnits } from "ethers/lib/utils"; import { ethers } from "hardhat"; import { NETWORK_ADDRESSES } from "src/networkAddresses"; -import { initMainnetUser, setMaxStalePeriodForAllAssets, setMaxStalePeriodInChainlinkOracle } from "src/utils"; +import { initMainnetUser, setMaxStalePeriodForAllAssets } from "src/utils"; import { forking, pretendExecutingVip, testVip } from "src/vip-framework"; import vip640 from "../../vips/vip-640/bscmainnet"; @@ -12,10 +12,8 @@ import vip999, { ATLAS_ORACLE, BOUND_VALIDATOR, BTCB, - BTCB_FEED_FALLBACK, - BTCB_FEED_MAIN, - BTCB_FEED_PIVOT, BTCB_LOWER_BOUND, + BTCB_ORACLE_CONFIGS, BTCB_UPPER_BOUND, CHAINLINK_ORACLE, FIXED_RATE_VAULT_CONTROLLER, @@ -34,6 +32,7 @@ import vip999, { } from "../../vips/vip-999/bscmainnet"; import ACM_ABI from "./abi/AccessControlManager.json"; import BOUND_VALIDATOR_ABI from "./abi/BoundValidator.json"; +import CHAINLINK_ORACLE_ABI from "./abi/ChainlinkOracle.json"; import VAULT_ABI from "./abi/InstitutionalLoanVault.json"; import CONTROLLER_ABI from "./abi/InstitutionalVaultController.json"; import ORACLE_ABI from "./abi/ResilientOracle.json"; @@ -42,14 +41,30 @@ import ERC20_ABI from "./abi/VenusERC20.json"; const { bscmainnet } = NETWORK_ADDRESSES; const FORK_BLOCK = 108402150; -const ONE_YEAR = 31536000; const USDT_WHALE = "0xF977814e90dA44bFA03b6295A0616a897441aceC"; // Binance Hot Wallet const LENDER = "0x2222222222222222222222222222222222222222"; // dummy test lender +// Minimal price-oracle stub that always returns 1e18 — swapped in so getPrice-probing calls +// succeed against feeds that are stale on the fork or not configured yet. +// 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 deployStubOracle = async (): Promise => { + const [deployer] = await ethers.getSigners(); + const factory = new ethers.ContractFactory( + ["function getPrice(address) external pure returns (uint256)"], + STUB_ORACLE_BYTECODE, + deployer, + ); + const stubOracle = await factory.deploy(); + await stubOracle.deployed(); + return stubOracle; +}; // IVaultTypes.VaultState const VaultState = { @@ -71,6 +86,8 @@ forking(FORK_BLOCK, async () => { let timelock: any; let vaultsBefore: any; let btcbPriceAtFork: any; + let originalControllerOracle: string; + const vceBtcConfigsAfterVip: Record = {}; before(async () => { oracle = await ethers.getContractAt(ORACLE_ABI, bscmainnet.RESILIENT_ORACLE); @@ -81,26 +98,13 @@ forking(FORK_BLOCK, async () => { timelock = await initMainnetUser(bscmainnet.NORMAL_TIMELOCK, parseUnits("40")); vaultsBefore = await controller.allVaultsLength(); - const usdtPriceAtFork = await oracle.getPrice(SUPPLY_ASSET); + // Capture the BTCB price while the fork-block feed data is still fresh btcbPriceAtFork = await oracle.getPrice(BTCB); - await setMaxStalePeriodForAllAssets(oracle, [btcb, usdt]); - const redstoneOracleForExisting = await ethers.getContractAt( - ["function setDirectPrice(address asset, uint256 price) external"], - REDSTONE_ORACLE, - ); - await redstoneOracleForExisting.connect(timelock).setDirectPrice(BTCB, btcbPriceAtFork); - await redstoneOracleForExisting.connect(timelock).setDirectPrice(SUPPLY_ASSET, usdtPriceAtFork); // Reproduce the prerequisite controller/vault-upgrade VIP (VIP-640), which upgrades the // controller/vault implementations and re-grants the 6-arg createVault permission. await pretendExecutingVip(await vip640(), bscmainnet.NORMAL_TIMELOCK); - 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(); - await controller.connect(timelock).setOracle(stubOracle.address); - vceBTC = await ethers.getContractAt(ERC20_ABI, VCEBTC); }); @@ -130,26 +134,48 @@ forking(FORK_BLOCK, async () => { expect( await acm.connect(vceBtcAsCaller).isAllowedToCall(bscmainnet.CRITICAL_GUARDIAN, "mint(address,uint256)"), ).to.equal(false); + expect( + await acm.connect(vceBtcAsCaller).isAllowedToCall(bscmainnet.NORMAL_TIMELOCK, "burn(address,uint256)"), + ).to.equal(false); + expect( + await acm.connect(vceBtcAsCaller).isAllowedToCall(bscmainnet.CRITICAL_GUARDIAN, "burn(address,uint256)"), + ).to.equal(false); + }); + + it("BTCB's live sub-oracle configs match the feeds and stale periods the VIP clones", async () => { + for (const { name, address, feed, maxStalePeriod } of BTCB_ORACLE_CONFIGS) { + const subOracle = await ethers.getContractAt(CHAINLINK_ORACLE_ABI, address); + const config = await subOracle.tokenConfigs(BTCB); + expect(config.feed, name).to.equal(feed); + expect(config.maxStalePeriod, name).to.equal(maxStalePeriod); + } + }); + + // createVault probes getPrice() for both vault assets, but on the fork the feeds are stale by + // execution time (testVip advances days) and vceBTC's config doesn't exist until the VIP runs. + // Swap the controller's oracle for an always-1e18 stub; callbackAfterExecution restores it. + it("[Test-Only] swaps the controller's oracle for a stub so createVault's price probe passes during execution", async () => { + originalControllerOracle = await controller.oracle(); + expect(originalControllerOracle).to.equal(bscmainnet.RESILIENT_ORACLE); + const stubOracle = await deployStubOracle(); + await controller.connect(timelock).setOracle(stubOracle.address); + expect(await controller.oracle()).to.equal(stubOracle.address); }); }); testVip("VIP-999 Create Ceffu Custody BTC Fixed Rate Vault", await vip999(), { callbackAfterExecution: async () => { - // vceBTC's oracle config is only created by the VIP itself, so its stale period - // can only be bumped now. - for (const [oracleAddress, feed] of [ - [CHAINLINK_ORACLE, BTCB_FEED_MAIN], - [REDSTONE_ORACLE, BTCB_FEED_PIVOT], - [ATLAS_ORACLE, BTCB_FEED_FALLBACK], - ]) { - await setMaxStalePeriodInChainlinkOracle(oracleAddress, VCEBTC, feed, bscmainnet.NORMAL_TIMELOCK, ONE_YEAR); + // createVault executed against the stub oracle set in the last pre-VIP step; restore the + // original oracle so everything below runs against production wiring. + await controller.connect(timelock).setOracle(originalControllerOracle); + // Snapshot the VIP-set vceBTC sub-oracle configs before the stale-period bump rewrites them. + for (const { name, address } of BTCB_ORACLE_CONFIGS) { + const subOracle = await ethers.getContractAt(CHAINLINK_ORACLE_ABI, address); + vceBtcConfigsAfterVip[name] = await subOracle.tokenConfigs(VCEBTC); } - const redstoneOracle = await ethers.getContractAt( - ["function setDirectPrice(address asset, uint256 price) external"], - REDSTONE_ORACLE, - ); - await redstoneOracle.connect(timelock).setDirectPrice(VCEBTC, btcbPriceAtFork); - await controller.connect(timelock).setOracle(bscmainnet.RESILIENT_ORACLE); + // Fork feed data is frozen at the fork block while testVip advanced simulated time past the + // real stale periods: bump them for every priced asset (vceBTC's config only exists now). + await setMaxStalePeriodForAllAssets(oracle, [btcb, usdt, vceBTC]); }, }); @@ -259,6 +285,29 @@ forking(FORK_BLOCK, async () => { expect(vceBtcPrice).to.equal(btcbPrice); }); + // Together with the pre-VIP check that BTCB's live configs match BTCB_ORACLE_CONFIGS, this proves + // vceBTC's configs are exact clones of BTCB's. + for (const { name, feed, maxStalePeriod } of BTCB_ORACLE_CONFIGS) { + it(`vceBTC ${name} config clones BTCB's feed and stale period`, async () => { + const vceBtcConfig = vceBtcConfigsAfterVip[name]; + expect(vceBtcConfig, `${name} snapshot missing — callbackAfterExecution did not run`).to.not.be.undefined; + expect(vceBtcConfig.asset).to.equal(VCEBTC); + expect(vceBtcConfig.feed).to.equal(feed); + expect(vceBtcConfig.maxStalePeriod).to.equal(maxStalePeriod); + }); + } + + it("vceBTC ResilientOracle config mirrors BTCB's (same sub-oracles, all enabled)", async () => { + const btcbConfig = await oracle.getTokenConfig(BTCB); + const vceBtcConfig = await oracle.getTokenConfig(VCEBTC); + expect(vceBtcConfig.asset).to.equal(VCEBTC); + expect(vceBtcConfig.oracles).to.deep.equal([CHAINLINK_ORACLE, REDSTONE_ORACLE, ATLAS_ORACLE]); + expect(vceBtcConfig.oracles).to.deep.equal(btcbConfig.oracles); + expect(vceBtcConfig.enableFlagsForOracles).to.deep.equal(btcbConfig.enableFlagsForOracles); + expect(vceBtcConfig.cachingEnabled).to.equal(btcbConfig.cachingEnabled); + expect(vceBtcConfig.cachingEnabled).to.equal(false); + }); + it("BTCB bounds are configured correctly in BoundValidator", async () => { const boundValidator = await ethers.getContractAt(BOUND_VALIDATOR_ABI, BOUND_VALIDATOR); const btcbBounds = await boundValidator.validateConfigs(BTCB); diff --git a/src/networkAddresses.ts b/src/networkAddresses.ts index c985fd67f..002bf48a2 100644 --- a/src/networkAddresses.ts +++ b/src/networkAddresses.ts @@ -50,6 +50,7 @@ export const NETWORK_ADDRESSES = { BINANCE_ORACLE: oracleBscmainnetContracts.addresses.BinanceOracle, RESILIENT_ORACLE: oracleBscmainnetContracts.addresses.ResilientOracle, REDSTONE_ORACLE: oracleBscmainnetContracts.addresses.RedStoneOracle, + ATLAS_ORACLE: "0x9E6928Ec418948ceb9f1cd9872fD312b13D841D0", }, bsctestnet: { DEFAULT_PROPOSER_ADDRESS: "0x2Ce1d0ffD7E869D9DF33e28552b12DdDed326706", @@ -76,6 +77,7 @@ export const NETWORK_ADDRESSES = { BINANCE_ORACLE: oracleBsctestnetContracts.addresses.BinanceOracle, RESILIENT_ORACLE: oracleBsctestnetContracts.addresses.ResilientOracle, REDSTONE_ORACLE: oracleBsctestnetContracts.addresses.RedStoneOracle, + ATLAS_ORACLE: "0x7F00af2f30a55e79311392C98fBBfA629D19b3A5", }, ethereum: { NORMAL_TIMELOCK: "0xd969E79406c35E80750aAae061D402Aab9325714", diff --git a/src/utils.ts b/src/utils.ts index 84da07f85..3306f0fbe 100644 --- a/src/utils.ts +++ b/src/utils.ts @@ -478,6 +478,8 @@ export const setMaxStalePeriod = async ( binance: tryGetForkedNetworkAddress("BINANCE_ORACLE"), chainlink: tryGetForkedNetworkAddress("CHAINLINK_ORACLE"), redstone: tryGetForkedNetworkAddress("REDSTONE_ORACLE"), + // same interface as the Chainlink oracle, different address + atlas: tryGetForkedNetworkAddress("ATLAS_ORACLE"), }; const adminKey = NORMAL_TIMELOCK_NETWORKS.includes(FORKED_NETWORK ?? "") ? "NORMAL_TIMELOCK" : "GUARDIAN"; @@ -495,7 +497,7 @@ export const setMaxStalePeriod = async ( const symbol = underlyingAsset.address.toLowerCase() === ORACLE_BNB.toLowerCase() ? "BNB" : await underlyingAsset.symbol(); await setMaxStalePeriodInBinanceOracle(oracle, symbol, maxStalePeriodInSeconds); - } else if (oracle === knownOracles.chainlink || oracle === knownOracles.redstone) { + } else if (oracle === knownOracles.chainlink || oracle === knownOracles.redstone || oracle === knownOracles.atlas) { await setMaxStalePeriodInChainlinkOracle( oracle, underlyingAsset.address, diff --git a/vips/vip-999/bscmainnet.ts b/vips/vip-999/bscmainnet.ts index c7598ce12..502ffaa2c 100644 --- a/vips/vip-999/bscmainnet.ts +++ b/vips/vip-999/bscmainnet.ts @@ -5,22 +5,45 @@ import { makeProposal } from "src/utils"; const { bscmainnet } = NETWORK_ADDRESSES; +// TODO before proposing: +// 1. Execute the controller/vault upgrade VIP (vip-640) first, then remove the +// pretendExecutingVip(vip640) prerequisite from the simulation. +// 2. Set the real Ceffu institution operator address (INSTITUTION_OPERATOR below). +// 3. Redeploy vceBTC from fixed-rate-vaults CustodyReceiptToken (current deployment is the older +// VenusERC20 bytecode without pause/unpause), transfer ownership to the Normal Timelock, +// update VCEBTC, add pause()/unpause() ACM grants, and update the simulations. + // Deployed vceBTC export const VCEBTC = "0xba63642A893b0F15aDE730943972824c9e2147a7"; export const FIXED_RATE_VAULT_CONTROLLER = "0x6D9e91cB766259af42619c14c994E694E57e6E85"; export const BTCB = "0x7130d2A12B9BCbFAe4f2634d864A1Ee1Ce3Ead9c"; -// BTCB's oracle config -export const CHAINLINK_ORACLE = "0x1B2103441A0A108daD8848D8F5d790e4D402921F"; -export const REDSTONE_ORACLE = "0x8455EFA4D7Ff63b8BFD96AdD889483Ea7d39B70a"; -export const ATLAS_ORACLE = "0x9E6928Ec418948ceb9f1cd9872fD312b13D841D0"; -export const BTCB_FEED_MAIN = "0x8ECF7dE377F788A813F5215668E282556b35f300"; -export const BTCB_FEED_PIVOT = "0xa51738d1937FFc553d5070f43300B385AA2D9F55"; -export const BTCB_FEED_FALLBACK = "0x4f6c53fb9CdD46269d24bCa4E68bB680879132fc"; -export const BTCB_STALE_MAIN = 100; -export const BTCB_STALE_PIVOT = 100; -export const BTCB_STALE_FALLBACK = 120; +export const CHAINLINK_ORACLE = bscmainnet.CHAINLINK_ORACLE; +export const REDSTONE_ORACLE = bscmainnet.REDSTONE_ORACLE; +export const ATLAS_ORACLE = bscmainnet.ATLAS_ORACLE; + +// BTCB's oracle config per sub-oracle — the VIP clones these for vceBTC: (oracle, feed, max stale period) +export const BTCB_ORACLE_CONFIGS = [ + { + name: "Chainlink", + address: CHAINLINK_ORACLE, + feed: "0x8ECF7dE377F788A813F5215668E282556b35f300", + maxStalePeriod: 100, + }, + { + name: "RedStone", + address: REDSTONE_ORACLE, + feed: "0xa51738d1937FFc553d5070f43300B385AA2D9F55", + maxStalePeriod: 100, + }, + { + name: "Atlas", + address: ATLAS_ORACLE, + feed: "0x4f6c53fb9CdD46269d24bCa4E68bB680879132fc", + maxStalePeriod: 120, + }, +]; export const BOUND_VALIDATOR = "0x6E332fF0bB52475304494E4AE5063c1051c7d735"; export const BTCB_UPPER_BOUND = parseUnits("1.01", 18); @@ -29,7 +52,7 @@ export const BTCB_LOWER_BOUND = parseUnits("0.99", 18); // USDT export const SUPPLY_ASSET = "0x55d398326f99059fF775485246999027B3197955"; -// TODO: replace with the real Ceffu operator address before proposal +// TODO(2): placeholder — replace with the real Ceffu operator address export const INSTITUTION_OPERATOR = "0x1111111111111111111111111111111111111111"; // Initial vceBTC collateral supply @@ -80,7 +103,7 @@ This proposal onboards a new custody-mirror collateral token, **vceBTC ("Ceffu C #### Actions -1. **Oracle** — price vceBTC identically to BTCB by cloning BTCB's full oracle configuration: the main / pivot / fallback sub-oracle feeds, the BoundValidator bounds, and the ResilientOracle token config. +1. **Oracle** — price vceBTC identically to BTCB by cloning BTCB's full oracle configuration: the Chainlink / RedStone / Atlas sub-oracle feeds and stale periods, the BoundValidator bounds, and the ResilientOracle token config. 2. **Ownership** — accept ownership of vceBTC (already transferred to the Normal Timelock by the deployer). 3. **Access control** — grant \`mint(address,uint256)\` and \`burn(address,uint256)\` on vceBTC to the Normal Timelock and the Guardian. (The \`createVault(...)\` permission on the InstitutionalVaultController is granted by the separate controller/vault-upgrade VIP.) 4. **Initial supply** — mint the initial vceBTC collateral to the Ceffu multisig. @@ -95,21 +118,11 @@ This proposal onboards a new custody-mirror collateral token, **vceBTC ("Ceffu C // ────────────────────────────────────────────────────────────────────── // 1. Oracle configuration identically to BTCB // ────────────────────────────────────────────────────────────────────── - { - target: CHAINLINK_ORACLE, - signature: "setTokenConfig((address,address,uint256))", - params: [[VCEBTC, BTCB_FEED_MAIN, BTCB_STALE_MAIN]], - }, - { - target: REDSTONE_ORACLE, + ...BTCB_ORACLE_CONFIGS.map(({ address, feed, maxStalePeriod }) => ({ + target: address, signature: "setTokenConfig((address,address,uint256))", - params: [[VCEBTC, BTCB_FEED_PIVOT, BTCB_STALE_PIVOT]], - }, - { - target: ATLAS_ORACLE, - signature: "setTokenConfig((address,address,uint256))", - params: [[VCEBTC, BTCB_FEED_FALLBACK, BTCB_STALE_FALLBACK]], - }, + params: [[VCEBTC, feed, maxStalePeriod]], + })), { target: BOUND_VALIDATOR, signature: "setValidateConfig((address,uint256,uint256))", diff --git a/vips/vip-999/bsctestnet.ts b/vips/vip-999/bsctestnet.ts index c372458b2..911917dbf 100644 --- a/vips/vip-999/bsctestnet.ts +++ b/vips/vip-999/bsctestnet.ts @@ -6,6 +6,11 @@ import { makeProposal } from "src/utils"; const { bsctestnet } = NETWORK_ADDRESSES; +// TODO before proposing: +// 1. Redeploy vceBTC from fixed-rate-vaults CustodyReceiptToken (current deployment is the older +// VenusERC20 bytecode without pause/unpause), transfer ownership to the Normal Timelock, +// update VCEBTC, add pause()/unpause() ACM grants, and update the simulations. + export const VCEBTC = "0x3C5Fc884BF6d1Ec8957A75EF6436b3B5750A57da"; export const FIXED_RATE_VAULT_CONTROLLER = "0xf77dED2A00F94e33C392126238360D4642c16Ba2"; export const BTCB = "0xA808e341e8e723DC6BA0Bb5204Bafc2330d7B8e4";