diff --git a/simulations/vip-671/abi/AccessControlManager.json b/simulations/vip-671/abi/AccessControlManager.json new file mode 100644 index 000000000..6fe053ac3 --- /dev/null +++ b/simulations/vip-671/abi/AccessControlManager.json @@ -0,0 +1,23 @@ +[ + { + "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" + }, + { + "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" + } +] diff --git a/simulations/vip-671/abi/DeviationBoundedOracle.json b/simulations/vip-671/abi/DeviationBoundedOracle.json new file mode 100644 index 000000000..9984f9c99 --- /dev/null +++ b/simulations/vip-671/abi/DeviationBoundedOracle.json @@ -0,0 +1,1374 @@ +[ + { + "inputs": [ + { + "internalType": "contract ResilientOracleInterface", + "name": "_resilientOracle", + "type": "address" + }, + { + "internalType": "address", + "name": "nativeMarketAddress", + "type": "address" + }, + { + "internalType": "address", + "name": "vaiAddress", + "type": "address" + } + ], + "stateMutability": "nonpayable", + "type": "constructor" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "asset", + "type": "address" + }, + { + "internalType": "uint64", + "name": "lastProtectionTriggeredAt", + "type": "uint64" + }, + { + "internalType": "uint64", + "name": "cooldownPeriod", + "type": "uint64" + } + ], + "name": "CooldownNotElapsed", + "type": "error" + }, + { + "inputs": [], + "name": "InvalidArrayLength", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "uint8", + "name": "action", + "type": "uint8" + } + ], + "name": "InvalidKeeperAction", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "asset", + "type": "address" + }, + { + "internalType": "uint128", + "name": "newMax", + "type": "uint128" + }, + { + "internalType": "uint256", + "name": "currentSpot", + "type": "uint256" + } + ], + "name": "InvalidMaxPrice", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "asset", + "type": "address" + }, + { + "internalType": "uint128", + "name": "newMin", + "type": "uint128" + }, + { + "internalType": "uint256", + "name": "currentSpot", + "type": "uint256" + } + ], + "name": "InvalidMinPrice", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "resetThreshold", + "type": "uint256" + } + ], + "name": "InvalidResetThreshold", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "asset", + "type": "address" + } + ], + "name": "MarketAlreadyInitialized", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "asset", + "type": "address" + } + ], + "name": "MarketNotInitialized", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "price", + "type": "uint256" + } + ], + "name": "PriceExceedsUint128", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "asset", + "type": "address" + }, + { + "internalType": "uint256", + "name": "currentRangeRatio", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "resetThreshold", + "type": "uint256" + } + ], + "name": "PriceRangeNotConverged", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "asset", + "type": "address" + } + ], + "name": "ProtectedPriceActive", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "asset", + "type": "address" + } + ], + "name": "ProtectedPriceInactive", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "threshold", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "maximum", + "type": "uint256" + } + ], + "name": "ThresholdAboveMaximum", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "threshold", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "minimum", + "type": "uint256" + } + ], + "name": "ThresholdBelowMinimum", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "sender", + "type": "address" + }, + { + "internalType": "address", + "name": "calledContract", + "type": "address" + }, + { + "internalType": "string", + "name": "methodSignature", + "type": "string" + } + ], + "name": "Unauthorized", + "type": "error" + }, + { + "inputs": [], + "name": "VAINotAllowed", + "type": "error" + }, + { + "inputs": [], + "name": "ZeroAddressNotAllowed", + "type": "error" + }, + { + "inputs": [], + "name": "ZeroPriceNotAllowed", + "type": "error" + }, + { + "inputs": [], + "name": "ZeroValueNotAllowed", + "type": "error" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "asset", + "type": "address" + }, + { + "indexed": false, + "internalType": "bool", + "name": "whitelisted", + "type": "bool" + } + ], + "name": "BoundedPricingWhitelistUpdated", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "asset", + "type": "address" + }, + { + "indexed": false, + "internalType": "bool", + "name": "oldEnabled", + "type": "bool" + }, + { + "indexed": false, + "internalType": "bool", + "name": "newEnabled", + "type": "bool" + } + ], + "name": "CachingEnabledUpdated", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "asset", + "type": "address" + }, + { + "indexed": false, + "internalType": "uint64", + "name": "oldCooldown", + "type": "uint64" + }, + { + "indexed": false, + "internalType": "uint64", + "name": "newCooldown", + "type": "uint64" + } + ], + "name": "CooldownPeriodSet", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "uint8", + "name": "version", + "type": "uint8" + } + ], + "name": "Initialized", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "asset", + "type": "address" + }, + { + "indexed": false, + "internalType": "uint128", + "name": "oldMax", + "type": "uint128" + }, + { + "indexed": false, + "internalType": "uint128", + "name": "newMax", + "type": "uint128" + } + ], + "name": "MaxPriceUpdated", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "asset", + "type": "address" + }, + { + "indexed": false, + "internalType": "uint128", + "name": "oldMin", + "type": "uint128" + }, + { + "indexed": false, + "internalType": "uint128", + "name": "newMin", + "type": "uint128" + } + ], + "name": "MinPriceUpdated", + "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": "uint128", + "name": "minPrice", + "type": "uint128" + }, + { + "indexed": false, + "internalType": "uint128", + "name": "maxPrice", + "type": "uint128" + }, + { + "indexed": false, + "internalType": "uint64", + "name": "cooldownPeriod", + "type": "uint64" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "triggerThreshold", + "type": "uint256" + } + ], + "name": "ProtectionInitialized", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "asset", + "type": "address" + } + ], + "name": "ProtectionModeExited", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "asset", + "type": "address" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "spotPrice", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "uint128", + "name": "minPrice", + "type": "uint128" + }, + { + "indexed": false, + "internalType": "uint128", + "name": "maxPrice", + "type": "uint128" + } + ], + "name": "ProtectionTriggered", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "asset", + "type": "address" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "oldExitThreshold", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "newExitThreshold", + "type": "uint256" + } + ], + "name": "ResetThresholdSet", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "asset", + "type": "address" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "oldThreshold", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "newThreshold", + "type": "uint256" + } + ], + "name": "TriggerThresholdSet", + "type": "event" + }, + { + "inputs": [], + "name": "COLLATERAL_PRICE_CACHE_SLOT", + "outputs": [ + { + "internalType": "bytes32", + "name": "", + "type": "bytes32" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "DEBT_PRICE_CACHE_SLOT", + "outputs": [ + { + "internalType": "bytes32", + "name": "", + "type": "bytes32" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "KEEPER_DEADBAND", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "MAX_THRESHOLD", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "MIN_THRESHOLD", + "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": "RESILIENT_ORACLE", + "outputs": [ + { + "internalType": "contract ResilientOracleInterface", + "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": "uint256", + "name": "", + "type": "uint256" + } + ], + "name": "allAssets", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "name": "assetProtectionConfig", + "outputs": [ + { + "internalType": "uint128", + "name": "minPrice", + "type": "uint128" + }, + { + "internalType": "uint128", + "name": "maxPrice", + "type": "uint128" + }, + { + "internalType": "bool", + "name": "currentlyUsingProtectedPrice", + "type": "bool" + }, + { + "internalType": "bool", + "name": "isBoundedPricingEnabled", + "type": "bool" + }, + { + "internalType": "uint64", + "name": "lastProtectionTriggeredAt", + "type": "uint64" + }, + { + "internalType": "uint64", + "name": "cooldownPeriod", + "type": "uint64" + }, + { + "internalType": "address", + "name": "asset", + "type": "address" + }, + { + "internalType": "uint128", + "name": "triggerThreshold", + "type": "uint128" + }, + { + "internalType": "uint128", + "name": "resetThreshold", + "type": "uint128" + }, + { + "internalType": "bool", + "name": "cachingEnabled", + "type": "bool" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "asset", + "type": "address" + } + ], + "name": "canExitProtection", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address[]", + "name": "assets", + "type": "address[]" + }, + { + "internalType": "uint128[]", + "name": "proposedMins", + "type": "uint128[]" + }, + { + "internalType": "uint128[]", + "name": "proposedMaxs", + "type": "uint128[]" + } + ], + "name": "checkAndGetWindowDrift", + "outputs": [ + { + "internalType": "bool[]", + "name": "needsMinUpdate", + "type": "bool[]" + }, + { + "internalType": "bool[]", + "name": "needsMaxUpdate", + "type": "bool[]" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "asset", + "type": "address" + } + ], + "name": "currentlyUsingProtectedPrice", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "asset", + "type": "address" + } + ], + "name": "exitProtectionMode", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "getAllBoundedPricingEnabledAssets", + "outputs": [ + { + "internalType": "address[]", + "name": "", + "type": "address[]" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "vToken", + "type": "address" + } + ], + "name": "getBoundedCollateralPrice", + "outputs": [ + { + "internalType": "uint256", + "name": "collateralPrice", + "type": "uint256" + } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "vToken", + "type": "address" + } + ], + "name": "getBoundedCollateralPriceView", + "outputs": [ + { + "internalType": "uint256", + "name": "collateralPrice", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "vToken", + "type": "address" + } + ], + "name": "getBoundedDebtPrice", + "outputs": [ + { + "internalType": "uint256", + "name": "debtPrice", + "type": "uint256" + } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "vToken", + "type": "address" + } + ], + "name": "getBoundedDebtPriceView", + "outputs": [ + { + "internalType": "uint256", + "name": "debtPrice", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "vToken", + "type": "address" + } + ], + "name": "getBoundedPrices", + "outputs": [ + { + "internalType": "uint256", + "name": "collateralPrice", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "debtPrice", + "type": "uint256" + } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "vToken", + "type": "address" + } + ], + "name": "getBoundedPricesView", + "outputs": [ + { + "internalType": "uint256", + "name": "collateralPrice", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "debtPrice", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "getInitializedAssets", + "outputs": [ + { + "internalType": "address[]", + "name": "", + "type": "address[]" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "accessControlManager_", + "type": "address" + } + ], + "name": "initialize", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "asset", + "type": "address" + } + ], + "name": "isBoundedPricingEnabled", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "view", + "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": "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": "bool", + "name": "enabled", + "type": "bool" + } + ], + "name": "setAssetBoundedPricingEnabled", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "asset", + "type": "address" + }, + { + "internalType": "bool", + "name": "enabled", + "type": "bool" + } + ], + "name": "setCachingEnabled", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "asset", + "type": "address" + }, + { + "internalType": "uint64", + "name": "newCooldown", + "type": "uint64" + } + ], + "name": "setCooldownPeriod", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "asset", + "type": "address" + }, + { + "internalType": "uint256", + "name": "newTriggerThreshold", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "newResetThreshold", + "type": "uint256" + } + ], + "name": "setThresholds", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "components": [ + { + "internalType": "address", + "name": "asset", + "type": "address" + }, + { + "internalType": "uint64", + "name": "cooldownPeriod", + "type": "uint64" + }, + { + "internalType": "uint256", + "name": "triggerThreshold", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "resetThreshold", + "type": "uint256" + }, + { + "internalType": "bool", + "name": "enableBoundedPricing", + "type": "bool" + }, + { + "internalType": "bool", + "name": "enableCaching", + "type": "bool" + } + ], + "internalType": "struct IDeviationBoundedOracle.TokenConfigInput", + "name": "tokenConfig_", + "type": "tuple" + } + ], + "name": "setTokenConfig", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "components": [ + { + "internalType": "address", + "name": "asset", + "type": "address" + }, + { + "internalType": "uint64", + "name": "cooldownPeriod", + "type": "uint64" + }, + { + "internalType": "uint256", + "name": "triggerThreshold", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "resetThreshold", + "type": "uint256" + }, + { + "internalType": "bool", + "name": "enableBoundedPricing", + "type": "bool" + }, + { + "internalType": "bool", + "name": "enableCaching", + "type": "bool" + } + ], + "internalType": "struct IDeviationBoundedOracle.TokenConfigInput[]", + "name": "tokenConfigs_", + "type": "tuple[]" + } + ], + "name": "setTokenConfigs", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "components": [ + { + "internalType": "address", + "name": "asset", + "type": "address" + }, + { + "internalType": "enum IDeviationBoundedOracle.KeeperAction", + "name": "action", + "type": "uint8" + }, + { + "internalType": "uint256", + "name": "value", + "type": "uint256" + } + ], + "internalType": "struct IDeviationBoundedOracle.KeeperActionItem[]", + "name": "actions", + "type": "tuple[]" + } + ], + "name": "syncPriceBoundsAndProtections", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "newOwner", + "type": "address" + } + ], + "name": "transferOwnership", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "asset", + "type": "address" + }, + { + "internalType": "uint128", + "name": "newMax", + "type": "uint128" + } + ], + "name": "updateMaxPrice", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "asset", + "type": "address" + }, + { + "internalType": "uint128", + "name": "newMin", + "type": "uint128" + } + ], + "name": "updateMinPrice", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "vToken", + "type": "address" + } + ], + "name": "updateProtectionState", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "vai", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + } +] diff --git a/simulations/vip-671/abi/ERC20.json b/simulations/vip-671/abi/ERC20.json new file mode 100644 index 000000000..3a509c9c4 --- /dev/null +++ b/simulations/vip-671/abi/ERC20.json @@ -0,0 +1,134 @@ +[ + { + "inputs": [ + { "internalType": "string", "name": "name_", "type": "string" }, + { "internalType": "string", "name": "symbol_", "type": "string" }, + { "internalType": "uint8", "name": "decimals_", "type": "uint8" } + ], + "stateMutability": "nonpayable", + "type": "constructor" + }, + { + "anonymous": false, + "inputs": [ + { "indexed": true, "internalType": "address", "name": "owner", "type": "address" }, + { "indexed": true, "internalType": "address", "name": "spender", "type": "address" }, + { "indexed": false, "internalType": "uint256", "name": "value", "type": "uint256" } + ], + "name": "Approval", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { "indexed": true, "internalType": "address", "name": "from", "type": "address" }, + { "indexed": true, "internalType": "address", "name": "to", "type": "address" }, + { "indexed": false, "internalType": "uint256", "name": "value", "type": "uint256" } + ], + "name": "Transfer", + "type": "event" + }, + { + "inputs": [ + { "internalType": "address", "name": "owner", "type": "address" }, + { "internalType": "address", "name": "spender", "type": "address" } + ], + "name": "allowance", + "outputs": [{ "internalType": "uint256", "name": "", "type": "uint256" }], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { "internalType": "address", "name": "spender", "type": "address" }, + { "internalType": "uint256", "name": "amount", "type": "uint256" } + ], + "name": "approve", + "outputs": [{ "internalType": "bool", "name": "", "type": "bool" }], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [{ "internalType": "address", "name": "account", "type": "address" }], + "name": "balanceOf", + "outputs": [{ "internalType": "uint256", "name": "", "type": "uint256" }], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "decimals", + "outputs": [{ "internalType": "uint8", "name": "", "type": "uint8" }], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { "internalType": "address", "name": "spender", "type": "address" }, + { "internalType": "uint256", "name": "subtractedValue", "type": "uint256" } + ], + "name": "decreaseAllowance", + "outputs": [{ "internalType": "bool", "name": "", "type": "bool" }], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [{ "internalType": "uint256", "name": "amount", "type": "uint256" }], + "name": "faucet", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { "internalType": "address", "name": "spender", "type": "address" }, + { "internalType": "uint256", "name": "addedValue", "type": "uint256" } + ], + "name": "increaseAllowance", + "outputs": [{ "internalType": "bool", "name": "", "type": "bool" }], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "name", + "outputs": [{ "internalType": "string", "name": "", "type": "string" }], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "symbol", + "outputs": [{ "internalType": "string", "name": "", "type": "string" }], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "totalSupply", + "outputs": [{ "internalType": "uint256", "name": "", "type": "uint256" }], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { "internalType": "address", "name": "to", "type": "address" }, + { "internalType": "uint256", "name": "amount", "type": "uint256" } + ], + "name": "transfer", + "outputs": [{ "internalType": "bool", "name": "", "type": "bool" }], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { "internalType": "address", "name": "from", "type": "address" }, + { "internalType": "address", "name": "to", "type": "address" }, + { "internalType": "uint256", "name": "amount", "type": "uint256" } + ], + "name": "transferFrom", + "outputs": [{ "internalType": "bool", "name": "", "type": "bool" }], + "stateMutability": "nonpayable", + "type": "function" + } +] diff --git a/simulations/vip-671/abi/ProtocolShareReserve.json b/simulations/vip-671/abi/ProtocolShareReserve.json new file mode 100644 index 000000000..1ca7eee00 --- /dev/null +++ b/simulations/vip-671/abi/ProtocolShareReserve.json @@ -0,0 +1,49 @@ +[ + { "inputs": [], "name": "owner", "outputs": [{ "type": "address" }], "stateMutability": "view", "type": "function" }, + { + "inputs": [], + "name": "poolRegistry", + "outputs": [{ "type": "address" }], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [{ "type": "address" }], + "name": "addPoolRegistry", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "getPoolRegistries", + "outputs": [{ "type": "address[]" }], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [{ "type": "address" }, { "type": "address" }], + "name": "isMarketRegistered", + "outputs": [{ "type": "bool" }], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "totalDistributions", + "outputs": [{ "type": "uint256" }], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [{ "type": "uint256" }], + "name": "distributionTargets", + "outputs": [ + { "name": "schema", "type": "uint8" }, + { "name": "percentage", "type": "uint16" }, + { "name": "destination", "type": "address" } + ], + "stateMutability": "view", + "type": "function" + } +] diff --git a/simulations/vip-671/abi/ResilientOracle.json b/simulations/vip-671/abi/ResilientOracle.json new file mode 100644 index 000000000..373eb14e9 --- /dev/null +++ b/simulations/vip-671/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-671/abi/SpokeComptroller.json b/simulations/vip-671/abi/SpokeComptroller.json new file mode 100644 index 000000000..e47b255b7 --- /dev/null +++ b/simulations/vip-671/abi/SpokeComptroller.json @@ -0,0 +1,2516 @@ +[ + { + "inputs": [ + { + "internalType": "address", + "name": "poolRegistry_", + "type": "address" + } + ], + "stateMutability": "nonpayable", + "type": "constructor" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "market", + "type": "address" + }, + { + "internalType": "enum Action", + "name": "action", + "type": "uint8" + } + ], + "name": "ActionPaused", + "type": "error" + }, + { + "inputs": [], + "name": "BorrowActionNotPaused", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "market", + "type": "address" + }, + { + "internalType": "uint256", + "name": "cap", + "type": "uint256" + } + ], + "name": "BorrowCapExceeded", + "type": "error" + }, + { + "inputs": [], + "name": "BorrowCapIsNotZero", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "borrows", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "maxClearableDebt", + "type": "uint256" + } + ], + "name": "CollateralCoversDebt", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "expectedLessThanOrEqualTo", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "actual", + "type": "uint256" + } + ], + "name": "CollateralExceedsThreshold", + "type": "error" + }, + { + "inputs": [], + "name": "CollateralFactorIsNotZero", + "type": "error" + }, + { + "inputs": [], + "name": "ComptrollerMismatch", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "borrows", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "maxClearableDebt", + "type": "uint256" + } + ], + "name": "DebtExceedsClearableAmount", + "type": "error" + }, + { + "inputs": [], + "name": "DelegationStatusUnchanged", + "type": "error" + }, + { + "inputs": [], + "name": "EnterMarketActionNotPaused", + "type": "error" + }, + { + "inputs": [], + "name": "ExitMarketActionNotPaused", + "type": "error" + }, + { + "inputs": [], + "name": "InsufficientLiquidity", + "type": "error" + }, + { + "inputs": [], + "name": "InsufficientShortfall", + "type": "error" + }, + { + "inputs": [], + "name": "InvalidArrayLength", + "type": "error" + }, + { + "inputs": [], + "name": "InvalidCloseFactor", + "type": "error" + }, + { + "inputs": [], + "name": "InvalidCollateralFactor", + "type": "error" + }, + { + "inputs": [], + "name": "InvalidLiquidationIncentive", + "type": "error" + }, + { + "inputs": [], + "name": "InvalidLiquidationThreshold", + "type": "error" + }, + { + "inputs": [], + "name": "InvalidVToken", + "type": "error" + }, + { + "inputs": [], + "name": "LiquidateActionNotPaused", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "liquidator", + "type": "address" + } + ], + "name": "LiquidationNotAllowed", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "market", + "type": "address" + } + ], + "name": "MarketAlreadyListed", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "vToken", + "type": "address" + }, + { + "internalType": "address", + "name": "user", + "type": "address" + } + ], + "name": "MarketNotCollateral", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "market", + "type": "address" + } + ], + "name": "MarketNotListed", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "loopsLimit", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "requiredLoops", + "type": "uint256" + } + ], + "name": "MaxLoopsLimitExceeded", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "expectedGreaterThan", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "actual", + "type": "uint256" + } + ], + "name": "MinimalCollateralViolated", + "type": "error" + }, + { + "inputs": [], + "name": "MintActionNotPaused", + "type": "error" + }, + { + "inputs": [], + "name": "NonzeroBorrowBalance", + "type": "error" + }, + { + "inputs": [], + "name": "NonzeroBorrowBalanceAfterLiquidation", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "vToken", + "type": "address" + } + ], + "name": "PriceError", + "type": "error" + }, + { + "inputs": [], + "name": "RedeemActionNotPaused", + "type": "error" + }, + { + "inputs": [], + "name": "RepayActionNotPaused", + "type": "error" + }, + { + "inputs": [], + "name": "RewardsDistributorAlreadyExists", + "type": "error" + }, + { + "inputs": [], + "name": "SeizeActionNotPaused", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "vToken", + "type": "address" + }, + { + "internalType": "address", + "name": "user", + "type": "address" + } + ], + "name": "SnapshotError", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "market", + "type": "address" + }, + { + "internalType": "uint256", + "name": "cap", + "type": "uint256" + } + ], + "name": "SupplyCapExceeded", + "type": "error" + }, + { + "inputs": [], + "name": "SupplyCapIsNotZero", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "market", + "type": "address" + }, + { + "internalType": "address", + "name": "supplier", + "type": "address" + } + ], + "name": "SupplyNotAllowed", + "type": "error" + }, + { + "inputs": [], + "name": "TooMuchRepay", + "type": "error" + }, + { + "inputs": [], + "name": "TransferActionNotPaused", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "sender", + "type": "address" + }, + { + "internalType": "address", + "name": "calledContract", + "type": "address" + }, + { + "internalType": "string", + "name": "methodSignature", + "type": "string" + } + ], + "name": "Unauthorized", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "expectedSender", + "type": "address" + }, + { + "internalType": "address", + "name": "actualSender", + "type": "address" + } + ], + "name": "UnexpectedSender", + "type": "error" + }, + { + "inputs": [], + "name": "ZeroAddressNotAllowed", + "type": "error" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "contract VToken", + "name": "vToken", + "type": "address" + }, + { + "indexed": false, + "internalType": "enum Action", + "name": "action", + "type": "uint8" + }, + { + "indexed": false, + "internalType": "bool", + "name": "pauseState", + "type": "bool" + } + ], + "name": "ActionPausedMarket", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "liquidator", + "type": "address" + }, + { + "indexed": false, + "internalType": "bool", + "name": "allowed", + "type": "bool" + } + ], + "name": "AllowedLiquidatorUpdated", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "vToken", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "supplier", + "type": "address" + }, + { + "indexed": false, + "internalType": "bool", + "name": "allowed", + "type": "bool" + } + ], + "name": "AllowedSupplierUpdated", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "approver", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "delegate", + "type": "address" + }, + { + "indexed": false, + "internalType": "bool", + "name": "approved", + "type": "bool" + } + ], + "name": "DelegateUpdated", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "uint8", + "name": "version", + "type": "uint8" + } + ], + "name": "Initialized", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "vToken", + "type": "address" + }, + { + "indexed": false, + "internalType": "bool", + "name": "enable", + "type": "bool" + } + ], + "name": "IsForcedLiquidationEnabledUpdated", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "bool", + "name": "enabled", + "type": "bool" + } + ], + "name": "LiquidationAllowlistEnabledUpdated", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "contract VToken", + "name": "vToken", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "account", + "type": "address" + } + ], + "name": "MarketEntered", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "contract VToken", + "name": "vToken", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "account", + "type": "address" + } + ], + "name": "MarketExited", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "contract VToken", + "name": "vToken", + "type": "address" + } + ], + "name": "MarketSupported", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "vToken", + "type": "address" + } + ], + "name": "MarketUnlisted", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "uint256", + "name": "oldMaxLoopsLimit", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "newmaxLoopsLimit", + "type": "uint256" + } + ], + "name": "MaxLoopsLimitUpdated", + "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": "contract VToken", + "name": "vToken", + "type": "address" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "newBorrowCap", + "type": "uint256" + } + ], + "name": "NewBorrowCap", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "uint256", + "name": "oldCloseFactorMantissa", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "newCloseFactorMantissa", + "type": "uint256" + } + ], + "name": "NewCloseFactor", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "contract VToken", + "name": "vToken", + "type": "address" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "oldCollateralFactorMantissa", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "newCollateralFactorMantissa", + "type": "uint256" + } + ], + "name": "NewCollateralFactor", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "contract IDeviationBoundedOracle", + "name": "oldBoundedOracle", + "type": "address" + }, + { + "indexed": false, + "internalType": "contract IDeviationBoundedOracle", + "name": "newBoundedOracle", + "type": "address" + } + ], + "name": "NewDeviationBoundedOracle", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "uint256", + "name": "oldLiquidationIncentiveMantissa", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "newLiquidationIncentiveMantissa", + "type": "uint256" + } + ], + "name": "NewLiquidationIncentive", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "contract VToken", + "name": "vToken", + "type": "address" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "oldLiquidationThresholdMantissa", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "newLiquidationThresholdMantissa", + "type": "uint256" + } + ], + "name": "NewLiquidationThreshold", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "vToken", + "type": "address" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "oldLiquidationIncentiveMantissa", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "newLiquidationIncentiveMantissa", + "type": "uint256" + } + ], + "name": "NewMarketLiquidationIncentive", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "uint256", + "name": "oldMinLiquidatableCollateral", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "newMinLiquidatableCollateral", + "type": "uint256" + } + ], + "name": "NewMinLiquidatableCollateral", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "contract ResilientOracleInterface", + "name": "oldPriceOracle", + "type": "address" + }, + { + "indexed": false, + "internalType": "contract ResilientOracleInterface", + "name": "newPriceOracle", + "type": "address" + } + ], + "name": "NewPriceOracle", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "rewardsDistributor", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "rewardToken", + "type": "address" + } + ], + "name": "NewRewardsDistributor", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "contract VToken", + "name": "vToken", + "type": "address" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "newSupplyCap", + "type": "uint256" + } + ], + "name": "NewSupplyCap", + "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": "vToken", + "type": "address" + }, + { + "indexed": false, + "internalType": "bool", + "name": "enabled", + "type": "bool" + } + ], + "name": "SupplyAllowlistEnabledUpdated", + "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": "", + "type": "address" + }, + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "name": "accountAssets", + "outputs": [ + { + "internalType": "contract VToken", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "market", + "type": "address" + }, + { + "internalType": "enum Action", + "name": "action", + "type": "uint8" + } + ], + "name": "actionPaused", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "contract RewardsDistributor", + "name": "_rewardsDistributor", + "type": "address" + } + ], + "name": "addRewardsDistributor", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "name": "allMarkets", + "outputs": [ + { + "internalType": "contract VToken", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + }, + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "name": "approvedDelegates", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "name": "borrowCaps", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + }, + { + "internalType": "address", + "name": "", + "type": "address" + }, + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "name": "borrowVerify", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "account", + "type": "address" + }, + { + "internalType": "contract VToken", + "name": "vToken", + "type": "address" + } + ], + "name": "checkMembership", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "closeFactorMantissa", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "deviationBoundedOracle", + "outputs": [ + { + "internalType": "contract IDeviationBoundedOracle", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "vToken", + "type": "address" + } + ], + "name": "effectiveLiquidationIncentive", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "vToken", + "type": "address" + }, + { + "internalType": "address", + "name": "account", + "type": "address" + } + ], + "name": "enterMarketBehalf", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address[]", + "name": "vTokens", + "type": "address[]" + } + ], + "name": "enterMarkets", + "outputs": [ + { + "internalType": "uint256[]", + "name": "", + "type": "uint256[]" + } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "vTokenAddress", + "type": "address" + } + ], + "name": "exitMarket", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "account", + "type": "address" + } + ], + "name": "getAccountLiquidity", + "outputs": [ + { + "internalType": "uint256", + "name": "error", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "liquidity", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "shortfall", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "getAllMarkets", + "outputs": [ + { + "internalType": "contract VToken[]", + "name": "", + "type": "address[]" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "account", + "type": "address" + } + ], + "name": "getAssetsIn", + "outputs": [ + { + "internalType": "contract VToken[]", + "name": "", + "type": "address[]" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "account", + "type": "address" + } + ], + "name": "getBorrowingPower", + "outputs": [ + { + "internalType": "uint256", + "name": "error", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "liquidity", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "shortfall", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "account", + "type": "address" + }, + { + "internalType": "address", + "name": "vTokenModify", + "type": "address" + }, + { + "internalType": "uint256", + "name": "redeemTokens", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "borrowAmount", + "type": "uint256" + } + ], + "name": "getHypotheticalAccountLiquidity", + "outputs": [ + { + "internalType": "uint256", + "name": "error", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "liquidity", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "shortfall", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "getRewardDistributors", + "outputs": [ + { + "internalType": "contract RewardsDistributor[]", + "name": "", + "type": "address[]" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "vToken", + "type": "address" + } + ], + "name": "getRewardsByMarket", + "outputs": [ + { + "components": [ + { + "internalType": "address", + "name": "rewardToken", + "type": "address" + }, + { + "internalType": "uint256", + "name": "supplySpeed", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "borrowSpeed", + "type": "uint256" + } + ], + "internalType": "struct SpokeComptrollerStorage.RewardSpeeds[]", + "name": "rewardSpeeds", + "type": "tuple[]" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "user", + "type": "address" + } + ], + "name": "healAccount", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "loopLimit", + "type": "uint256" + }, + { + "internalType": "address", + "name": "accessControlManager", + "type": "address" + } + ], + "name": "initialize", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "name": "isAllowedLiquidator", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + }, + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "name": "isAllowedSupplier", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "isComptroller", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "pure", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "name": "isForcedLiquidationEnabled", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "isLiquidationAllowlistEnabled", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "contract VToken", + "name": "vToken", + "type": "address" + } + ], + "name": "isMarketListed", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "name": "isSupplyAllowlistEnabled", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "borrower", + "type": "address" + }, + { + "components": [ + { + "internalType": "contract VToken", + "name": "vTokenCollateral", + "type": "address" + }, + { + "internalType": "contract VToken", + "name": "vTokenBorrowed", + "type": "address" + }, + { + "internalType": "uint256", + "name": "repayAmount", + "type": "uint256" + } + ], + "internalType": "struct SpokeComptrollerStorage.LiquidationOrder[]", + "name": "orders", + "type": "tuple[]" + } + ], + "name": "liquidateAccount", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + }, + { + "internalType": "address", + "name": "", + "type": "address" + }, + { + "internalType": "address", + "name": "", + "type": "address" + }, + { + "internalType": "address", + "name": "", + "type": "address" + }, + { + "internalType": "uint256", + "name": "", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "name": "liquidateBorrowVerify", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "vTokenBorrowed", + "type": "address" + }, + { + "internalType": "address", + "name": "vTokenCollateral", + "type": "address" + }, + { + "internalType": "uint256", + "name": "actualRepayAmount", + "type": "uint256" + } + ], + "name": "liquidateCalculateSeizeTokens", + "outputs": [ + { + "internalType": "uint256", + "name": "error", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "tokensToSeize", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "liquidationIncentiveMantissa", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "name": "liquidationIncentives", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "name": "markets", + "outputs": [ + { + "internalType": "bool", + "name": "isListed", + "type": "bool" + }, + { + "internalType": "uint256", + "name": "collateralFactorMantissa", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "liquidationThresholdMantissa", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "maxLoopsLimit", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "minLiquidatableCollateral", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + }, + { + "internalType": "address", + "name": "", + "type": "address" + }, + { + "internalType": "uint256", + "name": "", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "name": "mintVerify", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "oracle", + "outputs": [ + { + "internalType": "contract ResilientOracleInterface", + "name": "", + "type": "address" + } + ], + "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": "poolRegistry", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "vToken", + "type": "address" + }, + { + "internalType": "address", + "name": "borrower", + "type": "address" + }, + { + "internalType": "uint256", + "name": "borrowAmount", + "type": "uint256" + } + ], + "name": "preBorrowHook", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "vTokenBorrowed", + "type": "address" + }, + { + "internalType": "address", + "name": "vTokenCollateral", + "type": "address" + }, + { + "internalType": "address", + "name": "borrower", + "type": "address" + }, + { + "internalType": "uint256", + "name": "repayAmount", + "type": "uint256" + }, + { + "internalType": "bool", + "name": "skipLiquidityCheck", + "type": "bool" + } + ], + "name": "preLiquidateHook", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "vToken", + "type": "address" + }, + { + "internalType": "address", + "name": "minter", + "type": "address" + }, + { + "internalType": "uint256", + "name": "mintAmount", + "type": "uint256" + } + ], + "name": "preMintHook", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "vToken", + "type": "address" + }, + { + "internalType": "address", + "name": "redeemer", + "type": "address" + }, + { + "internalType": "uint256", + "name": "redeemTokens", + "type": "uint256" + } + ], + "name": "preRedeemHook", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "vToken", + "type": "address" + }, + { + "internalType": "address", + "name": "borrower", + "type": "address" + } + ], + "name": "preRepayHook", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "vTokenCollateral", + "type": "address" + }, + { + "internalType": "address", + "name": "seizerContract", + "type": "address" + }, + { + "internalType": "address", + "name": "liquidator", + "type": "address" + }, + { + "internalType": "address", + "name": "borrower", + "type": "address" + } + ], + "name": "preSeizeHook", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "vToken", + "type": "address" + }, + { + "internalType": "address", + "name": "src", + "type": "address" + }, + { + "internalType": "address", + "name": "dst", + "type": "address" + }, + { + "internalType": "uint256", + "name": "transferTokens", + "type": "uint256" + } + ], + "name": "preTransferHook", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + }, + { + "internalType": "address", + "name": "", + "type": "address" + }, + { + "internalType": "uint256", + "name": "", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "name": "redeemVerify", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "renounceOwnership", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + }, + { + "internalType": "address", + "name": "", + "type": "address" + }, + { + "internalType": "address", + "name": "", + "type": "address" + }, + { + "internalType": "uint256", + "name": "", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "name": "repayBorrowVerify", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + }, + { + "internalType": "address", + "name": "", + "type": "address" + }, + { + "internalType": "address", + "name": "", + "type": "address" + }, + { + "internalType": "address", + "name": "", + "type": "address" + }, + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "name": "seizeVerify", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "accessControlManager_", + "type": "address" + } + ], + "name": "setAccessControlManager", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "contract VToken[]", + "name": "marketsList", + "type": "address[]" + }, + { + "internalType": "enum Action[]", + "name": "actionsList", + "type": "uint8[]" + }, + { + "internalType": "bool", + "name": "paused", + "type": "bool" + } + ], + "name": "setActionsPaused", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "liquidator", + "type": "address" + }, + { + "internalType": "bool", + "name": "allowed", + "type": "bool" + } + ], + "name": "setAllowedLiquidator", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "vToken", + "type": "address" + }, + { + "internalType": "address", + "name": "supplier", + "type": "address" + }, + { + "internalType": "bool", + "name": "allowed", + "type": "bool" + } + ], + "name": "setAllowedSupplier", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "newCloseFactorMantissa", + "type": "uint256" + } + ], + "name": "setCloseFactor", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "contract VToken", + "name": "vToken", + "type": "address" + }, + { + "internalType": "uint256", + "name": "newCollateralFactorMantissa", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "newLiquidationThresholdMantissa", + "type": "uint256" + } + ], + "name": "setCollateralFactor", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "contract IDeviationBoundedOracle", + "name": "newBoundedOracle", + "type": "address" + } + ], + "name": "setDeviationBoundedOracle", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "vTokenBorrowed", + "type": "address" + }, + { + "internalType": "bool", + "name": "enable", + "type": "bool" + } + ], + "name": "setForcedLiquidation", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "bool", + "name": "enabled", + "type": "bool" + } + ], + "name": "setLiquidationAllowlistEnabled", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "newLiquidationIncentiveMantissa", + "type": "uint256" + } + ], + "name": "setLiquidationIncentive", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "contract VToken[]", + "name": "vTokens", + "type": "address[]" + }, + { + "internalType": "uint256[]", + "name": "newBorrowCaps", + "type": "uint256[]" + } + ], + "name": "setMarketBorrowCaps", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "vToken", + "type": "address" + }, + { + "internalType": "uint256", + "name": "newLiquidationIncentiveMantissa", + "type": "uint256" + } + ], + "name": "setMarketLiquidationIncentive", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "contract VToken[]", + "name": "vTokens", + "type": "address[]" + }, + { + "internalType": "uint256[]", + "name": "newSupplyCaps", + "type": "uint256[]" + } + ], + "name": "setMarketSupplyCaps", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "limit", + "type": "uint256" + } + ], + "name": "setMaxLoopsLimit", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "newMinLiquidatableCollateral", + "type": "uint256" + } + ], + "name": "setMinLiquidatableCollateral", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "contract ResilientOracleInterface", + "name": "newOracle", + "type": "address" + } + ], + "name": "setPriceOracle", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "vToken", + "type": "address" + }, + { + "internalType": "bool", + "name": "enabled", + "type": "bool" + } + ], + "name": "setSupplyAllowlistEnabled", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "name": "supplyCaps", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "contract VToken", + "name": "vToken", + "type": "address" + } + ], + "name": "supportMarket", + "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" + }, + { + "internalType": "address", + "name": "", + "type": "address" + }, + { + "internalType": "address", + "name": "", + "type": "address" + }, + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "name": "transferVerify", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "market", + "type": "address" + } + ], + "name": "unlistMarket", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "delegate", + "type": "address" + }, + { + "internalType": "bool", + "name": "approved", + "type": "bool" + } + ], + "name": "updateDelegate", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "account", + "type": "address" + } + ], + "name": "updatePrices", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + } +] diff --git a/simulations/vip-671/abi/SpokePoolRegistry.json b/simulations/vip-671/abi/SpokePoolRegistry.json new file mode 100644 index 000000000..b637988ae --- /dev/null +++ b/simulations/vip-671/abi/SpokePoolRegistry.json @@ -0,0 +1,680 @@ +[ + { + "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" + }, + { + "inputs": [], + "name": "ZeroAddressNotAllowed", + "type": "error" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "uint8", + "name": "version", + "type": "uint8" + } + ], + "name": "Initialized", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "comptroller", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "vTokenAddress", + "type": "address" + } + ], + "name": "MarketAdded", + "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": "comptroller", + "type": "address" + }, + { + "components": [ + { + "internalType": "string", + "name": "category", + "type": "string" + }, + { + "internalType": "string", + "name": "logoURL", + "type": "string" + }, + { + "internalType": "string", + "name": "description", + "type": "string" + } + ], + "indexed": false, + "internalType": "struct PoolRegistryInterface.VenusPoolMetaData", + "name": "oldMetadata", + "type": "tuple" + }, + { + "components": [ + { + "internalType": "string", + "name": "category", + "type": "string" + }, + { + "internalType": "string", + "name": "logoURL", + "type": "string" + }, + { + "internalType": "string", + "name": "description", + "type": "string" + } + ], + "indexed": false, + "internalType": "struct PoolRegistryInterface.VenusPoolMetaData", + "name": "newMetadata", + "type": "tuple" + } + ], + "name": "PoolMetadataUpdated", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "comptroller", + "type": "address" + }, + { + "indexed": false, + "internalType": "string", + "name": "oldName", + "type": "string" + }, + { + "indexed": false, + "internalType": "string", + "name": "newName", + "type": "string" + } + ], + "name": "PoolNameSet", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "comptroller", + "type": "address" + }, + { + "components": [ + { + "internalType": "string", + "name": "name", + "type": "string" + }, + { + "internalType": "address", + "name": "creator", + "type": "address" + }, + { + "internalType": "address", + "name": "comptroller", + "type": "address" + }, + { + "internalType": "uint256", + "name": "blockPosted", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "timestampPosted", + "type": "uint256" + } + ], + "indexed": false, + "internalType": "struct PoolRegistryInterface.VenusPool", + "name": "pool", + "type": "tuple" + } + ], + "name": "PoolRegistered", + "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": [ + { + "components": [ + { + "internalType": "contract VToken", + "name": "vToken", + "type": "address" + }, + { + "internalType": "uint256", + "name": "collateralFactor", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "liquidationThreshold", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "initialSupply", + "type": "uint256" + }, + { + "internalType": "address", + "name": "vTokenReceiver", + "type": "address" + }, + { + "internalType": "uint256", + "name": "supplyCap", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "borrowCap", + "type": "uint256" + } + ], + "internalType": "struct PoolRegistry.AddMarketInput", + "name": "input", + "type": "tuple" + } + ], + "name": "addMarket", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "string", + "name": "name", + "type": "string" + }, + { + "internalType": "contract Comptroller", + "name": "comptroller", + "type": "address" + }, + { + "internalType": "uint256", + "name": "closeFactor", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "liquidationIncentive", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "minLiquidatableCollateral", + "type": "uint256" + } + ], + "name": "addPool", + "outputs": [ + { + "internalType": "uint256", + "name": "index", + "type": "uint256" + } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "getAllPools", + "outputs": [ + { + "components": [ + { + "internalType": "string", + "name": "name", + "type": "string" + }, + { + "internalType": "address", + "name": "creator", + "type": "address" + }, + { + "internalType": "address", + "name": "comptroller", + "type": "address" + }, + { + "internalType": "uint256", + "name": "blockPosted", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "timestampPosted", + "type": "uint256" + } + ], + "internalType": "struct PoolRegistryInterface.VenusPool[]", + "name": "", + "type": "tuple[]" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "comptroller", + "type": "address" + } + ], + "name": "getPoolByComptroller", + "outputs": [ + { + "components": [ + { + "internalType": "string", + "name": "name", + "type": "string" + }, + { + "internalType": "address", + "name": "creator", + "type": "address" + }, + { + "internalType": "address", + "name": "comptroller", + "type": "address" + }, + { + "internalType": "uint256", + "name": "blockPosted", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "timestampPosted", + "type": "uint256" + } + ], + "internalType": "struct PoolRegistryInterface.VenusPool", + "name": "", + "type": "tuple" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "asset", + "type": "address" + } + ], + "name": "getPoolsSupportedByAsset", + "outputs": [ + { + "internalType": "address[]", + "name": "", + "type": "address[]" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "comptroller", + "type": "address" + }, + { + "internalType": "address", + "name": "asset", + "type": "address" + } + ], + "name": "getVTokenForAsset", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "comptroller", + "type": "address" + } + ], + "name": "getVenusPoolMetadata", + "outputs": [ + { + "components": [ + { + "internalType": "string", + "name": "category", + "type": "string" + }, + { + "internalType": "string", + "name": "logoURL", + "type": "string" + }, + { + "internalType": "string", + "name": "description", + "type": "string" + } + ], + "internalType": "struct PoolRegistryInterface.VenusPoolMetaData", + "name": "", + "type": "tuple" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "accessControlManager_", + "type": "address" + } + ], + "name": "initialize", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "name": "metadata", + "outputs": [ + { + "internalType": "string", + "name": "category", + "type": "string" + }, + { + "internalType": "string", + "name": "logoURL", + "type": "string" + }, + { + "internalType": "string", + "name": "description", + "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": "accessControlManager_", + "type": "address" + } + ], + "name": "setAccessControlManager", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "comptroller", + "type": "address" + }, + { + "internalType": "string", + "name": "name", + "type": "string" + } + ], + "name": "setPoolName", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "newOwner", + "type": "address" + } + ], + "name": "transferOwnership", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "comptroller", + "type": "address" + }, + { + "components": [ + { + "internalType": "string", + "name": "category", + "type": "string" + }, + { + "internalType": "string", + "name": "logoURL", + "type": "string" + }, + { + "internalType": "string", + "name": "description", + "type": "string" + } + ], + "internalType": "struct PoolRegistryInterface.VenusPoolMetaData", + "name": "metadata_", + "type": "tuple" + } + ], + "name": "updatePoolMetadata", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + } +] diff --git a/simulations/vip-671/abi/VToken.json b/simulations/vip-671/abi/VToken.json new file mode 100644 index 000000000..c039d2ef1 --- /dev/null +++ b/simulations/vip-671/abi/VToken.json @@ -0,0 +1,2105 @@ +[ + { + "inputs": [ + { + "internalType": "bool", + "name": "timeBased_", + "type": "bool" + }, + { + "internalType": "uint256", + "name": "blocksPerYear_", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "maxBorrowRateMantissa_", + "type": "uint256" + } + ], + "stateMutability": "nonpayable", + "type": "constructor" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "actualAddAmount", + "type": "uint256" + } + ], + "name": "AddReservesFactorFreshCheck", + "type": "error" + }, + { + "inputs": [], + "name": "BorrowCashNotAvailable", + "type": "error" + }, + { + "inputs": [], + "name": "BorrowFreshnessCheck", + "type": "error" + }, + { + "inputs": [], + "name": "DelegateNotApproved", + "type": "error" + }, + { + "inputs": [], + "name": "ForceLiquidateBorrowUnauthorized", + "type": "error" + }, + { + "inputs": [], + "name": "HealBorrowUnauthorized", + "type": "error" + }, + { + "inputs": [], + "name": "InvalidBlocksPerYear", + "type": "error" + }, + { + "inputs": [], + "name": "InvalidTimeBasedConfiguration", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "errorCode", + "type": "uint256" + } + ], + "name": "LiquidateAccrueCollateralInterestFailed", + "type": "error" + }, + { + "inputs": [], + "name": "LiquidateCloseAmountIsUintMax", + "type": "error" + }, + { + "inputs": [], + "name": "LiquidateCloseAmountIsZero", + "type": "error" + }, + { + "inputs": [], + "name": "LiquidateCollateralFreshnessCheck", + "type": "error" + }, + { + "inputs": [], + "name": "LiquidateFreshnessCheck", + "type": "error" + }, + { + "inputs": [], + "name": "LiquidateLiquidatorIsBorrower", + "type": "error" + }, + { + "inputs": [], + "name": "LiquidateSeizeLiquidatorIsBorrower", + "type": "error" + }, + { + "inputs": [], + "name": "MintFreshnessCheck", + "type": "error" + }, + { + "inputs": [], + "name": "ProtocolSeizeShareTooBig", + "type": "error" + }, + { + "inputs": [], + "name": "RedeemFreshnessCheck", + "type": "error" + }, + { + "inputs": [], + "name": "RedeemTransferOutNotPossible", + "type": "error" + }, + { + "inputs": [], + "name": "ReduceReservesCashNotAvailable", + "type": "error" + }, + { + "inputs": [], + "name": "ReduceReservesCashValidation", + "type": "error" + }, + { + "inputs": [], + "name": "ReduceReservesFreshCheck", + "type": "error" + }, + { + "inputs": [], + "name": "RepayBorrowFreshnessCheck", + "type": "error" + }, + { + "inputs": [], + "name": "SetInterestRateModelFreshCheck", + "type": "error" + }, + { + "inputs": [], + "name": "SetReserveFactorBoundsCheck", + "type": "error" + }, + { + "inputs": [], + "name": "SetReserveFactorFreshCheck", + "type": "error" + }, + { + "inputs": [], + "name": "TransferNotAllowed", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "sender", + "type": "address" + }, + { + "internalType": "address", + "name": "calledContract", + "type": "address" + }, + { + "internalType": "string", + "name": "methodSignature", + "type": "string" + } + ], + "name": "Unauthorized", + "type": "error" + }, + { + "inputs": [], + "name": "ZeroAddressNotAllowed", + "type": "error" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "uint256", + "name": "cashPrior", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "interestAccumulated", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "borrowIndex", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "totalBorrows", + "type": "uint256" + } + ], + "name": "AccrueInterest", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "owner", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "spender", + "type": "address" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "amount", + "type": "uint256" + } + ], + "name": "Approval", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "borrower", + "type": "address" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "badDebtDelta", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "badDebtOld", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "badDebtNew", + "type": "uint256" + } + ], + "name": "BadDebtIncreased", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "uint256", + "name": "badDebtOld", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "badDebtNew", + "type": "uint256" + } + ], + "name": "BadDebtRecovered", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "borrower", + "type": "address" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "borrowAmount", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "accountBorrows", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "totalBorrows", + "type": "uint256" + } + ], + "name": "Borrow", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "uint256", + "name": "oldInternalCash", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "newInternalCash", + "type": "uint256" + } + ], + "name": "CashSynced", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "payer", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "borrower", + "type": "address" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "repayAmount", + "type": "uint256" + } + ], + "name": "HealBorrow", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "uint8", + "name": "version", + "type": "uint8" + } + ], + "name": "Initialized", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "liquidator", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "borrower", + "type": "address" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "repayAmount", + "type": "uint256" + }, + { + "indexed": true, + "internalType": "address", + "name": "vTokenCollateral", + "type": "address" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "seizeTokens", + "type": "uint256" + } + ], + "name": "LiquidateBorrow", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "minter", + "type": "address" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "mintAmount", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "mintTokens", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "accountBalance", + "type": "uint256" + } + ], + "name": "Mint", + "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": "contract ComptrollerInterface", + "name": "oldComptroller", + "type": "address" + }, + { + "indexed": true, + "internalType": "contract ComptrollerInterface", + "name": "newComptroller", + "type": "address" + } + ], + "name": "NewComptroller", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "contract InterestRateModel", + "name": "oldInterestRateModel", + "type": "address" + }, + { + "indexed": true, + "internalType": "contract InterestRateModel", + "name": "newInterestRateModel", + "type": "address" + } + ], + "name": "NewMarketInterestRateModel", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "uint256", + "name": "oldProtocolSeizeShareMantissa", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "newProtocolSeizeShareMantissa", + "type": "uint256" + } + ], + "name": "NewProtocolSeizeShare", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "oldProtocolShareReserve", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "newProtocolShareReserve", + "type": "address" + } + ], + "name": "NewProtocolShareReserve", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "uint256", + "name": "oldReduceReservesBlockOrTimestampDelta", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "newReduceReservesBlockOrTimestampDelta", + "type": "uint256" + } + ], + "name": "NewReduceReservesBlockDelta", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "uint256", + "name": "oldReserveFactorMantissa", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "newReserveFactorMantissa", + "type": "uint256" + } + ], + "name": "NewReserveFactor", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "oldShortfall", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "newShortfall", + "type": "address" + } + ], + "name": "NewShortfallContract", + "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": "amount", + "type": "uint256" + } + ], + "name": "ProtocolSeize", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "redeemer", + "type": "address" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "redeemAmount", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "redeemTokens", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "accountBalance", + "type": "uint256" + } + ], + "name": "Redeem", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "payer", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "borrower", + "type": "address" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "repayAmount", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "accountBorrows", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "totalBorrows", + "type": "uint256" + } + ], + "name": "RepayBorrow", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "benefactor", + "type": "address" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "addAmount", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "newTotalReserves", + "type": "uint256" + } + ], + "name": "ReservesAdded", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "protocolShareReserve", + "type": "address" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "reduceAmount", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "newTotalReserves", + "type": "uint256" + } + ], + "name": "SpreadReservesReduced", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "token", + "type": "address" + } + ], + "name": "SweepToken", + "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": "amount", + "type": "uint256" + } + ], + "name": "Transfer", + "type": "event" + }, + { + "inputs": [], + "name": "NO_ERROR", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "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": "accrualBlockNumber", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "accrueInterest", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "addAmount", + "type": "uint256" + } + ], + "name": "addReserves", + "outputs": [], + "stateMutability": "nonpayable", + "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": [], + "name": "badDebt", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "recoveredAmount_", + "type": "uint256" + } + ], + "name": "badDebtRecovered", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "owner", + "type": "address" + } + ], + "name": "balanceOf", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "owner", + "type": "address" + } + ], + "name": "balanceOfUnderlying", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "blocksOrSecondsPerYear", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "borrowAmount", + "type": "uint256" + } + ], + "name": "borrow", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "account", + "type": "address" + } + ], + "name": "borrowBalanceCurrent", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "account", + "type": "address" + } + ], + "name": "borrowBalanceStored", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "borrower", + "type": "address" + }, + { + "internalType": "uint256", + "name": "borrowAmount", + "type": "uint256" + } + ], + "name": "borrowBehalf", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "borrowIndex", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "borrowRatePerBlock", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "comptroller", + "outputs": [ + { + "internalType": "contract ComptrollerInterface", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "decimals", + "outputs": [ + { + "internalType": "uint8", + "name": "", + "type": "uint8" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "spender", + "type": "address" + }, + { + "internalType": "uint256", + "name": "subtractedValue", + "type": "uint256" + } + ], + "name": "decreaseAllowance", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "exchangeRateCurrent", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "exchangeRateStored", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "liquidator", + "type": "address" + }, + { + "internalType": "address", + "name": "borrower", + "type": "address" + }, + { + "internalType": "uint256", + "name": "repayAmount", + "type": "uint256" + }, + { + "internalType": "contract VTokenInterface", + "name": "vTokenCollateral", + "type": "address" + }, + { + "internalType": "bool", + "name": "skipLiquidityCheck", + "type": "bool" + } + ], + "name": "forceLiquidateBorrow", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "account", + "type": "address" + } + ], + "name": "getAccountSnapshot", + "outputs": [ + { + "internalType": "uint256", + "name": "error", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "vTokenBalance", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "borrowBalance", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "exchangeRate", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "getBlockNumberOrTimestamp", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "getCash", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "payer", + "type": "address" + }, + { + "internalType": "address", + "name": "borrower", + "type": "address" + }, + { + "internalType": "uint256", + "name": "repayAmount", + "type": "uint256" + } + ], + "name": "healBorrow", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "spender", + "type": "address" + }, + { + "internalType": "uint256", + "name": "addedValue", + "type": "uint256" + } + ], + "name": "increaseAllowance", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "underlying_", + "type": "address" + }, + { + "internalType": "contract ComptrollerInterface", + "name": "comptroller_", + "type": "address" + }, + { + "internalType": "contract InterestRateModel", + "name": "interestRateModel_", + "type": "address" + }, + { + "internalType": "uint256", + "name": "initialExchangeRateMantissa_", + "type": "uint256" + }, + { + "internalType": "string", + "name": "name_", + "type": "string" + }, + { + "internalType": "string", + "name": "symbol_", + "type": "string" + }, + { + "internalType": "uint8", + "name": "decimals_", + "type": "uint8" + }, + { + "internalType": "address", + "name": "admin_", + "type": "address" + }, + { + "internalType": "address", + "name": "accessControlManager_", + "type": "address" + }, + { + "components": [ + { + "internalType": "address", + "name": "shortfall", + "type": "address" + }, + { + "internalType": "address payable", + "name": "protocolShareReserve", + "type": "address" + } + ], + "internalType": "struct VTokenInterface.RiskManagementInit", + "name": "riskManagement", + "type": "tuple" + }, + { + "internalType": "uint256", + "name": "reserveFactorMantissa_", + "type": "uint256" + } + ], + "name": "initialize", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "interestRateModel", + "outputs": [ + { + "internalType": "contract InterestRateModel", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "internalCash", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "isTimeBased", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "isVToken", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "pure", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "borrower", + "type": "address" + }, + { + "internalType": "uint256", + "name": "repayAmount", + "type": "uint256" + }, + { + "internalType": "contract VTokenInterface", + "name": "vTokenCollateral", + "type": "address" + } + ], + "name": "liquidateBorrow", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "mintAmount", + "type": "uint256" + } + ], + "name": "mint", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "minter", + "type": "address" + }, + { + "internalType": "uint256", + "name": "mintAmount", + "type": "uint256" + } + ], + "name": "mintBehalf", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "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": "protocolSeizeShareMantissa", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "protocolShareReserve", + "outputs": [ + { + "internalType": "address payable", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "redeemTokens", + "type": "uint256" + } + ], + "name": "redeem", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "redeemer", + "type": "address" + }, + { + "internalType": "uint256", + "name": "redeemTokens", + "type": "uint256" + } + ], + "name": "redeemBehalf", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "redeemAmount", + "type": "uint256" + } + ], + "name": "redeemUnderlying", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "redeemer", + "type": "address" + }, + { + "internalType": "uint256", + "name": "redeemAmount", + "type": "uint256" + } + ], + "name": "redeemUnderlyingBehalf", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "reduceAmount", + "type": "uint256" + } + ], + "name": "reduceReserves", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "reduceReservesBlockDelta", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "reduceReservesBlockNumber", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "renounceOwnership", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "repayAmount", + "type": "uint256" + } + ], + "name": "repayBorrow", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "borrower", + "type": "address" + }, + { + "internalType": "uint256", + "name": "repayAmount", + "type": "uint256" + } + ], + "name": "repayBorrowBehalf", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "reserveFactorMantissa", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "liquidator", + "type": "address" + }, + { + "internalType": "address", + "name": "borrower", + "type": "address" + }, + { + "internalType": "uint256", + "name": "seizeTokens", + "type": "uint256" + } + ], + "name": "seize", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "accessControlManager_", + "type": "address" + } + ], + "name": "setAccessControlManager", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "contract InterestRateModel", + "name": "newInterestRateModel", + "type": "address" + } + ], + "name": "setInterestRateModel", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "newProtocolSeizeShareMantissa_", + "type": "uint256" + } + ], + "name": "setProtocolSeizeShare", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address payable", + "name": "protocolShareReserve_", + "type": "address" + } + ], + "name": "setProtocolShareReserve", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "_newReduceReservesBlockOrTimestampDelta", + "type": "uint256" + } + ], + "name": "setReduceReservesBlockDelta", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "newReserveFactorMantissa", + "type": "uint256" + } + ], + "name": "setReserveFactor", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "shortfall_", + "type": "address" + } + ], + "name": "setShortfallContract", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "shortfall", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "supplyRatePerBlock", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "contract IERC20Upgradeable", + "name": "token", + "type": "address" + } + ], + "name": "sweepToken", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "symbol", + "outputs": [ + { + "internalType": "string", + "name": "", + "type": "string" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "syncCash", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "totalBorrows", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "totalBorrowsCurrent", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "totalReserves", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "totalSupply", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "dst", + "type": "address" + }, + { + "internalType": "uint256", + "name": "amount", + "type": "uint256" + } + ], + "name": "transfer", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "src", + "type": "address" + }, + { + "internalType": "address", + "name": "dst", + "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" + }, + { + "inputs": [], + "name": "underlying", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + } +] diff --git a/simulations/vip-671/bsctestnet.ts b/simulations/vip-671/bsctestnet.ts new file mode 100644 index 000000000..c8441b5ad --- /dev/null +++ b/simulations/vip-671/bsctestnet.ts @@ -0,0 +1,529 @@ +import { expect } from "chai"; +import { BigNumber } from "ethers"; +import { ethers } from "hardhat"; +import { NETWORK_ADDRESSES } from "src/networkAddresses"; +import { expectEvents } from "src/utils"; +import { forking, testVip } from "src/vip-framework"; + +import { + ACM, + DEFAULT_PROXY_ADMIN, + DEVIATION_BOUNDED_ORACLE, + GUARDIAN, + ISOLATED_POOL_REGISTRY, + NORMAL_TIMELOCK, + PROTOCOL_SHARE_RESERVE, + PROTOCOL_SHARE_RESERVE_IMPL, + RESILIENT_ORACLE, + RISK_FUND_BUYBACK, + RISK_FUND_CONVERTER, + SPOKE_COMPTROLLER, + SPOKE_COMPTROLLER_BEACON, + SPOKE_POOL_REGISTRY, + SPOKE_VTOKEN_BEACON, + VTREASURY, +} from "../../vips/vip-671/addresses/bsctestnet"; +import vip671 from "../../vips/vip-671/bsctestnet"; +import { + CLOSE_FACTOR, + COLLATERAL_MARKET, + DBO_COOLDOWN_PERIOD, + DBO_RESET_THRESHOLD, + DBO_TRIGGER_THRESHOLD, + LIQUIDITY_MARKET, + MARKETS, + MIN_LIQUIDATABLE_COLLATERAL, + POOL_LIQUIDATION_INCENTIVE, + POOL_NAME, + REDUCE_RESERVES_BLOCK_DELTA, + RISK_FUND_SHARE_BPS, + SCHEMA_LIQUIDATION, + SCHEMA_SPREAD, +} from "../../vips/vip-671/config"; +import { + ASSUMED_GUARDIAN_ROLES, + ASSUMED_WILDCARD_ROLES, + REGISTRY_DRIVEN_ROLES, + SPOKE_COMPTROLLER_ROLES, +} from "../../vips/vip-671/permissions"; +import ACM_ABI from "./abi/AccessControlManager.json"; +import DBO_ABI from "./abi/DeviationBoundedOracle.json"; +import ERC20_ABI from "./abi/ERC20.json"; +import PSR_ABI from "./abi/ProtocolShareReserve.json"; +import RESILIENT_ORACLE_ABI from "./abi/ResilientOracle.json"; +import COMPTROLLER_ABI from "./abi/SpokeComptroller.json"; +import POOL_REGISTRY_ABI from "./abi/SpokePoolRegistry.json"; +import VTOKEN_ABI from "./abi/VToken.json"; + +const { bsctestnet } = NETWORK_ADDRESSES; + +/// A market of a LIVE isolated pool, used to prove the ProtocolShareReserve upgrade is not a +/// regression. Repointing PSR with `setPoolRegistry` instead of `addPoolRegistry` would stop this pool +/// from reporting income and revert its liquidations, which is the whole reason protocol-reserve#168 +/// exists. Comptroller_StableCoins and the underlying of its first market, on bsctestnet. +const LIVE_ISOLATED_COMPTROLLER = "0x10b57706AD2345e590c2eA4DC02faef0d9f5b08B"; +const LIVE_ISOLATED_UNDERLYING = "0xe73774DfCD551BF75650772dC2cC56a2B6323453"; + +/// After the spoke stack landed on this chain (vUSDC_HubSpoke was deployed at 129,845,780) and before +/// this VIP runs, so the pre-VIP assertions below describe a real, untouched deployment. +const FORK_BLOCK = 129848000; + +/// `AccessControlManager.isAllowedToCall` keys the role on `msg.sender`, so the only honest way to ask +/// "may X configure THIS contract" is to ask it from that contract's own address. `hasPermission` is +/// the view that takes the target explicitly, but it does NOT consult the address(0) wildcards, so it +/// answers false for roles this chain really does grant. Both matter here, and they answer different +/// questions, so the helpers are kept separate. +const ACM_IFACE = new ethers.utils.Interface(["function isAllowedToCall(address,string) view returns (bool)"]); +const mayCall = async (onBehalfOf: string, account: string, signature: string): Promise => { + const data = ACM_IFACE.encodeFunctionData("isAllowedToCall", [account, signature]); + const result = await ethers.provider.call({ to: ACM, data, from: onBehalfOf }); + return ACM_IFACE.decodeFunctionResult("isAllowedToCall", result)[0]; +}; + +/// `PoolRegistry.addMarket` mints the seed at the market's initial exchange rate, so the receiver's +/// balance is the underlying amount scaled by it rather than the amount itself. +const seedVTokens = (amount: BigNumber, exchangeRate: BigNumber) => + amount.mul(BigNumber.from(10).pow(18)).div(exchangeRate); + +forking(FORK_BLOCK, async () => { + const comptroller = new ethers.Contract(SPOKE_COMPTROLLER, COMPTROLLER_ABI, ethers.provider); + const registry = new ethers.Contract(SPOKE_POOL_REGISTRY, POOL_REGISTRY_ABI, ethers.provider); + const psr = new ethers.Contract(PROTOCOL_SHARE_RESERVE, PSR_ABI, ethers.provider); + const resilientOracle = new ethers.Contract(RESILIENT_ORACLE, RESILIENT_ORACLE_ABI, ethers.provider); + const dbo = new ethers.Contract(DEVIATION_BOUNDED_ORACLE, DBO_ABI, ethers.provider); + + describe("Pre-VIP state", () => { + it("leaves both Ownable2Step contracts with the deployer, timelock only nominated", async () => { + for (const c of [comptroller, registry]) { + expect(await c.owner()).to.not.equal(NORMAL_TIMELOCK); + expect(await c.pendingOwner()).to.equal(NORMAL_TIMELOCK); + } + }); + + it("has both beacons already owned by the timelock, so the VIP needs no command for them", async () => { + const beaconAbi = ["function owner() view returns (address)"]; + for (const beacon of [SPOKE_COMPTROLLER_BEACON, SPOKE_VTOKEN_BEACON]) { + const c = new ethers.Contract(beacon, beaconAbi, ethers.provider); + expect(await c.owner()).to.equal(NORMAL_TIMELOCK); + } + }); + + it("wires the comptroller to the spoke registry, not the isolated-pools one", async () => { + // A constructor immutable with no setter, and `supportMarket` checks it. Nothing in the VIP can + // fix a mismatch, so this is the assertion that has to hold before anything else runs. + expect(await comptroller.poolRegistry()).to.equal(SPOKE_POOL_REGISTRY); + expect(await comptroller.poolRegistry()).to.not.equal(ISOLATED_POOL_REGISTRY); + expect(await comptroller.accessControlManager()).to.equal(ACM); + }); + + it("has an empty registry and no pool", async () => { + expect(await registry.getAllPools()).to.have.lengthOf(0); + const pool = await registry.getPoolByComptroller(SPOKE_COMPTROLLER); + expect(pool.comptroller).to.equal(ethers.constants.AddressZero); + }); + + it("has both oracles unset, so the pool cannot be registered or serve a borrow", async () => { + expect(await comptroller.oracle()).to.equal(ethers.constants.AddressZero); + expect(await comptroller.deviationBoundedOracle()).to.equal(ethers.constants.AddressZero); + }); + + it("lists no market", async () => { + expect(await comptroller.getAllMarkets()).to.have.lengthOf(0); + for (const m of MARKETS) { + const market = await comptroller.markets(m.vToken); + expect(market.isListed, `${m.symbol} listed`).to.be.false; + } + }); + + it("gives the spoke registry none of the setters addPool drives", async () => { + // The identical wildcards exist, but they name the isolated-pools registry as the account, so + // this registry inherits nothing and addPool would revert. + for (const signature of REGISTRY_DRIVEN_ROLES) { + expect(await mayCall(SPOKE_COMPTROLLER, SPOKE_POOL_REGISTRY, signature), `spoke registry may ${signature}`).to + .be.false; + expect( + await mayCall(SPOKE_COMPTROLLER, ISOLATED_POOL_REGISTRY, signature), + `isolated registry may ${signature}`, + ).to.be.true; + } + }); + + it("gives nobody the spoke-only roles", async () => { + // Neither grantee holds these before the VIP, so every post-VIP assertion below is the VIP's + // doing rather than a wildcard that was already there. + for (const signature of SPOKE_COMPTROLLER_ROLES) { + for (const account of [NORMAL_TIMELOCK, GUARDIAN]) { + expect(await mayCall(SPOKE_COMPTROLLER, account, signature), `${account} may ${signature}`).to.be.false; + } + } + }); + + it("points ProtocolShareReserve at the isolated-pools registry only", async () => { + expect(await psr.poolRegistry()).to.equal(ISOLATED_POOL_REGISTRY); + }); + + it("still routes the risk fund share through the retired converter", async () => { + const rows = await Promise.all( + [...Array((await psr.totalDistributions()).toNumber())].map((_, i) => psr.distributionTargets(i)), + ); + const converterRows = rows.filter((r: any) => r.destination === RISK_FUND_CONVERTER); + expect(converterRows).to.have.lengthOf(2); + for (const row of converterRows) { + expect(row.percentage).to.equal(RISK_FUND_SHARE_BPS); + } + expect(rows.filter((r: any) => r.destination === RISK_FUND_BUYBACK)).to.have.lengthOf(0); + }); + + it("prices both underlyings already, so the VIP needs no ResilientOracle command", async () => { + for (const m of MARKETS) { + expect(await resilientOracle.getPrice(m.underlying), `${m.symbol} price`).to.be.gt(0); + } + }); + + it("bounds the liquidity underlying but not the collateral one", async () => { + expect(await dbo.isBoundedPricingEnabled(LIQUIDITY_MARKET.underlying)).to.be.true; + expect(await dbo.isBoundedPricingEnabled(COLLATERAL_MARKET.underlying)).to.be.false; + }); + }); + + // Everything this VIP takes for granted rather than granting. The failure mode these guard against is + // the quiet one: a permission the VIP relies on gets revoked or was never really there, nothing here + // notices because the VIP does not touch it, and the proposal reverts on execution. Asserting them + // pre-VIP means that shows up as a red simulation on the day it changes. + describe("Pre-VIP assumptions", () => { + const proxyAdmin = new ethers.Contract( + DEFAULT_PROXY_ADMIN, + [ + "function owner() view returns (address)", + "function getProxyAdmin(address) view returns (address)", + "function getProxyImplementation(address) view returns (address)", + ], + ethers.provider, + ); + + it("lets the timelock configure the new comptroller through the existing wildcards", async () => { + for (const signature of ASSUMED_WILDCARD_ROLES.comptroller) { + expect(await mayCall(SPOKE_COMPTROLLER, NORMAL_TIMELOCK, signature), `timelock may ${signature}`).to.be.true; + } + }); + + it("lets the timelock configure the new vTokens through the existing wildcards", async () => { + for (const m of MARKETS) { + const vToken = new ethers.Contract(m.vToken, VTOKEN_ABI, ethers.provider); + // A wildcard only reaches this market if the market asks THIS ACM in the first place. + expect(await vToken.accessControlManager(), `${m.symbol} acm`).to.equal(ACM); + + for (const signature of ASSUMED_WILDCARD_ROLES.vToken) { + expect(await mayCall(m.vToken, NORMAL_TIMELOCK, signature), `timelock may ${m.symbol}.${signature}`).to.be + .true; + } + } + }); + + it("lets the timelock drive the new registry through the existing wildcards", async () => { + // The half of the wildcard story that does work: the account is the timelock, which is unchanged, + // so a registry deployed yesterday inherits these. Contrast REGISTRY_DRIVEN_ROLES, where the + // account is the registry itself and nothing is inherited. + expect(await registry.accessControlManager()).to.equal(ACM); + for (const signature of ASSUMED_WILDCARD_ROLES.poolRegistry) { + expect(await mayCall(SPOKE_POOL_REGISTRY, NORMAL_TIMELOCK, signature), `timelock may ${signature}`).to.be.true; + } + }); + + it("lets every timelock configure the bounded oracle and the reserve", async () => { + const timelocks = [NORMAL_TIMELOCK, bsctestnet.FAST_TRACK_TIMELOCK, bsctestnet.CRITICAL_TIMELOCK]; + const targets: [string, string[]][] = [ + [DEVIATION_BOUNDED_ORACLE, ASSUMED_WILDCARD_ROLES.deviationBoundedOracle], + [PROTOCOL_SHARE_RESERVE, ASSUMED_WILDCARD_ROLES.protocolShareReserve], + ]; + + for (const [target, signatures] of targets) { + for (const signature of signatures) { + for (const timelock of timelocks) { + expect(await mayCall(target, timelock, signature), `${timelock} may ${signature}`).to.be.true; + } + } + } + }); + + it("already opens the pause path on the new comptroller to all three timelocks", async () => { + // This is what the decision to grant the emergency timelocks nothing rests on. If it were false, + // this pool would ship with no fast pause and the grant list would need revisiting. + const [pauseRole] = ASSUMED_WILDCARD_ROLES.pause; + for (const timelock of [NORMAL_TIMELOCK, bsctestnet.FAST_TRACK_TIMELOCK, bsctestnet.CRITICAL_TIMELOCK]) { + expect(await mayCall(SPOKE_COMPTROLLER, timelock, pauseRole), `${timelock} may pause`).to.be.true; + // The call signature carries uint8[] because Action is an enum, and that string is granted to + // nobody. Pinned so neither string gets "fixed" to match the other. + expect(await mayCall(SPOKE_COMPTROLLER, timelock, "setActionsPaused(address[],uint8[],bool)")).to.be.false; + } + }); + + it("already lets the Guardian set the parameters that decide when a position is underwater", async () => { + for (const signature of ASSUMED_GUARDIAN_ROLES) { + expect(await mayCall(SPOKE_COMPTROLLER, GUARDIAN, signature), `guardian may ${signature}`).to.be.true; + } + }); + + it("owns the reserve and its proxy admin from the timelock, so neither call needs an ACM grant", async () => { + // addPoolRegistry is onlyOwner, not access-controlled, and so is ProxyAdmin.upgrade. + expect(await psr.owner()).to.equal(NORMAL_TIMELOCK); + expect(await proxyAdmin.owner()).to.equal(NORMAL_TIMELOCK); + expect(await proxyAdmin.getProxyAdmin(PROTOCOL_SHARE_RESERVE)).to.equal(DEFAULT_PROXY_ADMIN); + }); + + it("has the reserve still on the single-registry implementation, so the upgrade is not a no-op", async () => { + expect(await proxyAdmin.getProxyImplementation(PROTOCOL_SHARE_RESERVE)).to.not.equal(PROTOCOL_SHARE_RESERVE_IMPL); + // getPoolRegistries does not exist yet, so the proxy falls through to a revert. + let reachable = true; + try { + await psr.getPoolRegistries(); + } catch { + reachable = false; + } + expect(reachable, "getPoolRegistries reachable before the upgrade").to.be.false; + }); + }); + + testVip("VIP-671 Hub-Funded Spoke pool", await vip671(), { + callbackAfterExecution: async txResponse => { + // 6 to the spoke registry, then the same 6 spoke-only roles to each of the two grantees. + await expectEvents( + txResponse, + [ACM_ABI], + ["PermissionGranted"], + [REGISTRY_DRIVEN_ROLES.length + 2 * SPOKE_COMPTROLLER_ROLES.length], + ); + await expectEvents(txResponse, [POOL_REGISTRY_ABI], ["PoolRegistered", "MarketAdded"], [1, MARKETS.length]); + await expectEvents( + txResponse, + [COMPTROLLER_ABI], + [ + "NewPriceOracle", + "NewDeviationBoundedOracle", + "NewMarketLiquidationIncentive", + "SupplyAllowlistEnabledUpdated", + ], + [1, 1, MARKETS.length, 1], + ); + }, + }); + + describe("Post-VIP state", () => { + it("hands both Ownable2Step contracts to the timelock", async () => { + for (const c of [comptroller, registry]) { + expect(await c.owner()).to.equal(NORMAL_TIMELOCK); + expect(await c.pendingOwner()).to.equal(ethers.constants.AddressZero); + } + }); + + it("sets both oracles", async () => { + expect(await comptroller.oracle()).to.equal(RESILIENT_ORACLE); + expect(await comptroller.deviationBoundedOracle()).to.equal(DEVIATION_BOUNDED_ORACLE); + }); + + it("gives the collateral underlying the same price window as the liquidity one", async () => { + const liquidity = await dbo.assetProtectionConfig(LIQUIDITY_MARKET.underlying); + const collateral = await dbo.assetProtectionConfig(COLLATERAL_MARKET.underlying); + + expect(await dbo.isBoundedPricingEnabled(COLLATERAL_MARKET.underlying)).to.be.true; + expect(collateral.asset).to.equal(COLLATERAL_MARKET.underlying); + expect(collateral.cooldownPeriod).to.equal(DBO_COOLDOWN_PERIOD); + expect(collateral.triggerThreshold).to.equal(DBO_TRIGGER_THRESHOLD); + expect(collateral.resetThreshold).to.equal(DBO_RESET_THRESHOLD); + + // The point of the command: the two sides of one pool are priced the same way. + expect(collateral.cooldownPeriod).to.equal(liquidity.cooldownPeriod); + expect(collateral.triggerThreshold).to.equal(liquidity.triggerThreshold); + expect(collateral.resetThreshold).to.equal(liquidity.resetThreshold); + expect(collateral.cachingEnabled).to.equal(liquidity.cachingEnabled); + + // The oracle seeds the window from spot, so it opens closed and starts unprotected. + const spot = await resilientOracle.getPrice(COLLATERAL_MARKET.underlying); + expect(collateral.minPrice).to.equal(spot); + expect(collateral.maxPrice).to.equal(spot); + expect(collateral.currentlyUsingProtectedPrice).to.be.false; + }); + + it("leaves the liquidity underlying's window untouched", async () => { + // setTokenConfig reverts MarketAlreadyInitialized for an initialised asset, so USDT must not be + // in the payload at all. + const liquidity = await dbo.assetProtectionConfig(LIQUIDITY_MARKET.underlying); + expect(liquidity.asset).to.equal(LIQUIDITY_MARKET.underlying); + expect(await dbo.isBoundedPricingEnabled(LIQUIDITY_MARKET.underlying)).to.be.true; + }); + + it("registers the pool with the parameters it was deployed for", async () => { + const pools = await registry.getAllPools(); + expect(pools).to.have.lengthOf(1); + expect(pools[0].name).to.equal(POOL_NAME); + expect(pools[0].comptroller).to.equal(SPOKE_COMPTROLLER); + + // addPool pushes these three into the comptroller itself, under the grants this VIP gives the + // registry, so asserting them here proves those grants landed and were used. + expect(await comptroller.closeFactorMantissa()).to.equal(CLOSE_FACTOR); + expect(await comptroller.minLiquidatableCollateral()).to.equal(MIN_LIQUIDATABLE_COLLATERAL); + expect(await comptroller.effectiveLiquidationIncentive(ethers.constants.AddressZero)).to.equal( + POOL_LIQUIDATION_INCENTIVE, + ); + }); + + it("leaves the pool out of the isolated-pools registry", async () => { + const isolated = new ethers.Contract(ISOLATED_POOL_REGISTRY, POOL_REGISTRY_ABI, ethers.provider); + const listed = await isolated.getAllPools(); + expect(listed.map((p: any) => p.comptroller)).to.not.include(SPOKE_COMPTROLLER); + }); + + it("lists both markets with their risk parameters and caps", async () => { + expect(await comptroller.getAllMarkets()).to.have.lengthOf(MARKETS.length); + + for (const m of MARKETS) { + const market = await comptroller.markets(m.vToken); + expect(market.isListed, `${m.symbol} listed`).to.be.true; + expect(market.collateralFactorMantissa, `${m.symbol} CF`).to.equal(m.collateralFactor); + expect(market.liquidationThresholdMantissa, `${m.symbol} LT`).to.equal(m.liquidationThreshold); + expect(await comptroller.supplyCaps(m.vToken), `${m.symbol} supply cap`).to.equal(m.supplyCap); + expect(await comptroller.borrowCaps(m.vToken), `${m.symbol} borrow cap`).to.equal(m.borrowCap); + } + }); + + it("configures each vToken and seeds it to the treasury", async () => { + for (const m of MARKETS) { + const vToken = new ethers.Contract(m.vToken, VTOKEN_ABI, ethers.provider); + expect(await vToken.comptroller(), `${m.symbol} comptroller`).to.equal(SPOKE_COMPTROLLER); + expect(await vToken.underlying(), `${m.symbol} underlying`).to.equal(m.underlying); + expect(await vToken.interestRateModel(), `${m.symbol} irm`).to.equal(m.interestRateModel); + expect(await vToken.reserveFactorMantissa(), `${m.symbol} reserve factor`).to.equal(m.reserveFactor); + expect(await vToken.reduceReservesBlockDelta(), `${m.symbol} block delta`).to.equal( + REDUCE_RESERVES_BLOCK_DELTA, + ); + + // The seed is minted to the treasury, so it is the treasury that must hold it, not the timelock. + const expected = seedVTokens(m.initialSupply, await vToken.exchangeRateStored()); + expect(await vToken.balanceOf(VTREASURY), `${m.symbol} seed`).to.be.closeTo(expected, expected.div(1000)); + expect(await vToken.totalSupply(), `${m.symbol} total supply`).to.be.gt(0); + } + }); + + it("leaves no underlying approval behind on the registry", async () => { + for (const m of MARKETS) { + const underlying = new ethers.Contract(m.underlying, ERC20_ABI, ethers.provider); + expect(await underlying.allowance(NORMAL_TIMELOCK, SPOKE_POOL_REGISTRY), `${m.symbol} allowance`).to.equal(0); + } + }); + + it("pins the per-market liquidation discount on every market", async () => { + for (const m of MARKETS) { + expect(await comptroller.effectiveLiquidationIncentive(m.vToken), `${m.symbol} incentive`).to.equal( + m.liquidationIncentive, + ); + } + }); + + it("restricts supply on the liquidity market and nothing else", async () => { + expect(await comptroller.isSupplyAllowlistEnabled(LIQUIDITY_MARKET.vToken)).to.be.true; + for (const m of MARKETS.filter(m => m.vToken !== LIQUIDITY_MARKET.vToken)) { + expect(await comptroller.isSupplyAllowlistEnabled(m.vToken), `${m.symbol} allowlist`).to.be.false; + } + }); + + it("arms that allowlist with no members, so the market is closed until Phase 2", async () => { + for (const account of [NORMAL_TIMELOCK, VTREASURY, SPOKE_POOL_REGISTRY]) { + expect(await comptroller.isAllowedSupplier(LIQUIDITY_MARKET.vToken, account)).to.be.false; + } + }); + + it("leaves the liquidation allowlist disabled, so liquidation stays permissionless", async () => { + expect(await comptroller.isLiquidationAllowlistEnabled()).to.be.false; + }); + + it("grants the spoke registry exactly the setters addPool drives", async () => { + for (const signature of REGISTRY_DRIVEN_ROLES) { + expect(await mayCall(SPOKE_COMPTROLLER, SPOKE_POOL_REGISTRY, signature), `spoke registry may ${signature}`).to + .be.true; + } + }); + + it("grants the spoke-only roles to the Normal Timelock and the Guardian, and to nobody else", async () => { + for (const signature of SPOKE_COMPTROLLER_ROLES) { + for (const account of [NORMAL_TIMELOCK, GUARDIAN]) { + expect(await mayCall(SPOKE_COMPTROLLER, account, signature), `${account} may ${signature}`).to.be.true; + } + for (const other of [bsctestnet.FAST_TRACK_TIMELOCK, bsctestnet.CRITICAL_TIMELOCK]) { + expect(await mayCall(SPOKE_COMPTROLLER, other, signature), `${other} may ${signature}`).to.be.false; + } + } + }); + + it("gives the Guardian everything QA has to drive, and no pause power it did not have", async () => { + // The point of the Guardian grants: each of these is a knob a test turns in both directions, and + // none of them was reachable without a proposal before this VIP. + for (const signature of [ + "setAllowedSupplier(address,address,bool)", + "setSupplyAllowlistEnabled(address,bool)", + "setAllowedLiquidator(address,bool)", + "setLiquidationAllowlistEnabled(bool)", + "setForcedLiquidation(address,bool)", + ]) { + expect(await mayCall(SPOKE_COMPTROLLER, GUARDIAN, signature), `guardian may ${signature}`).to.be.true; + } + + // Deliberately not granted, and not inherited: the pause path stays with the timelocks. The role + // string carries uint256[] while the call signature carries uint8[], because Action is an enum. + expect(await mayCall(SPOKE_COMPTROLLER, GUARDIAN, "setActionsPaused(address[],uint256[],bool)")).to.be.false; + for (const timelock of [NORMAL_TIMELOCK, bsctestnet.FAST_TRACK_TIMELOCK, bsctestnet.CRITICAL_TIMELOCK]) { + expect( + await mayCall(SPOKE_COMPTROLLER, timelock, "setActionsPaused(address[],uint256[],bool)"), + `${timelock} may pause`, + ).to.be.true; + } + }); + + it("keeps the isolated-pools registry primary on ProtocolShareReserve and adds the spoke one", async () => { + expect(await psr.poolRegistry()).to.equal(ISOLATED_POOL_REGISTRY); + expect(await psr.getPoolRegistries()).to.include(SPOKE_POOL_REGISTRY); + }); + + it("still resolves the live isolated pools, so the upgrade is not a regression", async () => { + // The failure mode this replaces: `setPoolRegistry` would have swapped the single registry, and + // every existing isolated pool would have stopped taking income and started reverting on the + // seize path the block it landed. + expect(await psr.isMarketRegistered(LIVE_ISOLATED_COMPTROLLER, LIVE_ISOLATED_UNDERLYING)).to.be.true; + }); + + it("lets ProtocolShareReserve resolve this pool's markets", async () => { + // The whole point of the upgrade: without it every liquidation in this pool reverts, because + // VToken calls updateAssetsState unconditionally on the seize path. + for (const m of MARKETS) { + expect(await psr.isMarketRegistered(SPOKE_COMPTROLLER, m.underlying), `${m.symbol} known to PSR`).to.be.true; + } + }); + + it("moves the risk fund share to the buyback and drops the converter rows", async () => { + const rows = await Promise.all( + [...Array((await psr.totalDistributions()).toNumber())].map((_, i) => psr.distributionTargets(i)), + ); + expect( + rows.filter((r: any) => r.destination === RISK_FUND_CONVERTER), + "converter rows", + ).to.have.lengthOf(0); + + const buyback = rows.filter((r: any) => r.destination === RISK_FUND_BUYBACK); + expect(buyback.map((r: any) => r.schema).sort()).to.deep.equal([SCHEMA_SPREAD, SCHEMA_LIQUIDATION]); + for (const row of buyback) { + expect(row.percentage).to.equal(RISK_FUND_SHARE_BPS); + } + }); + + it("keeps every schema summing to 100%, which ProtocolShareReserve enforces", async () => { + const rows = await Promise.all( + [...Array((await psr.totalDistributions()).toNumber())].map((_, i) => psr.distributionTargets(i)), + ); + for (const schema of [SCHEMA_SPREAD, SCHEMA_LIQUIDATION]) { + const total = rows + .filter((r: any) => r.schema === schema) + .reduce((sum: number, r: any) => sum + r.percentage, 0); + expect(total, `schema ${schema} total`).to.equal(10_000); + } + }); + }); +}); diff --git a/vips/vip-671/addresses/bsctestnet.ts b/vips/vip-671/addresses/bsctestnet.ts new file mode 100644 index 000000000..da6c82511 --- /dev/null +++ b/vips/vip-671/addresses/bsctestnet.ts @@ -0,0 +1,128 @@ +import { NETWORK_ADDRESSES } from "src/networkAddresses"; + +// =================================================================================================== +// VIP-671 [BNB Chain Testnet] — Hub-Funded Spoke pool, PHASE 1 address book. +// +// Phase 1 covers the spoke pool side only (isolated-pools#559). The Liquidity Hub wiring +// (venus-liquidity-hub#22) and the bStock liquidation leg (venus-protocol#707) are Phase 2 and have +// no addresses here on purpose. +// +// The spoke stack below is deployed and was read back from bsctestnet (chainId 97) while drafting: +// - SpokePoolRegistry and Comptroller_HubSpoke are both Ownable2Step with the deployer still the +// live owner and the Normal Timelock only nominated, so both need `acceptOwnership()`. +// - Comptroller_HubSpoke.poolRegistry() == SpokePoolRegistry. That is a constructor immutable with +// no setter, and `supportMarket` checks it, so this is the check that must never drift. +// - Both beacons are already owned by the Normal Timelock, so neither needs a command. +// - Both oracles on the comptroller are still the zero address, and the registry holds no pools. +// - Both markets carry the 10% reserve factor and the 5% protocol seize share their initializer +// set, and neither is listed yet. +// +// Underlying decimals: bsctestnet USDT and USDC both have 6 DECIMALS, so every amount in ../config.ts +// is 6-decimal and both markets list at an initial exchange rate of 1e16, below 1e18. +// =================================================================================================== + +// The Normal Timelock and the Guardian are the two grantees. The Fast-track and Critical timelocks are +// deliberately absent from this file: they receive nothing from this VIP. The Guardian's grants are a +// bsctestnet decision, made so QA can drive this pool without a proposal per test case, and they do not +// carry to a mainnet listing. See ../permissions.ts. +const { ACCESS_CONTROL_MANAGER, GUARDIAN, NORMAL_TIMELOCK, RESILIENT_ORACLE, VTREASURY } = NETWORK_ADDRESSES.bsctestnet; + +export const ACM = ACCESS_CONTROL_MANAGER; +export { GUARDIAN, NORMAL_TIMELOCK, RESILIENT_ORACLE, VTREASURY }; + +// --------------------------------------------------------------------------------------------------- +// The spoke stack. isolated-pools deploy/024 through deploy/028, all live on bsctestnet. +// --------------------------------------------------------------------------------------------------- + +/// `SpokePoolRegistry`, a second `PoolRegistry` instance behind the chain's DefaultProxyAdmin. The +/// spoke pool is kept out of the isolated-pools directory that the indexer, the frontend pool list +/// and the risk tooling all iterate. Ownable2Step, nominated only: `acceptOwnership()` comes first. +export const SPOKE_POOL_REGISTRY = "0xeAA45288d804971e5a76f33559e629F5b2b1Cb8B"; + +/// `Comptroller_HubSpoke`, a BeaconProxy over SpokeComptrollerBeacon. Ownable2Step, nominated only. +export const SPOKE_COMPTROLLER = "0x11960c84d6c4F2a978a12372721C3A6A88C78f4c"; + +/// Reference only. `UpgradeableBeacon` is plain `Ownable` and both were handed to the Normal Timelock +/// inside their deploy transactions, so this VIP has no command for either. +export const SPOKE_COMPTROLLER_BEACON = "0x076f3fb34C8937a62aD562c33249798367542d75"; +export const SPOKE_VTOKEN_BEACON = "0xB9c3b5A6f13FD5BEF51eE8B62ED3EA384a9d1B45"; + +/// Reference only. The lens holds no state, has no owner and no AccessControlManager, takes the +/// registry to read as a call argument, and nothing on chain stores its address, so it needs no +/// command in this or any VIP. +export const SPOKE_POOL_LENS = "0xfbCBFF4ca8b2fFe3231b0c0BBEa25C79F4B0597c"; + +// --------------------------------------------------------------------------------------------------- +// Markets and underlyings. +// --------------------------------------------------------------------------------------------------- +// Both are 6 decimals and both mint through `allocateTo(address,uint256)`, not the `faucet(uint256)` +// the bStock mocks carry. Checked against the deployed bytecode. +export const USDT = "0xA11c8D9DC9b66E209Ef60F0C8D969D3CD988782c"; +export const USDC = "0x16227D60f7a0e586C66B005219dfc887D13C9531"; + +export const VUSDT_SPOKE = "0xC88bAF0bA49a98F15A00182752f6d10bd3932F6a"; // vUSDT_HubSpoke +export const VUSDC_SPOKE = "0xD05514217FD359659aE7da7740e79C11947eBB32"; // vUSDC_HubSpoke + +/// One shared `JumpRateModelV2` for both markets. Verified on chain: kink 0.8e18, 70,080,000 blocks +/// per year. Constructed by deploy/028 from the curve in isolated-pools helpers/spokeDeploymentConfig. +export const IRM_SPOKE = "0x6700020659b6100A92ac1817D5489d67ee8D8F32"; + +// --------------------------------------------------------------------------------------------------- +// Oracles — both live. The ResilientOracle needs no command; the bounded oracle takes one. +// --------------------------------------------------------------------------------------------------- + +/// Taken from oracle/deployments/bsctestnet_addresses.json (DeviationBoundedOracle_Proxy), the same +/// address VIP-633 uses. It resolves `vToken.underlying()` and keys its config on the UNDERLYING, so +/// the new spoke vTokens inherit whatever the underlying already carries. +/// +/// USDT already carries a price window here; USDC carried none, so this VIP gives USDC USDT's exact +/// configuration and both sides of the pool end up priced the same way. See ../config.ts. +export const DEVIATION_BOUNDED_ORACLE = "0xE0dafC97895B3c98d3B96D3f8739AaC73166beB8"; + +// --------------------------------------------------------------------------------------------------- +// ProtocolShareReserve. +// --------------------------------------------------------------------------------------------------- + +/// @venusprotocol/protocol-reserve deployments/bsctestnet_addresses.json (ProtocolShareReserve_Proxy). +/// Owned by the Normal Timelock, so the calls in this VIP need no ACM grant. +export const PROTOCOL_SHARE_RESERVE = "0x25c7c7D6Bf710949fD7f03364E9BA19a1b3c10E3"; + +/// The multi-registry `ProtocolShareReserve` implementation from protocol-reserve#168, deployed but +/// not yet adopted: the proxy above still points at 0x6eFa596c53E6A753DdA643e3e3FEcA1570879b7C, which +/// carries `setPoolRegistry` but not `addPoolRegistry`. This VIP performs the upgrade. +/// +/// Verified against the deployed bytecode: `addPoolRegistry`, `removePoolRegistry`, +/// `getPoolRegistries` and `isMarketRegistered` are all present, and every constructor immutable +/// matches the implementation in use today (CORE_POOL_COMPTROLLER 0x94d1…b77D, which is the +/// bsctestnet Unitroller, plus WBNB and vBNB). An immutable that drifted here would silently break +/// the core-pool bypass in `updateAssetsState`. +/// +/// No reinitializer and no migration: PSR is a leaf contract, the new state is appended at slots +/// 305/306 and slots 301-304 are untouched. +export const PROTOCOL_SHARE_RESERVE_IMPL = "0x248Ea902F2f50cb232799196530bCDaDcF2659E9"; + +/// The transparent-proxy admin both protocol-reserve and isolated-pools upgrade through on this chain. +/// Verified: it is the admin of the ProtocolShareReserve proxy above, and it is owned by the Normal +/// Timelock, so the upgrade in this VIP needs no ACM grant either. +export const DEFAULT_PROXY_ADMIN = "0x7877fFd62649b6A1557B55D4c20fcBaB17344C91"; + +/// ProtocolShareReserve income destinations, both live. +/// +/// bsctestnet still routes 20% of both income schemas to `RiskFundConverter`, a destination bscmainnet +/// retired in VIP-618 when it moved to the buyback contracts. That drift is why this VIP touches the +/// distribution config: `RiskFundConverter` resolves the paying pool through a single `poolRegistry` +/// of its own and reverts `MarketNotExistInPool` (selector 0x983f1fb5, reproduced against the live +/// contract) for a comptroller that registry does not know, which would take down every +/// `releaseFunds` call naming the spoke pool. +/// +/// `RiskFundBuyback` is already deployed on this chain (VIP-618) and holds exactly the 20% share on +/// both schemas on bscmainnet, so moving the row here is the mainnet shape, not a new allocation. +/// Verified against the live contract: called as ProtocolShareReserve, it accepts a comptroller no +/// registry knows. +export const RISK_FUND_CONVERTER = "0x32Fbf7bBbd79355B86741E3181ef8c1D9bD309Bb"; +export const RISK_FUND_BUYBACK = "0x1a063a07853b9bC797E571E54B5Ce632195071fE"; + +/// The isolated-pools registry. Referenced only to make the contrast explicit: this pool is NOT +/// listed here, and the wildcard ACM grants this address holds name it as the account, so none of +/// them carry over to the spoke registry. +export const ISOLATED_POOL_REGISTRY = NETWORK_ADDRESSES.bsctestnet.POOL_REGISTRY; diff --git a/vips/vip-671/bsctestnet.ts b/vips/vip-671/bsctestnet.ts new file mode 100644 index 000000000..e7bf2ffdc --- /dev/null +++ b/vips/vip-671/bsctestnet.ts @@ -0,0 +1,472 @@ +import { Command, ProposalType } from "src/types"; +import { makeProposal } from "src/utils"; + +import { + ACM, + DEFAULT_PROXY_ADMIN, + DEVIATION_BOUNDED_ORACLE, + GUARDIAN, + NORMAL_TIMELOCK, + PROTOCOL_SHARE_RESERVE, + PROTOCOL_SHARE_RESERVE_IMPL, + RESILIENT_ORACLE, + RISK_FUND_BUYBACK, + RISK_FUND_CONVERTER, + SPOKE_COMPTROLLER, + SPOKE_POOL_REGISTRY, + VTREASURY, +} from "./addresses/bsctestnet"; +import { + CLOSE_FACTOR, + COLLATERAL_MARKET, + DBO_COOLDOWN_PERIOD, + DBO_ENABLE_BOUNDED_PRICING, + DBO_ENABLE_CACHING, + DBO_RESET_THRESHOLD, + DBO_TRIGGER_THRESHOLD, + LIQUIDITY_MARKET, + MARKETS, + MIN_LIQUIDATABLE_COLLATERAL, + POOL_LIQUIDATION_INCENTIVE, + POOL_NAME, + REDUCE_RESERVES_BLOCK_DELTA, + RISK_FUND_SHARE_BPS, + SCHEMA_LIQUIDATION, + SCHEMA_SPREAD, + SpokeMarket, +} from "./config"; +import { REGISTRY_DRIVEN_ROLES, SPOKE_COMPTROLLER_ROLES, giveCallPermission } from "./permissions"; + +// =================================================================================================== +// VIP-671 [BNB Chain Testnet] — Hub-Funded Spoke pool, PHASE 1: the spoke pool itself. +// +// SCOPE. This VIP covers only the work in isolated-pools#559: stand the spoke pool up, register it in +// its own registry, list its markets, and grant the permissions that pool needs. Two things are +// deliberately NOT here and ship as Phase 2: +// - Liquidity Hub integration (venus-liquidity-hub#22): addResource, addYieldGroup, the inner and +// outer queues, and `setAllowedSupplier` for the Hub's spoke source. +// - bStock liquidation (venus-protocol#707): BStockLiquidator is not deployed on this chain at all. +// +// STATUS. Address-complete. The whole spoke stack and the new ProtocolShareReserve implementation are +// deployed on bsctestnet, and every address here was read back from the chain rather than taken from a +// deployment record alone. +// +// --------------------------------------------------------------------------------------------------- +// WHY THIS POOL HAS A REGISTRY OF ITS OWN +// +// The registry is the directory every consumer iterates to answer "which pools exist": `getAllPools` +// drives the indexer, the frontend pool list and the risk tooling, and `getVTokenForAsset` is what +// ProtocolShareReserve uses as a membership check. A pool whose supply side is restricted to known +// accounts does not belong in that list, so isolated-pools#559 gives it a second `PoolRegistry` +// instance. Two consequences run through this whole VIP: +// - The spoke comptroller bakes that registry in as a CONSTRUCTOR IMMUTABLE and `supportMarket` +// checks `msg.sender == poolRegistry`. There is no `setPoolRegistry` on this fork, so the +// implementation must have been constructed with SPOKE_POOL_REGISTRY. Nothing in this VIP can fix +// a mismatch. +// - The ACM wildcard grants that let the isolated-pools registry configure a comptroller name THAT +// registry as the account. The spoke registry inherits none of them. See step 2. +// +// --------------------------------------------------------------------------------------------------- +// ORDER IS LOAD-BEARING. Do not reshuffle these: +// +// 1. Both `acceptOwnership()` calls FIRST. The deploy scripts leave the comptroller and the registry +// Ownable2Step-nominated, so the deployer is still the live owner and every owner-gated setter +// reverts until these land. (The two beacons are plain `Ownable` and are handed over inside the +// deploy transaction, so they need no command.) +// 2. The registry's six ACM grants BEFORE `addPool`. `PoolRegistry` drives those setters on the +// comptroller as `msg.sender`; without them `addPool` reverts at execution. +// 3. `setPriceOracle` BEFORE `addPool`. `addPool` dereferences `comptroller.oracle()` and rejects +// the zero address. +// 4. `setDeviationBoundedOracle` BEFORE the pool serves anyone. `_updateProtectionStates` calls into +// it with no zero check on every borrow, and on every redeem by an account that has entered a +// market, so both fail closed while it is unset. Supplying still works, since `preMintHook` reads +// no price. +// 5. `addMarket` BEFORE `setSupplyAllowlistEnabled`. `addMarket` seeds initial supply through +// `mintBehalf`, and `preMintHook` gates the account CREDITED with the vTokens, so arming the +// allowlist first would block the registry's own seed mint. It cannot be armed first anyway: +// `setSupplyAllowlistEnabled` reverts `MarketNotListed` on an unlisted market. +// +// --------------------------------------------------------------------------------------------------- +// PERMISSIONS ALREADY COVERED ON THIS CHAIN +// The Normal Timelock holds wildcard (address(0)-keyed) grants for every shared Comptroller, VToken +// and PoolRegistry role this listing uses, and a wildcard reaches a brand-new contract, so the new +// comptroller and the new registry inherit them. Only what is in ./permissions.ts is missing. +// +// Those inherited grants are the assumption this VIP is most exposed to, because nothing in the +// proposal touches them and a revocation would only surface at execution. They are therefore listed +// as `ASSUMED_WILDCARD_ROLES` in ./permissions.ts rather than as prose, and asserted BEFORE the +// proposal runs in simulations/vip-671/bsctestnet.ts, together with the two ownership assumptions +// step 4 rests on. Re-running that simulation is what re-verifies them. +// +// --------------------------------------------------------------------------------------------------- +// CROSS-REPO DEPENDENCY — see step 4. +// ProtocolShareReserve stores ONE pool registry and rejects income from any non-core pool that +// registry does not know. `VToken` calls `updateAssetsState` unconditionally when reducing reserves +// AND when seizing the protocol's share of liquidated collateral, so a spoke pool whose registry PSR +// does not know has EVERY LIQUIDATION REVERT, after the collateral has already moved. +// protocol-reserve#168 fixes this by letting PSR resolve through more than one registry. This VIP +// carries the proxy upgrade and the `addPoolRegistry` call together, which that PR requires. +// =================================================================================================== + +/// Mint the seed to the Timelock, list the market through the spoke registry, then drop the approval +/// back to zero. +/// +/// The mocked USDT and USDC on this chain expose `allocateTo(address,uint256)`, NOT the +/// `faucet(uint256)` the bStock mocks carry, so no treasury withdrawal is needed. Verified against the +/// deployed bytecode: the `faucet` selector is absent from both. Same pattern as VIP-195 and VIP-198. +const listMarket = (m: SpokeMarket): Command[] => [ + { + target: m.vToken, + signature: "setReduceReservesBlockDelta(uint256)", + params: [REDUCE_RESERVES_BLOCK_DELTA], + }, + { + target: m.vToken, + signature: "setReserveFactor(uint256)", + params: [m.reserveFactor], + }, + { + target: m.underlying, + signature: "allocateTo(address,uint256)", + params: [NORMAL_TIMELOCK, m.initialSupply], + }, + { + target: m.underlying, + signature: "approve(address,uint256)", + params: [SPOKE_POOL_REGISTRY, m.initialSupply], + }, + { + target: SPOKE_POOL_REGISTRY, + signature: "addMarket((address,uint256,uint256,uint256,address,uint256,uint256))", + params: [ + [m.vToken, m.collateralFactor, m.liquidationThreshold, m.initialSupply, VTREASURY, m.supplyCap, m.borrowCap], + ], + }, + { + target: m.underlying, + signature: "approve(address,uint256)", + params: [SPOKE_POOL_REGISTRY, 0], + }, +]; + +export const vip671 = () => { + const meta = { + version: "v2", + title: "VIP-671 [BNB Chain Testnet] Hub-Funded Spoke pool: list the pool and its markets", + description: `#### Summary + +If passed, this VIP will register the first **Hub-Funded Spoke pool** on BNB Chain testnet, in a pool registry of its own, and list its two markets. + +A Hub-Funded Spoke pool is one pool whose two sides are controlled separately: + +- **Liquidity side (USDT)** — borrowable, but supply is restricted to a per-market allowlist. The protocol therefore meters exactly how much liquidity the pool holds and what its utilisation is. +- **Collateral side (USDC)** — permissionless to supply, and usable as collateral against a USDT borrow. + +Both markets are capped, and the pool has its own Comptroller and its own registry, so a depeg, an oracle problem or bad debt in this pool is contained to it and cannot reach the Core pool or the existing isolated pools. + +Risk parameters mirror the isolated pools' Stablecoins pool on this network. This pool restricts who may supply, borrow and liquidate; it does not take more risk per market. + +#### Proposed changes + +1. Accept ownership of the new spoke Comptroller and of the new spoke pool registry, and point the Comptroller at the **ResilientOracle** and the **DeviationBoundedOracle**. The second is required before the pool serves any borrow or redeem. +2. Grant the spoke registry the six Comptroller setters it drives while registering a pool, and grant both the Normal Timelock and the Guardian the roles that exist only on this pool: the supply allowlist, the liquidation allowlist, and the per-market liquidation incentive, plus forced liquidation. Nothing is granted to the Fast-track or the Critical timelock. +3. Upgrade the **ProtocolShareReserve** so it can resolve markets through more than one pool registry, and register the spoke registry alongside the existing one. Without this the pool cannot report income and its liquidations would revert. The existing isolated pools are unaffected: their registry stays the primary one and is still checked first. +4. Move the risk fund's 20% share of protocol income from the legacy **RiskFundConverter** to **RiskFundBuyback**, on both income schemas. BNB Chain mainnet made this same move in VIP-618 and testnet was left behind, and the old contract is the one income destination that cannot resolve a pool outside its own registry. The share itself is unchanged, only the destination. +5. Register the pool and list both markets, each seeded with 10,000 of its underlying, capped at 1,000,000 supply and 400,000 borrow, with an 80% collateral factor and an 88% liquidation threshold. +6. Set the per-market liquidation discount on both markets, and restrict supply on the USDT market to its allowlist. + +#### Notes + +- Testnet only. No Hub-Funded Spoke pool is deployed on any mainnet. +- The Guardian's grants are for testing this pool, so that adding a test supplier, lifting an allowlist or forcing a liquidation does not need a proposal each time. They apply to this pool on this network only, and a mainnet listing would keep these roles with the Normal Timelock. The Guardian already sets collateral factors and caps on this network, which are the parameters that decide when a position can be liquidated. +- This is the first of two proposals. Connecting the pool's liquidity side to the Liquidity Hub is a separate proposal. +- The USDT market's supply allowlist ships **enabled with no members**, so nobody can supply it until the second proposal authorises the Hub's spoke source. Redeeming is never restricted, and the seed supply minted at listing is unaffected. +- Exit is never gated: repay, redeem, withdraw and transfer stay permissionless, and the pool keeps the usual market-level pause controls. +- Liquidation stays permissionless. The optional liquidation allowlist ships disabled. +- Both markets keep the 10% reserve factor and 5% protocol seize share they were deployed with.`, + 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. Take ownership of both Ownable2Step contracts. Everything owner-gated below depends on + // these two landing first. + // + // Not here on purpose: SpokeComptrollerBeacon and the spoke VTokenBeacon are plain `Ownable` + // and the deploy scripts transfer them inside the deploy transaction, and the SpokePoolLens + // has no owner, no AccessControlManager and no state-changing function at all. None of the + // three needs a governance command. See ./addresses/bsctestnet.ts. + // ------------------------------------------------------------------------------------------- + { target: SPOKE_POOL_REGISTRY, signature: "acceptOwnership()", params: [] }, + { target: SPOKE_COMPTROLLER, signature: "acceptOwnership()", params: [] }, + + // ------------------------------------------------------------------------------------------- + // 2. ACM grants. + // + // 2a. The six setters the registry drives on the comptroller during `addPool`/`addMarket`. + // The account is the SPOKE REGISTRY, not a timelock. The identical wildcard grants that + // already exist name the isolated-pools registry, so none of them carry over and + // `addPool` in step 5 reverts without these. + // ------------------------------------------------------------------------------------------- + ...REGISTRY_DRIVEN_ROLES.map(signature => + giveCallPermission(ACM, SPOKE_COMPTROLLER, signature, SPOKE_POOL_REGISTRY), + ), + + // 2b. The roles that exist only on this fork, plus forced liquidation. Verified not granted to + // any timelock or to the Guardian on this chain. Everything else the listing needs is + // already held by the timelocks as a wildcard, so it is not re-granted. See + // ./permissions.ts. + // + // The Fast-track and Critical timelocks get nothing from this VIP. Their pre-existing + // `setActionsPaused(address[],uint256[],bool)` wildcards already reach this comptroller, + // so the emergency pause path is open from the first block without adding anything here. + ...SPOKE_COMPTROLLER_ROLES.map(signature => + giveCallPermission(ACM, SPOKE_COMPTROLLER, signature, NORMAL_TIMELOCK), + ), + + // 2c. The same six to the Guardian multisig. TESTNET ONLY, and the reasoning belongs with the + // grant: this pool exists to be tested, and these six are exactly the knobs a test has to + // turn in both directions. Add and remove a supplier on the liquidity market, arm and lift + // either allowlist, and force a liquidatable position instead of waiting for a depeg. + // On the Normal Timelock alone, each of those is a governance proposal, which delays the + // testing without restricting anything that matters. + // + // Narrower than it reads: the Guardian already holds `setCollateralFactor`, + // `setMarketSupplyCaps` and `setMarketBorrowCaps` as wildcards here, which are the + // parameters that decide when an account is underwater, so it can already push a position + // into shortfall on any pool on this chain. What it gains is who may supply and who may + // liquidate on ONE testnet pool. + // + // Do NOT carry this block into a mainnet listing. There these six stay with the Normal + // Timelock. + ...SPOKE_COMPTROLLER_ROLES.map(signature => giveCallPermission(ACM, SPOKE_COMPTROLLER, signature, GUARDIAN)), + + // ------------------------------------------------------------------------------------------- + // 3. Oracles. Both are onlyOwner, so they follow acceptOwnership; `setPriceOracle` also has to + // precede `addPool`, which rejects a zero oracle. + // + // Both oracles key their config on the UNDERLYING (the bounded oracle resolves + // `vToken.underlying()` first), so the new spoke vTokens inherit whatever USDT and USDC + // already carry and the ResilientOracle, which prices both, needs no command. + // ------------------------------------------------------------------------------------------- + { target: SPOKE_COMPTROLLER, signature: "setPriceOracle(address)", params: [RESILIENT_ORACLE] }, + { + target: SPOKE_COMPTROLLER, + signature: "setDeviationBoundedOracle(address)", + params: [DEVIATION_BOUNDED_ORACLE], + }, + + // ------------------------------------------------------------------------------------------- + // 3b. Give USDC the price window USDT already has, so both sides of the pool are priced the same + // way. Without this the collateral market prices at spot while the liquidity market prices + // through a bounded window: not a revert, `_updateAndGetBoundedPrices` returns spot for an + // asset with bounded pricing disabled, but an inconsistency inside one pool. + // + // Every parameter is USDT's live configuration, read off the oracle rather than chosen here. + // The oracle seeds the window itself, reading the current spot into both minPrice and + // maxPrice, so no price is passed and there is nothing to time. + // + // USDT is deliberately NOT re-configured: `_setTokenConfig` reverts `MarketAlreadyInitialized` + // for an asset that already has a config, so this is the collateral market only. + // + // No ACM grant needed, all three timelocks already hold this role against the oracle. The + // role string is the EXPANDED tuple, not the struct name, unlike PoolRegistry's `addMarket`. + // ------------------------------------------------------------------------------------------- + { + target: DEVIATION_BOUNDED_ORACLE, + signature: "setTokenConfig((address,uint64,uint256,uint256,bool,bool))", + params: [ + [ + COLLATERAL_MARKET.underlying, + DBO_COOLDOWN_PERIOD, + DBO_TRIGGER_THRESHOLD, + DBO_RESET_THRESHOLD, + DBO_ENABLE_BOUNDED_PRICING, + DBO_ENABLE_CACHING, + ], + ], + }, + + // ------------------------------------------------------------------------------------------- + // 4. ProtocolShareReserve: upgrade to the multi-registry implementation, then register the spoke + // registry alongside the existing one. Both calls are owner-gated and both owners are the + // Normal Timelock (the proxy admin's owner and PSR's owner), so neither needs an ACM grant. + // + // THESE TWO MUST STAY TOGETHER, IN THIS ORDER, IN THIS PROPOSAL. protocol-reserve#168 makes + // that a rollout requirement: the upgrade alone leaves the spoke registry unknown, and + // `addPoolRegistry` does not exist before it. + // + // Why it is load-bearing rather than housekeeping: PSR rejects income from a non-core pool + // its registries do not know, and `VToken` calls `updateAssetsState` unconditionally both in + // `_reduceReservesFresh` and in the protocol-seize path (VToken.sol:1314). Without this, every + // liquidation in this pool reverts, after the collateral has already moved. + // + // Do NOT substitute `setPoolRegistry`. It REPLACES the primary registry, which would stop + // every live isolated pool from taking income and revert their liquidations the block it + // lands. Both directions of that trade are pinned in + // isolated-pools/tests/hardhat/Fork/HubSpoke/psrRegistryConflict.ts. After this VIP the + // isolated-pools registry stays primary and is probed first; the spoke registry is additional. + // + // The upgrade is storage-safe on its own: new state is appended at slots 305/306, 301-304 are + // untouched, so there is no reinitializer and no migration. `maxLoopsLimit` is 20 on this + // chain, so the one additional registry clears the bound `addPoolRegistry` enforces. + // + // This resolves PSR itself. The one income DESTINATION that still cannot resolve this pool is + // handled separately in step 4b. + // ------------------------------------------------------------------------------------------- + { + target: DEFAULT_PROXY_ADMIN, + signature: "upgrade(address,address)", + params: [PROTOCOL_SHARE_RESERVE, PROTOCOL_SHARE_RESERVE_IMPL], + }, + { + target: PROTOCOL_SHARE_RESERVE, + signature: "addPoolRegistry(address)", + params: [SPOKE_POOL_REGISTRY], + }, + + // ------------------------------------------------------------------------------------------- + // 4b. Move the risk-fund income share off `RiskFundConverter`, which is the one destination that + // would still revert for this pool. + // + // `_releaseFund` transfers to every destination and then calls `updateAssetsState` on it, + // unguarded (ProtocolShareReserve.sol:491). `RiskFundConverter` resolves the paying pool + // through a single `poolRegistry` of its own, which step 4 does not touch, and reverts + // `MarketNotExistInPool` for a comptroller it does not know. So without this, + // `releaseFunds(SPOKE_COMPTROLLER, ...)` reverts the moment this pool has income. + // Reproduced against the live contracts: called as ProtocolShareReserve with an unknown + // comptroller, `RiskFundConverter` reverts 0x983f1fb5 and every other destination on this + // chain returns cleanly. + // + // This is drift, not a decision: bscmainnet retired `RiskFundConverter` in VIP-618 and moved + // the same 20% of both schemas to `RiskFundBuyback`, but that VIP's testnet leg never + // repointed the distribution config. `RiskFundBuyback` is already deployed here, so this is + // the mainnet shape rather than a new allocation, and the totals are unchanged. + // + // Both rows move in ONE call because `_ensurePercentages` runs at the end of it and requires + // each schema to total exactly 100% or 0. Zeroing the old rows in a separate proposal would + // leave both schemas at 80% and revert. + // Schema 0 is SPREAD, schema 1 is LIQUIDATION. Both currently sit at 2000 bps. + // + // No ACM grant needed: all three timelocks already hold both role strings against PSR + // (checked on chain). The strings are `addOrUpdateDistributionConfigs(DistributionConfig[])` + // and `removeDistributionConfig(Schema,address)`, which carry the struct and enum names + // rather than the expanded types, so they differ from the call signatures below. + // ------------------------------------------------------------------------------------------- + { + target: PROTOCOL_SHARE_RESERVE, + signature: "addOrUpdateDistributionConfigs((uint8,uint16,address)[])", + params: [ + [ + [SCHEMA_SPREAD, 0, RISK_FUND_CONVERTER], + [SCHEMA_LIQUIDATION, 0, RISK_FUND_CONVERTER], + [SCHEMA_SPREAD, RISK_FUND_SHARE_BPS, RISK_FUND_BUYBACK], + [SCHEMA_LIQUIDATION, RISK_FUND_SHARE_BPS, RISK_FUND_BUYBACK], + ], + ], + }, + // `removeDistributionConfig` only deletes a row whose percentage is already zero, which the call + // above has just made true. Without these the zeroed rows stay in the array and keep costing a + // transfer-check loop iteration on every release. + { + target: PROTOCOL_SHARE_RESERVE, + signature: "removeDistributionConfig(uint8,address)", + params: [SCHEMA_SPREAD, RISK_FUND_CONVERTER], + }, + { + target: PROTOCOL_SHARE_RESERVE, + signature: "removeDistributionConfig(uint8,address)", + params: [SCHEMA_LIQUIDATION, RISK_FUND_CONVERTER], + }, + + // ------------------------------------------------------------------------------------------- + // 5. Register the pool. The registry pushes closeFactor, the pool-wide liquidation incentive and + // minLiquidatableCollateral into the Comptroller itself, under the grants from step 2a. + // The pool-wide incentive must be >= 1.05e18: this fork raises the upstream 1e18 floor to + // 1e18 + the VToken default protocol seize share. + // ------------------------------------------------------------------------------------------- + { + target: SPOKE_POOL_REGISTRY, + signature: "addPool(string,address,uint256,uint256,uint256)", + params: [POOL_NAME, SPOKE_COMPTROLLER, CLOSE_FACTOR, POOL_LIQUIDATION_INCENTIVE, MIN_LIQUIDATABLE_COLLATERAL], + }, + + // ------------------------------------------------------------------------------------------- + // 6. List the markets. Liquidity market first, so it is listed and seeded before its supply + // allowlist is armed in step 8. + // ------------------------------------------------------------------------------------------- + ...MARKETS.flatMap(listMarket), + + // ------------------------------------------------------------------------------------------- + // 7. Per-market liquidation discounts, keyed on the COLLATERAL market: the discount prices the + // collateral being seized, not the debt being repaid (PRD FR-5). A market left unset inherits + // the pool-wide value; pinning it explicitly keeps it from moving if the pool default is + // retuned later, and exercises the spoke-only setter on this chain. + // + // Floor: 1e18 + that market's `protocolSeizeShareMantissa`. Both markets were deployed with + // the 5% default, so the floor is 1.05e18 and the 1.1e18 set here clears it. To go BELOW that, + // `VToken.setProtocolSeizeShare` has to be lowered FIRST: it reads the incentive back through + // `liquidationIncentiveMantissa()`, which resolves per calling market, and rejects + // `share + 1e18 > incentive`. The two setters bound each other. + // + // `setProtocolSeizeShare` is deliberately not called: both markets keep the 5% they were + // deployed with. + // ------------------------------------------------------------------------------------------- + ...MARKETS.map( + (m): Command => ({ + target: SPOKE_COMPTROLLER, + signature: "setMarketLiquidationIncentive(address,uint256)", + params: [m.vToken, m.liquidationIncentive], + }), + ), + + // ------------------------------------------------------------------------------------------- + // 8. Arm the supply allowlist on the liquidity market, with NO members. + // + // This is the Phase 1 boundary, and it is the safe side of it. The market is closed to supply + // from the moment it is listed, so it can never be supplied permissionlessly in the window + // before the Hub arrives. Phase 2 adds the Hub's spoke source with + // `setAllowedSupplier(vUSDT_HubSpoke, , true)`. + // + // The allowlist meters the account CREDITED with the vTokens, not the one paying, and it + // gates `preMintHook` only. Redeeming is never restricted, and the seed supply minted to the + // treasury during `addMarket` is unaffected. + // + // The USDC market is left permissionless: PRD FR-4 makes the collateral-deposit allowlist + // optional and off by default. + // ------------------------------------------------------------------------------------------- + { + target: SPOKE_COMPTROLLER, + signature: "setSupplyAllowlistEnabled(address,bool)", + params: [LIQUIDITY_MARKET.vToken, true], + }, + + // ------------------------------------------------------------------------------------------- + // NOT IN THIS PROPOSAL, and not an oversight: + // + // - `setLiquidationAllowlistEnabled(false)` — the pool-wide liquidation allowlist already + // defaults to disabled, so a command setting it to false would be a no-op. The role is + // granted in steps 2b and 2c so it can be turned on and off during testing. + // - `setAllowedSupplier` for any account — the liquidity market ships closed and the Guardian + // can add a supplier once QA needs one, without a proposal. Phase 2 adds the Hub's source. + // - `enterMarketBehalf(address,address)` — granted only to a router that passes its own caller + // through as `account`, and no router is deployed on this chain. See ./permissions.ts. + // - `addRewardsDistributor` — no rewards programme is defined for this pool. + // - Liquidity Hub wiring and the bStock liquidation leg — Phase 2, see the scope note above. + // + // Covered by simulations/vip-671/bsctestnet.ts, which asserts the pre-VIP deployment state, the + // permissions this VIP assumes rather than grants, every command, and the post-VIP state. It also + // pins the two things most expensive to get wrong: that `SpokeComptroller.poolRegistry()` is the + // spoke registry, a constructor immutable no VIP can fix, and that the live isolated pools still + // resolve through ProtocolShareReserve after the upgrade. + // ------------------------------------------------------------------------------------------- + ], + meta, + ProposalType.REGULAR, + ); +}; + +export default vip671; diff --git a/vips/vip-671/config.ts b/vips/vip-671/config.ts new file mode 100644 index 000000000..4baeb8f94 --- /dev/null +++ b/vips/vip-671/config.ts @@ -0,0 +1,133 @@ +import { BigNumber } from "ethers"; +import { parseUnits } from "ethers/lib/utils"; + +import { IRM_SPOKE, USDC, USDT, VUSDC_SPOKE, VUSDT_SPOKE } from "./addresses/bsctestnet"; + +// =================================================================================================== +// VIP-671 — Hub-Funded Spoke pool parameters (BNB Chain Testnet), PHASE 1. +// +// Every value here is copied from isolated-pools helpers/spokeDeploymentConfig.ts, the file the +// deployment was run from, so the listing matches what was deployed rather than a second opinion. +// That config states its own rationale: the risk parameters mirror the isolated pools' Stablecoins +// pool on the same network, because the spoke pool restricts WHO may supply, borrow and liquidate +// rather than taking more risk per market. +// +// The split that makes this a spoke pool is not deployment input. Both markets are listed with the +// same risk parameters, and the liquidity side is then metered by arming its supply allowlist on +// `SpokeComptroller` after `addMarket`. See ./bsctestnet.ts step 8. +// =================================================================================================== + +/// From spokeDeploymentConfig: closeFactor 0.5, liquidationIncentive 1.1, minLiquidatableCollateral +/// 100 USD. `SpokeComptroller.setLiquidationIncentive` rejects anything below +/// MIN_POOL_LIQUIDATION_INCENTIVE_MANTISSA = 1.05e18 (SpokeComptrollerStorage.sol:138), which is +/// 1e18 + the VToken default protocol seize share of 5%. This fork raises that floor from the +/// upstream 1e18 so a pool cannot be registered paying a default-share market's liquidator less +/// collateral than it repaid. 1.1e18 clears it. +export const CLOSE_FACTOR = parseUnits("0.5", 18); +export const POOL_LIQUIDATION_INCENTIVE = parseUnits("1.1", 18); +export const MIN_LIQUIDATABLE_COLLATERAL = parseUnits("100", 18); // USD + +/// Matches the deployment record's pool name exactly. +export const POOL_NAME = "Hub-funded spoke"; + +/// From spokeDeploymentConfig. Lower than the 28800 the BNB Chain isolated markets run, which suits a +/// testnet where reserves should surface quickly rather than once a day. +export const REDUCE_RESERVES_BLOCK_DELTA = "100"; + +export type SpokeMarket = { + symbol: string; + vToken: string; + underlying: string; + underlyingDecimals: number; + interestRateModel: string; + collateralFactor: BigNumber; + liquidationThreshold: BigNumber; + /// `SpokeComptroller.setMarketLiquidationIncentive`, keyed on the COLLATERAL market: the discount + /// prices the collateral being seized, not the debt being repaid (PRD FR-5). + /// Floor: 1e18 + that market's `protocolSeizeShareMantissa` (SpokeComptroller.sol:1220), so 1.05e18 + /// at the 5% both markets were deployed with. + liquidationIncentive: BigNumber; + supplyCap: BigNumber; + borrowCap: BigNumber; + reserveFactor: BigNumber; + initialSupply: BigNumber; +}; + +/// Shared by both markets, straight from spokeDeploymentConfig. Both underlyings have 6 decimals on +/// this chain, so the amounts are 1e6 scaled, as the isolated Stablecoins pool has them here. +const shared = { + underlyingDecimals: 6, + interestRateModel: IRM_SPOKE, + collateralFactor: parseUnits("0.8", 18), + liquidationThreshold: parseUnits("0.88", 18), + reserveFactor: parseUnits("0.1", 18), + initialSupply: parseUnits("10000", 6), + supplyCap: parseUnits("1000000", 6), + borrowCap: parseUnits("400000", 6), + /// Pinned to the pool-wide value rather than differentiated. PRD FR-5 wants this varied by collateral + /// volatility, which is what the per-market setter is for, but both markets here are stablecoins with + /// identical risk parameters, so there is nothing to differentiate yet. Setting it explicitly keeps + /// each market where it is if the pool default is ever retuned, and exercises the spoke-only setter. + liquidationIncentive: parseUnits("1.1", 18), +}; + +/// The liquidity side. Supply is metered by the allowlist armed in step 8; borrowing is open to any +/// account with collateral in the pool. +export const MARKET_USDT: SpokeMarket = { + ...shared, + symbol: "vUSDT_HubSpoke", + vToken: VUSDT_SPOKE, + underlying: USDT, +}; + +/// The collateral side. Supply stays permissionless: PRD FR-4 makes the collateral-deposit allowlist +/// optional and off by default, and this VIP leaves it off. +export const MARKET_USDC: SpokeMarket = { + ...shared, + symbol: "vUSDC_HubSpoke", + vToken: VUSDC_SPOKE, + underlying: USDC, +}; + +/// Liquidity market first, so the market whose supply this pool meters is listed and seeded before its +/// allowlist is armed. +export const MARKETS: SpokeMarket[] = [MARKET_USDT, MARKET_USDC]; + +/// The market whose supply this VIP restricts. Phase 2 adds the Hub's spoke source to its allowlist. +export const LIQUIDITY_MARKET = MARKET_USDT; + +/// The permissionless side. Its underlying is the one that still needs a bounded price window. +export const COLLATERAL_MARKET = MARKET_USDC; + +// --------------------------------------------------------------------------------------------------- +// DeviationBoundedOracle configuration for the collateral market's underlying. +// +// USDT already carries a price window on this chain; USDC carried none, so its collateral was priced +// at spot while USDT's was bounded. Every value below is USDT's live config, read off the oracle, so +// the two sides of this pool are priced the same way rather than one being given a window someone +// picked for it. +// +// The oracle seeds the window itself: `_setTokenConfig` reads the current spot and writes it to both +// minPrice and maxPrice, so there is nothing to pass and nothing to time. +// --------------------------------------------------------------------------------------------------- + +/// Seconds protection stays active after the last trigger. +export const DBO_COOLDOWN_PERIOD = 3600; +/// Entry deviation. Sits exactly on the oracle's MIN_THRESHOLD of 5e16, which the check allows. +export const DBO_TRIGGER_THRESHOLD = parseUnits("0.05", 18); +/// Exit deviation. Must be strictly below the trigger. +export const DBO_RESET_THRESHOLD = parseUnits("0.02", 18); +/// Bounded pricing on from the start, transient caching off. Both match USDT. +export const DBO_ENABLE_BOUNDED_PRICING = true; +export const DBO_ENABLE_CACHING = false; + +// --------------------------------------------------------------------------------------------------- +// ProtocolShareReserve income distribution. `IProtocolShareReserve.Schema`, in declaration order. +// --------------------------------------------------------------------------------------------------- +export const SCHEMA_SPREAD = 0; +export const SCHEMA_LIQUIDATION = 1; + +/// The risk fund's existing share of both schemas. Carried over unchanged: this VIP moves the row from +/// `RiskFundConverter` to `RiskFundBuyback`, it does not retune the allocation. bscmainnet holds the +/// same 2000 bps on both schemas. +export const RISK_FUND_SHARE_BPS = 2000; diff --git a/vips/vip-671/permissions.ts b/vips/vip-671/permissions.ts new file mode 100644 index 000000000..5d1166aac --- /dev/null +++ b/vips/vip-671/permissions.ts @@ -0,0 +1,198 @@ +// =================================================================================================== +// VIP-671 — ACM role strings for the Hub-Funded Spoke pool (Phase 1). +// +// Every string is the literal argument passed to `_checkAccessAllowed(...)` in the contract, copied +// verbatim from isolated-pools/contracts/Spoke/SpokeComptroller.sol. The ACM hashes +// `keccak256(targetContract, roleString)`, so a string that merely looks right grants a role that +// nothing ever checks. +// +// --------------------------------------------------------------------------------------------------- +// HOW THE WILDCARD WORKS, AND WHY IT COVERS ONE SIDE BUT NOT THE OTHER +// +// AccessControlManager.isAllowedToCall(account, sig) tries keccak256(msg.sender, sig) first and +// falls back to keccak256(address(0), sig). So a grant made against address(0) is a WILDCARD over +// every target contract, and a brand-new contract inherits it. The ACCOUNT in the grant is exact. +// +// That asymmetry is the whole story here: +// - The timelocks already hold wildcard grants for the shared Comptroller / VToken / PoolRegistry +// roles, so those carry to the new comptroller and the new registry for free. +// - The six setters PoolRegistry drives while registering a pool are wildcard-granted too, but to +// the ISOLATED-POOLS registry address. The spoke pool has a registry of its own, which is a +// different account, so it inherits nothing and `addPool` reverts without the grants below. +// +// --------------------------------------------------------------------------------------------------- +// VERIFIED ON CHAIN (bsctestnet, ACM 0x45f8…a9AA), by reading +// `hasRole(keccak256(address(0), sig), account)`: +// +// ALREADY COVERED, so this VIP does NOT re-grant them. That list is `ASSUMED_WILDCARD_ROLES` and +// `ASSUMED_GUARDIAN_ROLES` at the bottom of this file, deliberately as code rather than as prose: a +// comment claiming a permission exists is exactly the assumption that turns into a failed execution, +// so every entry is asserted BEFORE the VIP runs in simulations/vip-671/bsctestnet.ts. A revocation +// between now and the proposal then fails the simulation instead of the proposal. +// +// NOT COVERED for any timelock, for the Guardian, or for anyone else, and therefore granted by this +// VIP: `REGISTRY_DRIVEN_ROLES` and `SPOKE_COMPTROLLER_ROLES`. Verified false for the Guardian too. +// +// --------------------------------------------------------------------------------------------------- +// GRANTEES: THE NORMAL TIMELOCK AND THE GUARDIAN. +// +// Both receive the same six roles. The Fast-track and Critical timelocks receive nothing. +// +// WHY THE GUARDIAN, AND WHY ONLY HERE. This pool exists on bsctestnet to be tested, and every one of +// these six roles is a knob QA has to turn in both directions to exercise it at all: add and remove a +// test supplier, arm and lift either allowlist, and switch forced liquidation on to produce a +// liquidatable position without waiting for a real depeg. Leaving them on the Normal Timelock alone +// means a governance proposal per test case, which is not a control, just a delay. The Guardian is +// the multisig that already carries the day-to-day testnet knobs on this chain. +// +// This is a TESTNET decision. A mainnet spoke pool should grant these to the Normal Timelock alone; +// do not copy this block into a mainnet listing. +// +// It is also a narrower change than it looks. The Guardian already holds `setCollateralFactor`, +// `setMarketSupplyCaps` and `setMarketBorrowCaps` as wildcards on this chain, and those are the +// parameters that decide when a position is underwater, so it can already put an account into +// shortfall on any pool. What it gains here is who may supply and who may liquidate on ONE testnet +// pool, not a class of control it did not have. +// +// One thing this does NOT change: all three timelocks can already call +// `setActionsPaused(address[],uint256[],bool)` through a pre-existing wildcard that reaches any new +// comptroller, so the emergency pause path on this pool is open from the first block. The Guardian +// does NOT hold that wildcard (verified false) and this VIP does not add it. +// +// NOTE on setActionsPaused. The role string is `setActionsPaused(address[],uint256[],bool)` while the +// CALL signature is `setActionsPaused(address[],uint8[],bool)`, because `Action` is an enum. They are +// different strings on purpose. Confirmed on chain: the `uint256[]` form is granted to all three +// timelocks and the `uint8[]` form is granted to none, so the `uint256[]` form is the live one. Do +// not "fix" either to match the other. +// (isolated-pools/tests/hardhat/Fork/HubSpoke/fixture.ts SPOKE_ROLES has the `uint8[]` form, which +// grants a role nothing checks. Reported upstream; it does not affect this VIP.) +// =================================================================================================== + +/** + * The five role strings that exist only on `SpokeComptroller`. No other Venus contract checks a string + * with these names, so no pre-existing wildcard can cover them. A pool listed without these is listed + * with its allowlists and its per-market liquidation incentives permanently unreachable. + */ +export const SPOKE_ONLY_ROLES = [ + "setMarketLiquidationIncentive(address,uint256)", + "setSupplyAllowlistEnabled(address,bool)", + "setAllowedSupplier(address,address,bool)", + "setLiquidationAllowlistEnabled(bool)", + "setAllowedLiquidator(address,bool)", +]; + +/** + * Shared with the pooled `Comptroller`, but NOT wildcard-granted on bsctestnet — verified false for all + * three timelocks and for the Guardian. It is granted per pool. Included because it is the only lever + * that makes a stablecoin position liquidatable on demand, which is how the liquidation path gets + * tested here without waiting for a real depeg. + */ +export const SPOKE_EXTRA_ROLES = ["setForcedLiquidation(address,bool)"]; + +export const SPOKE_COMPTROLLER_ROLES = [...SPOKE_ONLY_ROLES, ...SPOKE_EXTRA_ROLES]; + +/** + * The six setters `PoolRegistry` calls into the comptroller as `msg.sender` while running `addPool` + * and `addMarket`. Granted to the SPOKE registry, not to a timelock. + * + * Verified on chain: the isolated-pools registry holds all six as wildcards and the spoke registry + * holds none, so `addPool` reverts on execution without these. This is the single most likely way for + * this VIP to fail, because the wildcards make it look covered until you check the account. + */ +export const REGISTRY_DRIVEN_ROLES = [ + "setCloseFactor(uint256)", + "setLiquidationIncentive(uint256)", + "setMinLiquidatableCollateral(uint256)", + "setCollateralFactor(address,uint256,uint256)", + "setMarketSupplyCaps(address[],uint256[])", + "setMarketBorrowCaps(address[],uint256[])", +]; + +/** + * TODO(deploy): `enterMarketBehalf(address,address)` lets an approved router enter a market for the + * supplier it is minting on behalf of, so a first-time supplier needs one transaction instead of two. + * It is NOT granted here. The contract's own guidance is to grant it only to a router that passes its + * own caller through as `account`, and no such router is deployed on this chain. Granting it to a + * timelock would be meaningless and would widen the surface for nothing. Add the grant in the VIP that + * ships the router. + */ +export const ROUTER_ROLES = ["enterMarketBehalf(address,address)"]; + +/** + * The permissions this VIP RELIES ON but does not grant, because bsctestnet already holds them as + * address(0) wildcards and a wildcard reaches a contract deployed after it was made. Keyed by the + * contract the role is checked on, since the ACM hashes the target into the role. + * + * Every string is copied from the source that checks it, including the two that carry a struct or enum + * name rather than the expanded tuple, which is where this kind of list usually goes wrong: + * PoolRegistry.sol:162 addMarket(AddMarketInput) + * PoolRegistry.sol:226 updatePoolMetadata(address,VenusPoolMetaData) + * ProtocolShareReserve.sol:235 addOrUpdateDistributionConfigs(DistributionConfig[]) + * ProtocolShareReserve.sol:283 removeDistributionConfig(Schema,address) + * The DeviationBoundedOracle goes the other way and uses the expanded tuple + * (DeviationBoundedOracle.sol:302). + * + * Asserted pre-VIP in simulations/vip-671/bsctestnet.ts, for the accounts named against each group. + */ +export const ASSUMED_WILDCARD_ROLES = { + /// Held by the Normal Timelock. The first six are the same strings the spoke registry is granted in + /// this VIP: the wildcard covers the timelock as an account, it does not cover the registry. + comptroller: [ + "setCloseFactor(uint256)", + "setLiquidationIncentive(uint256)", + "setMinLiquidatableCollateral(uint256)", + "setCollateralFactor(address,uint256,uint256)", + "setMarketSupplyCaps(address[],uint256[])", + "setMarketBorrowCaps(address[],uint256[])", + "unlistMarket(address)", + ], + + /// Held by the Normal Timelock, on each spoke vToken. The VIP calls the first two directly. + vToken: [ + "setReduceReservesBlockDelta(uint256)", + "setReserveFactor(uint256)", + "setInterestRateModel(address)", + "setProtocolSeizeShare(uint256)", + ], + + /// Held by the Normal Timelock, on the SPOKE registry. The VIP calls the first two directly, and + /// they are the reason a brand-new registry needs no grant of its own for the timelock to drive it. + poolRegistry: [ + "addPool(string,address,uint256,uint256,uint256)", + "addMarket(AddMarketInput)", + "setPoolName(address,string)", + "updatePoolMetadata(address,VenusPoolMetaData)", + ], + + /// Held by all three timelocks. The VIP calls this on the collateral underlying. + deviationBoundedOracle: ["setTokenConfig((address,uint64,uint256,uint256,bool,bool))"], + + /// Held by all three timelocks. The VIP calls both. + protocolShareReserve: [ + "addOrUpdateDistributionConfigs(DistributionConfig[])", + "removeDistributionConfig(Schema,address)", + ], + + /// Held by all three timelocks, on the comptroller. Not called by this VIP: asserted because the + /// decision to grant nothing to the Fast-track and Critical timelocks rests on the pause path + /// already reaching this pool without them. + pause: ["setActionsPaused(address[],uint256[],bool)"], +}; + +/** + * Held by the Guardian on the comptroller as wildcards, before this VIP. Asserted because the case for + * the Guardian grants above rests on it: these are the parameters that decide when an account is in + * shortfall, so the Guardian can already put a position underwater on any pool on this chain. + */ +export const ASSUMED_GUARDIAN_ROLES = [ + "setCollateralFactor(address,uint256,uint256)", + "setMarketSupplyCaps(address[],uint256[])", + "setMarketBorrowCaps(address[],uint256[])", +]; + +/// ACM `giveCallPermission` command builder. +export const giveCallPermission = (acm: string, contract: string, signature: string, account: string) => ({ + target: acm, + signature: "giveCallPermission(address,string,address)", + params: [contract, signature, account], +});