From 3a971d2cdf0e44cd334f16829411e46979556e52 Mon Sep 17 00:00:00 2001 From: Debugger022 Date: Fri, 13 Feb 2026 18:15:26 +0530 Subject: [PATCH 1/2] feat: add VIP-650 RedStone oracle feed expansion for BSC Core Pool Register 13 new RedStone price feeds (XVS, LTC, BCH, DOT, LINK, DAI, FIL, DOGE, AAVE, UNI, FDUSD, TWT, SOL) and update resilient oracle configs to use RedStone as MAIN with Chainlink/Binance redundancy. Includes BoundValidator config for TWT which previously had no PIVOT. --- simulations/vip-650/abi/boundValidator.json | 157 ++++++++ simulations/vip-650/abi/chainlinkOracle.json | 187 +++++++++ simulations/vip-650/abi/resilientOracle.json | 320 ++++++++++++++++ simulations/vip-650/bscmainnet.ts | 168 ++++++++ vips/vip-650/bscmainnet.ts | 384 +++++++++++++++++++ 5 files changed, 1216 insertions(+) create mode 100644 simulations/vip-650/abi/boundValidator.json create mode 100644 simulations/vip-650/abi/chainlinkOracle.json create mode 100644 simulations/vip-650/abi/resilientOracle.json create mode 100644 simulations/vip-650/bscmainnet.ts create mode 100644 vips/vip-650/bscmainnet.ts diff --git a/simulations/vip-650/abi/boundValidator.json b/simulations/vip-650/abi/boundValidator.json new file mode 100644 index 000000000..bd39b8e51 --- /dev/null +++ b/simulations/vip-650/abi/boundValidator.json @@ -0,0 +1,157 @@ +[ + { "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-650/abi/chainlinkOracle.json b/simulations/vip-650/abi/chainlinkOracle.json new file mode 100644 index 000000000..7dd5f6f5f --- /dev/null +++ b/simulations/vip-650/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": "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": [{ "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-650/abi/resilientOracle.json b/simulations/vip-650/abi/resilientOracle.json new file mode 100644 index 000000000..373eb14e9 --- /dev/null +++ b/simulations/vip-650/abi/resilientOracle.json @@ -0,0 +1,320 @@ +[ + { + "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": true, "internalType": "address", "name": "asset", "type": "address" }, + { "indexed": true, "internalType": "bool", "name": "enabled", "type": "bool" } + ], + "name": "CachedEnabled", + "type": "event" + }, + { + "anonymous": false, + "inputs": [{ "indexed": false, "internalType": "uint8", "name": "version", "type": "uint8" }], + "name": "Initialized", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { "indexed": false, "internalType": "address", "name": "oldAccessControlManager", "type": "address" }, + { "indexed": false, "internalType": "address", "name": "newAccessControlManager", "type": "address" } + ], + "name": "NewAccessControlManager", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { "indexed": true, "internalType": "address", "name": "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": "CACHE_SLOT", + "outputs": [{ "internalType": "bytes32", "name": "", "type": "bytes32" }], + "stateMutability": "view", + "type": "function" + }, + { + "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": "bool", "name": "cachingEnabled", "type": "bool" } + ], + "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": "bool", "name": "cachingEnabled", "type": "bool" } + ], + "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": "bool", "name": "cachingEnabled", "type": "bool" } + ], + "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-650/bscmainnet.ts b/simulations/vip-650/bscmainnet.ts new file mode 100644 index 000000000..1aa6b7c1f --- /dev/null +++ b/simulations/vip-650/bscmainnet.ts @@ -0,0 +1,168 @@ +import { expect } from "chai"; +import { Contract } from "ethers"; +import { ethers } from "hardhat"; +import { NETWORK_ADDRESSES } from "src/networkAddresses"; +import { + expectEvents, + initMainnetUser, + setMaxStalePeriodInBinanceOracle, + setMaxStalePeriodInChainlinkOracle, +} from "src/utils"; +import { forking, testVip } from "src/vip-framework"; + +import vip650, { + BOUND_VALIDATOR, + NEW_ORACLE_CONFIG, + NEW_REDSTONE_ORACLE_FEEDS, + NEW_STANDARD_ORACLE_CONFIG, + NEW_TWT_ORACLE_CONFIG, + OLD_ORACLE_CONFIG, + PRICE_LOWER_BOUND, + PRICE_UPPER_BOUND, +} from "../../vips/vip-650/bscmainnet"; +import BOUND_VALIDATOR_ABI from "./abi/boundValidator.json"; +import CHAINLINK_ORACLE_ABI from "./abi/chainlinkOracle.json"; +import RESILIENT_ORACLE_ABI from "./abi/resilientOracle.json"; + +const { bscmainnet } = NETWORK_ADDRESSES; +const addressZero = ethers.constants.AddressZero; + +forking(80964126, async () => { + let redstoneOracle: Contract; + let resilientOracle: Contract; + const preVipPrices: Record = {}; + + let boundValidator: Contract; + + before(async () => { + redstoneOracle = new ethers.Contract(bscmainnet.REDSTONE_ORACLE, CHAINLINK_ORACLE_ABI, ethers.provider); + resilientOracle = new ethers.Contract(bscmainnet.RESILIENT_ORACLE, RESILIENT_ORACLE_ABI, ethers.provider); + boundValidator = new ethers.Contract(BOUND_VALIDATOR, BOUND_VALIDATOR_ABI, ethers.provider); + }); + + describe("Pre-VIP behavior", async () => { + it("has no RedStone feed configured for assets to be added", async () => { + for (const { ASSET } of NEW_REDSTONE_ORACLE_FEEDS) { + const cfg = await redstoneOracle.tokenConfigs(ASSET); + expect(cfg.asset).to.equal(addressZero); + } + }); + + it("has resilient oracle configs matching old config for all assets", async () => { + for (const oldConfig of OLD_ORACLE_CONFIG) { + const cfg = await resilientOracle.getTokenConfig(oldConfig.ASSET); + + expect(cfg.asset).to.equal(oldConfig.ASSET); + expect(cfg.oracles[0]).to.equal(oldConfig.MAIN); + expect(cfg.oracles[1]).to.equal(oldConfig.PIVOT); + expect(cfg.oracles[2]).to.equal(oldConfig.FALLBACK); + expect(cfg.cachingEnabled).to.equal(oldConfig.CACHED); + } + }); + + it("stores current resilient oracle prices for all assets", async () => { + for (const { ASSET } of NEW_ORACLE_CONFIG) { + const price = await resilientOracle.getPrice(ASSET); + preVipPrices[ASSET] = price; + } + }); + }); + + testVip("VIP-650 bscmainnet", await vip650(), { + callbackAfterExecution: async txResponse => { + const totalTokenConfigAdded = NEW_REDSTONE_ORACLE_FEEDS.length + NEW_ORACLE_CONFIG.length; + await expectEvents( + txResponse, + [CHAINLINK_ORACLE_ABI, RESILIENT_ORACLE_ABI], + ["TokenConfigAdded", "TokenConfigAdded"], + [totalTokenConfigAdded, totalTokenConfigAdded], + ); + }, + }); + + describe("Post-VIP behavior", async () => { + it("sets expected RedStone oracle feeds", async () => { + for (const { ASSET, FEED, MAX_STALE_PERIOD } of NEW_REDSTONE_ORACLE_FEEDS) { + const cfg = await redstoneOracle.tokenConfigs(ASSET); + expect(cfg.asset).to.equal(ASSET); + expect(cfg.feed).to.equal(FEED); + expect(cfg.maxStalePeriod).to.equal(MAX_STALE_PERIOD); + } + }); + + it("sets expected resilient oracle config for all assets", async () => { + for (const oracleConfig of NEW_ORACLE_CONFIG) { + const cfg = await resilientOracle.getTokenConfig(oracleConfig.ASSET); + + const expectedOracles = [oracleConfig.MAIN, oracleConfig.PIVOT, oracleConfig.FALLBACK]; + + expect(cfg.asset).to.equal(oracleConfig.ASSET); + expect(cfg.oracles[0]).to.equal(expectedOracles[0]); + expect(cfg.oracles[1]).to.equal(expectedOracles[1]); + expect(cfg.oracles[2]).to.equal(expectedOracles[2]); + + expect(cfg.enableFlagsForOracles[0]).to.equal(expectedOracles[0] !== addressZero); + expect(cfg.enableFlagsForOracles[1]).to.equal(expectedOracles[1] !== addressZero); + expect(cfg.enableFlagsForOracles[2]).to.equal(expectedOracles[2] !== addressZero); + + expect(cfg.cachingEnabled).to.equal(oracleConfig.CACHED); + } + }); + + it("sets BoundValidator config for TWT", async () => { + const cfg = await boundValidator.validateConfigs(NEW_TWT_ORACLE_CONFIG.ASSET); + expect(cfg.upperBoundRatio).to.equal(PRICE_UPPER_BOUND); + expect(cfg.lowerBoundRatio).to.equal(PRICE_LOWER_BOUND); + }); + + it("keeps resilient oracle prices unchanged for all assets", async () => { + // Standard assets: extend stale periods for RedStone, Chainlink, and Binance + for (const { ASSET, NAME } of NEW_STANDARD_ORACLE_CONFIG) { + await setMaxStalePeriodInChainlinkOracle( + bscmainnet.REDSTONE_ORACLE, + ASSET, + ethers.constants.AddressZero, + bscmainnet.NORMAL_TIMELOCK, + 315360000, + ); + await setMaxStalePeriodInChainlinkOracle( + bscmainnet.CHAINLINK_ORACLE, + ASSET, + ethers.constants.AddressZero, + bscmainnet.NORMAL_TIMELOCK, + 315360000, + ); + await setMaxStalePeriodInBinanceOracle(bscmainnet.BINANCE_ORACLE, NAME, 315360000); + } + + // TWT: extend stale periods for RedStone and Binance (no Chainlink feed) + await setMaxStalePeriodInChainlinkOracle( + bscmainnet.REDSTONE_ORACLE, + NEW_TWT_ORACLE_CONFIG.ASSET, + ethers.constants.AddressZero, + bscmainnet.NORMAL_TIMELOCK, + 315360000, + ); + await setMaxStalePeriodInBinanceOracle(bscmainnet.BINANCE_ORACLE, NEW_TWT_ORACLE_CONFIG.NAME, 315360000); + + // Set direct price on RedStone oracle for TWT using the pre-VIP Binance price. + // This bypasses the feed staleness check that occurs in the fork simulation + // (the VIP execution advances time ~72h past the feed's last update). + const oracleAdmin = await initMainnetUser(bscmainnet.NORMAL_TIMELOCK, ethers.utils.parseEther("1.0")); + await redstoneOracle + .connect(oracleAdmin) + .setDirectPrice(NEW_TWT_ORACLE_CONFIG.ASSET, preVipPrices[NEW_TWT_ORACLE_CONFIG.ASSET]); + + // Verify prices haven't deviated significantly + for (const { NAME, ASSET } of NEW_ORACLE_CONFIG) { + const priceAfter = await resilientOracle.getPrice(ASSET); + const priceBefore = preVipPrices[ASSET]; + const diff = priceAfter.sub(priceBefore); + const diffPct = `${(Number(diff.mul(10000).div(priceBefore)) / 100).toFixed(2)}%`; + console.log(`${NAME}: difference = ${diff.toString()} (${diffPct})`); + const tolerance = priceBefore.mul(3).div(100); // allow 3% difference + expect(priceAfter).to.be.closeTo(priceBefore, tolerance); + } + }); + }); +}); diff --git a/vips/vip-650/bscmainnet.ts b/vips/vip-650/bscmainnet.ts new file mode 100644 index 000000000..9746dd7aa --- /dev/null +++ b/vips/vip-650/bscmainnet.ts @@ -0,0 +1,384 @@ +import { parseUnits } from "ethers/lib/utils"; +import { ethers } from "hardhat"; +import { NETWORK_ADDRESSES } from "src/networkAddresses"; +import { ProposalType } from "src/types"; +import { makeProposal } from "src/utils"; + +const addressZero = ethers.constants.AddressZero; +const { RESILIENT_ORACLE, CHAINLINK_ORACLE, REDSTONE_ORACLE, BINANCE_ORACLE } = NETWORK_ADDRESSES.bscmainnet; + +export const BOUND_VALIDATOR = "0x6E332fF0bB52475304494E4AE5063c1051c7d735"; +export const PRICE_LOWER_BOUND = parseUnits("0.95", 18); +export const PRICE_UPPER_BOUND = parseUnits("1.05", 18); + +/* ============ New RedStone Oracle Feeds ============ */ + +export const NEW_REDSTONE_ORACLE_FEEDS = [ + { + NAME: "XVS", + ASSET: "0xcF6BB5389c92Bdda8a3747Ddb454cB7a64626C63", + FEED: "0xED2B1ca5D7E246f615c2291De309643D41FeC97e", + MAX_STALE_PERIOD: 21600, + }, + { + NAME: "LTC", + ASSET: "0x4338665CBB7B2485A8855A139b75D5e34AB0DB94", + FEED: "0x7A9b672fc20b5C89D6774514052b3e0899E5E263", + MAX_STALE_PERIOD: 21600, + }, + { + NAME: "BCH", + ASSET: "0x8fF795a6F4D97E7887C79beA79aba5cc76444aDf", + FEED: "0x98ECE0D516f891a35278E3186772fb1545b274eB", + MAX_STALE_PERIOD: 21600, + }, + { + NAME: "DOT", + ASSET: "0x7083609fCE4d1d8Dc0C979AAb8c869Ea2C873402", + FEED: "0xa75CC459De167De5BC21ccdecCdB85e86377B00f", + MAX_STALE_PERIOD: 21600, + }, + { + NAME: "LINK", + ASSET: "0xF8A0BF9cF54Bb92F17374d9e9A321E6a111a51bD", + FEED: "0x1b0FDa12D125B864756Bbf191ad20eaB10915a6F", + MAX_STALE_PERIOD: 21600, + }, + { + NAME: "DAI", + ASSET: "0x1AF3F329e8BE154074D8769D1FFa4eE058B1DBc3", + FEED: "0x0bE6929FD4ad87347e97A525DB6ac8E884FCDCeC", + MAX_STALE_PERIOD: 21600, + }, + { + NAME: "FIL", + ASSET: "0x0D8Ce2A99Bb6e3B7Db580eD848240e4a0F9aE153", + FEED: "0xe49df9f60D6B1Dd1D5fdfCAB29216f4a8582dA86", + MAX_STALE_PERIOD: 21600, + }, + { + NAME: "DOGE", + ASSET: "0xbA2aE424d960c26247Dd6c32edC70B295c744C43", + FEED: "0x6f57Ff507735BcD3d86af83aF77ABD10395b2904", + MAX_STALE_PERIOD: 21600, + }, + { + NAME: "AAVE", + ASSET: "0xfb6115445Bff7b52FeB98650C87f44907E58f802", + FEED: "0xe4630835eA31ABD4247e449A550Fb92c8a5a4E96", + MAX_STALE_PERIOD: 21600, + }, + { + NAME: "UNI", + ASSET: "0xBf5140A22578168FD562DCcF235E5D43A02ce9B1", + FEED: "0x22d47686b3AEC9068768f84EFD8Ce2637a347B0A", + MAX_STALE_PERIOD: 21600, + }, + { + NAME: "FDUSD", + ASSET: "0xc5f0f7b66764F6ec8C8Dff7BA683102295E16409", + FEED: "0x98DC6E90D4c2f212ed9d124aD2aFBa4833268633", + MAX_STALE_PERIOD: 21600, + }, + { + NAME: "TWT", + ASSET: "0x4B0F1812e5Df2A09796481Ff14017e6005508003", + FEED: "0xefe76D1C11F267d8735D240f53317F238D8C77c9", + MAX_STALE_PERIOD: 21600, + }, + { + NAME: "SOL", + ASSET: "0x570A5D26f7765Ecb712C0924E4De545B89fD43dF", + FEED: "0x90196F6D52fce394C79D1614265d36D3F0033Ccf", + MAX_STALE_PERIOD: 21600, + }, +]; + +/* ============ Old Oracle Configs (pre-VIP) ============ */ + +export const OLD_ORACLE_CONFIG = [ + { + NAME: "XVS", + ASSET: "0xcF6BB5389c92Bdda8a3747Ddb454cB7a64626C63", + MAIN: CHAINLINK_ORACLE, + PIVOT: BINANCE_ORACLE, + FALLBACK: addressZero, + CACHED: false, + }, + { + NAME: "LTC", + ASSET: "0x4338665CBB7B2485A8855A139b75D5e34AB0DB94", + MAIN: CHAINLINK_ORACLE, + PIVOT: BINANCE_ORACLE, + FALLBACK: addressZero, + CACHED: false, + }, + { + NAME: "BCH", + ASSET: "0x8fF795a6F4D97E7887C79beA79aba5cc76444aDf", + MAIN: CHAINLINK_ORACLE, + PIVOT: BINANCE_ORACLE, + FALLBACK: addressZero, + CACHED: false, + }, + { + NAME: "DOT", + ASSET: "0x7083609fCE4d1d8Dc0C979AAb8c869Ea2C873402", + MAIN: CHAINLINK_ORACLE, + PIVOT: BINANCE_ORACLE, + FALLBACK: addressZero, + CACHED: false, + }, + { + NAME: "LINK", + ASSET: "0xF8A0BF9cF54Bb92F17374d9e9A321E6a111a51bD", + MAIN: CHAINLINK_ORACLE, + PIVOT: BINANCE_ORACLE, + FALLBACK: addressZero, + CACHED: false, + }, + { + NAME: "DAI", + ASSET: "0x1AF3F329e8BE154074D8769D1FFa4eE058B1DBc3", + MAIN: CHAINLINK_ORACLE, + PIVOT: BINANCE_ORACLE, + FALLBACK: addressZero, + CACHED: false, + }, + { + NAME: "FIL", + ASSET: "0x0D8Ce2A99Bb6e3B7Db580eD848240e4a0F9aE153", + MAIN: CHAINLINK_ORACLE, + PIVOT: BINANCE_ORACLE, + FALLBACK: addressZero, + CACHED: false, + }, + { + NAME: "DOGE", + ASSET: "0xbA2aE424d960c26247Dd6c32edC70B295c744C43", + MAIN: CHAINLINK_ORACLE, + PIVOT: BINANCE_ORACLE, + FALLBACK: addressZero, + CACHED: false, + }, + { + NAME: "AAVE", + ASSET: "0xfb6115445Bff7b52FeB98650C87f44907E58f802", + MAIN: CHAINLINK_ORACLE, + PIVOT: BINANCE_ORACLE, + FALLBACK: addressZero, + CACHED: false, + }, + { + NAME: "UNI", + ASSET: "0xBf5140A22578168FD562DCcF235E5D43A02ce9B1", + MAIN: CHAINLINK_ORACLE, + PIVOT: BINANCE_ORACLE, + FALLBACK: addressZero, + CACHED: false, + }, + { + NAME: "FDUSD", + ASSET: "0xc5f0f7b66764F6ec8C8Dff7BA683102295E16409", + MAIN: CHAINLINK_ORACLE, + PIVOT: BINANCE_ORACLE, + FALLBACK: addressZero, + CACHED: false, + }, + { + NAME: "TWT", + ASSET: "0x4B0F1812e5Df2A09796481Ff14017e6005508003", + MAIN: BINANCE_ORACLE, + PIVOT: addressZero, + FALLBACK: addressZero, + CACHED: false, + }, + { + NAME: "SOL", + ASSET: "0x570A5D26f7765Ecb712C0924E4De545B89fD43dF", + MAIN: CHAINLINK_ORACLE, + PIVOT: BINANCE_ORACLE, + FALLBACK: addressZero, + CACHED: false, + }, +]; + +/* ============ New Oracle Configs (post-VIP) ============ */ + +// Standard assets: RedStone MAIN, Chainlink PIVOT, Binance FALLBACK +export const NEW_STANDARD_ORACLE_CONFIG = [ + { + NAME: "XVS", + ASSET: "0xcF6BB5389c92Bdda8a3747Ddb454cB7a64626C63", + MAIN: REDSTONE_ORACLE, + PIVOT: CHAINLINK_ORACLE, + FALLBACK: BINANCE_ORACLE, + CACHED: false, + }, + { + NAME: "LTC", + ASSET: "0x4338665CBB7B2485A8855A139b75D5e34AB0DB94", + MAIN: REDSTONE_ORACLE, + PIVOT: CHAINLINK_ORACLE, + FALLBACK: BINANCE_ORACLE, + CACHED: false, + }, + { + NAME: "BCH", + ASSET: "0x8fF795a6F4D97E7887C79beA79aba5cc76444aDf", + MAIN: REDSTONE_ORACLE, + PIVOT: CHAINLINK_ORACLE, + FALLBACK: BINANCE_ORACLE, + CACHED: false, + }, + { + NAME: "DOT", + ASSET: "0x7083609fCE4d1d8Dc0C979AAb8c869Ea2C873402", + MAIN: REDSTONE_ORACLE, + PIVOT: CHAINLINK_ORACLE, + FALLBACK: BINANCE_ORACLE, + CACHED: false, + }, + { + NAME: "LINK", + ASSET: "0xF8A0BF9cF54Bb92F17374d9e9A321E6a111a51bD", + MAIN: REDSTONE_ORACLE, + PIVOT: CHAINLINK_ORACLE, + FALLBACK: BINANCE_ORACLE, + CACHED: false, + }, + { + NAME: "DAI", + ASSET: "0x1AF3F329e8BE154074D8769D1FFa4eE058B1DBc3", + MAIN: REDSTONE_ORACLE, + PIVOT: CHAINLINK_ORACLE, + FALLBACK: BINANCE_ORACLE, + CACHED: false, + }, + { + NAME: "FIL", + ASSET: "0x0D8Ce2A99Bb6e3B7Db580eD848240e4a0F9aE153", + MAIN: REDSTONE_ORACLE, + PIVOT: CHAINLINK_ORACLE, + FALLBACK: BINANCE_ORACLE, + CACHED: false, + }, + { + NAME: "DOGE", + ASSET: "0xbA2aE424d960c26247Dd6c32edC70B295c744C43", + MAIN: REDSTONE_ORACLE, + PIVOT: CHAINLINK_ORACLE, + FALLBACK: BINANCE_ORACLE, + CACHED: false, + }, + { + NAME: "AAVE", + ASSET: "0xfb6115445Bff7b52FeB98650C87f44907E58f802", + MAIN: REDSTONE_ORACLE, + PIVOT: CHAINLINK_ORACLE, + FALLBACK: BINANCE_ORACLE, + CACHED: false, + }, + { + NAME: "UNI", + ASSET: "0xBf5140A22578168FD562DCcF235E5D43A02ce9B1", + MAIN: REDSTONE_ORACLE, + PIVOT: CHAINLINK_ORACLE, + FALLBACK: BINANCE_ORACLE, + CACHED: false, + }, + { + NAME: "FDUSD", + ASSET: "0xc5f0f7b66764F6ec8C8Dff7BA683102295E16409", + MAIN: REDSTONE_ORACLE, + PIVOT: CHAINLINK_ORACLE, + FALLBACK: BINANCE_ORACLE, + CACHED: false, + }, + { + NAME: "SOL", + ASSET: "0x570A5D26f7765Ecb712C0924E4De545B89fD43dF", + MAIN: REDSTONE_ORACLE, + PIVOT: CHAINLINK_ORACLE, + FALLBACK: BINANCE_ORACLE, + CACHED: false, + }, +]; + +// TWT: promote RedStone to MAIN, Binance as PIVOT +export const NEW_TWT_ORACLE_CONFIG = { + NAME: "TWT", + ASSET: "0x4B0F1812e5Df2A09796481Ff14017e6005508003", + MAIN: REDSTONE_ORACLE, + PIVOT: BINANCE_ORACLE, + FALLBACK: addressZero, + CACHED: false, +}; + +export const NEW_ORACLE_CONFIG = [...NEW_STANDARD_ORACLE_CONFIG, NEW_TWT_ORACLE_CONFIG]; + +export const vip650 = () => { + const meta = { + version: "v2", + title: "VIP-650 [BNB Chain] Two-Vendor OEV Integration - RedStone Oracle Feed Expansion", + description: `**Description:** + +This proposal continues the Two-Vendor OEV Integration Framework adopted in [VIP-586](https://app.venus.io/#/governance/proposal/586) by expanding RedStone oracle coverage across the BSC Core Pool. + +It registers new RedStone price feeds for 13 additional assets and updates their resilient oracle configurations to strengthen price reliability through multi-vendor redundancy. + +**Actions:** + +- **Register new RedStone oracle feeds** for: XVS, LTC, BCH, DOT, LINK, DAI, FIL, DOGE, AAVE, UNI, FDUSD, TWT, SOL +- **Update resilient oracle configurations:** + - For XVS, LTC, BCH, DOT, LINK, DAI, FIL, DOGE, AAVE, UNI, FDUSD, SOL: Set MAIN=RedStone, PIVOT=Chainlink, FALLBACK=Binance + - For TWT: Set MAIN=RedStone, PIVOT=Binance +- **Set BoundValidator config** for TWT (required since TWT previously had no PIVOT oracle)`, + 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( + [ + /* ============ RedStone Oracle: Register new feeds ============ */ + + ...NEW_REDSTONE_ORACLE_FEEDS.map(feedData => { + return { + target: REDSTONE_ORACLE, + signature: "setTokenConfig((address,address,uint256))", + params: [[feedData.ASSET, feedData.FEED, feedData.MAX_STALE_PERIOD]], + }; + }), + + /* ============ Resilient Oracle: Update token configs ============ */ + + ...NEW_ORACLE_CONFIG.map(oracleData => { + return { + target: RESILIENT_ORACLE, + signature: "setTokenConfig((address,address[3],bool[3],bool))", + params: [ + [ + oracleData.ASSET, + [oracleData.MAIN, oracleData.PIVOT, oracleData.FALLBACK], + [oracleData.MAIN != addressZero, oracleData.PIVOT != addressZero, oracleData.FALLBACK != addressZero], + oracleData.CACHED, + ], + ], + }; + }), + + /* ============ BoundValidator: Set validation config for TWT ============ */ + + { + target: BOUND_VALIDATOR, + signature: "setValidateConfig((address,uint256,uint256))", + params: [[NEW_TWT_ORACLE_CONFIG.ASSET, PRICE_UPPER_BOUND, PRICE_LOWER_BOUND]], + }, + ], + meta, + ProposalType.REGULAR, + ); +}; + +export default vip650; From e69f01daca170d133eb566574cd67d6b0de22bc8 Mon Sep 17 00:00:00 2001 From: Debugger022 Date: Fri, 13 Feb 2026 19:47:17 +0530 Subject: [PATCH 2/2] test: add BoundValidator checks to VIP-650 simulation Add pre-VIP check verifying TWT has no BoundValidator config and post-VIP ValidateConfigAdded event verification. --- simulations/vip-650/bscmainnet.ts | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/simulations/vip-650/bscmainnet.ts b/simulations/vip-650/bscmainnet.ts index 1aa6b7c1f..79edebaf0 100644 --- a/simulations/vip-650/bscmainnet.ts +++ b/simulations/vip-650/bscmainnet.ts @@ -60,6 +60,12 @@ forking(80964126, async () => { } }); + it("has no BoundValidator config for TWT", async () => { + const cfg = await boundValidator.validateConfigs(NEW_TWT_ORACLE_CONFIG.ASSET); + expect(cfg.upperBoundRatio).to.equal(0); + expect(cfg.lowerBoundRatio).to.equal(0); + }); + it("stores current resilient oracle prices for all assets", async () => { for (const { ASSET } of NEW_ORACLE_CONFIG) { const price = await resilientOracle.getPrice(ASSET); @@ -77,6 +83,7 @@ forking(80964126, async () => { ["TokenConfigAdded", "TokenConfigAdded"], [totalTokenConfigAdded, totalTokenConfigAdded], ); + await expectEvents(txResponse, [BOUND_VALIDATOR_ABI], ["ValidateConfigAdded"], [1]); }, });