From d792f859dd830570de7f8545175eda92589fa489 Mon Sep 17 00:00:00 2001 From: Taylor Brent Date: Tue, 15 Oct 2024 16:19:42 -0400 Subject: [PATCH 01/40] draft demurrage collateral --- .../SelfReferentialDemurrageCollateral.sol | 182 ++++++++++++++++++ 1 file changed, 182 insertions(+) create mode 100644 contracts/plugins/assets/SelfReferentialDemurrageCollateral.sol diff --git a/contracts/plugins/assets/SelfReferentialDemurrageCollateral.sol b/contracts/plugins/assets/SelfReferentialDemurrageCollateral.sol new file mode 100644 index 000000000..6c1aba210 --- /dev/null +++ b/contracts/plugins/assets/SelfReferentialDemurrageCollateral.sol @@ -0,0 +1,182 @@ +// SPDX-License-Identifier: BlueOak-1.0.0 +pragma solidity 0.8.19; + +import "../../interfaces/IAsset.sol"; +import "./Asset.sol"; + +struct DemurrageConfig { + bytes32 targetName; + uint192 decay; // {1/s} fraction of the target unit to decay each second + // + AggregatorV3Interface feed0; + AggregatorV3Interface feed1; + uint48 timeout0; // {s} + uint192 error0; // {1} + uint48 timeout1; // {s} + uint192 error1; // {1} +} + +/** + * @title SelfReferentialDemurrageCollateral + * @notice Collateral plugin for a self-referential demurrage collateral, i.e /w a management fee + * + * No default detection + * Demurrage collateral implement a management fee in the form of a decaying exponential + * + * refPerTok = up-only + * targetPerRef = down-only + * product = constant + * + * If 1 feed: + * - feed0 must be {UoA/tok} + * - does not implement issuance premium + * If 2 feeds: + * - feed0 must be {target/tok} + * - feed1 must be {UoA/target} + * - implements issuance premium + * + * t0 = a previous (or current) moment in time, used as reference for the decay + * + * For Target Unit X: + * - tok = Tokenized X + * - ref = X @ t0 /w decay + * - target = X + * - UoA = USD + */ +contract SelfReferentialDemurrageCollateral is ICollateral, Asset { + using FixLib for uint192; + using OracleLib for AggregatorV3Interface; + + // === Old stuff === + + CollateralStatus public immutable status = CollateralStatus.SOUND; // never default + bytes32 public immutable targetName; + uint192 public savedPegPrice; // {target/ref} The peg price of the token during the last update + + // === New stuff === + + uint48 public immutable t0; // {s} deployment timestamp + uint192 public immutable decay; // {1/s} fraction of the target unit to decay each second + + // For each token, we maintain up to two feeds/timeouts/errors + // The data below would normally be a struct, but we want bytecode substitution + + AggregatorV3Interface internal immutable feed0; // {UoA/tok} or {target/tok} + AggregatorV3Interface internal immutable feed1; // empty or {UoA/target} + uint48 internal immutable timeout0; // {s} + uint48 internal immutable timeout1; // {s} + uint192 internal immutable error0; // {1} + uint192 internal immutable error1; // {1} + + bool internal immutable targetIsUoA; + + /// @param priceTimeout_ {s} The number of seconds over which savedHighPrice decays to 0 + /// @param maxTradeVolume_ {UoA} The max trade volume, in UoA + /// @param oracleTimeout_ {s} The number of seconds until the chainlinkFeed becomes invalid + constructor( + uint48 priceTimeout_, + IERC20Metadata erc20_, + uint192 maxTradeVolume_, + uint48 oracleTimeout_, + DemurrageConfig memory demurrageConfig, + bool targetIsUoA_ + ) + Asset( + priceTimeout_, + demurrageConfig.feed0, + demurrageConfig.error0, + erc20_, + maxTradeVolume_, + oracleTimeout_ + ) + { + require(address(demurrageConfig.feed0) != address(0), "missing feed0"); + require(demurrageConfig.timeout0 != 0, "missing timeout0"); + require(demurrageConfig.error0 > 0 && demurrageConfig.error0 < FIX_ONE, "bad error0"); + + if (address(demurrageConfig.feed1) != address(0)) { + require(demurrageConfig.timeout1 != 0, "missing timeout1"); + require(demurrageConfig.error1 > 0 && demurrageConfig.error1 < FIX_ONE, "bad error1"); + } + + t0 = uint48(block.timestamp); + decay = demurrageConfig.decay; + feed0 = demurrageConfig.feed0; + feed1 = demurrageConfig.feed1; + timeout0 = demurrageConfig.timeout0; + timeout1 = demurrageConfig.timeout1; + error0 = demurrageConfig.error0; + error1 = demurrageConfig.error1; + + targetName = demurrageConfig.targetName; + targetIsUoA = targetIsUoA_; + } + + /// Can revert, used by other contract functions in order to catch errors + /// Should NOT be manipulable by MEV + /// @return low {UoA/tok} The low price estimate + /// @return high {UoA/tok} The high price estimate + /// @return pegPrice {target/ref} The actual price observed in the peg + /// unused if only 1 feed + function tryPrice() + external + view + override + returns ( + uint192 low, + uint192 high, + uint192 pegPrice + ) + { + uint192 x = feed0.price(timeout0); + uint192 xErr = error0; + + // Use only 1 feed if 2nd feed not defined; else multiply together + // if only 1 feed: `y` is FIX_ONE and `yErr` is 0 + + uint192 y = FIX_ONE; + uint192 yErr; + if (address(feed1) != address(0)) { + y = feed1.price(timeout1); // {target/tok} + yErr = error1; + + // {target/ref} = {target/tok} / {ref/tok} + pegPrice = y.mul(_decay(), FLOOR); + } else if (targetIsUoA) { + // {target/ref} = {UoA/ref} = {UoA/tok} / {ref/tok} + pegPrice = x.mul(_decay(), FLOOR); + } + + // {UoA/tok} = {UoA/ref} * {ref/tok} + low = x.mul(FIX_ONE - xErr).mul(y.mul(FIX_ONE - yErr), FLOOR); + high = x.mul(FIX_ONE + xErr).mul(y.mul(FIX_ONE + yErr), CEIL); + // assert(low <= high); obviously true just by inspection + } + + // === Demurrage rates === + + /// @return {ref/tok} Quantity of whole reference units per whole collateral tokens + function refPerTok() public view override returns (uint192) { + // up-only + return FIX_ONE.div(_decay(), FLOOR); + } + + /// @return {target/ref} Quantity of whole target units per whole reference unit in the peg + function targetPerRef() public view override returns (uint192) { + // down-only + return _decay(); + } + + /// @return If the asset is an instance of ICollateral or not + function isCollateral() external pure override(Asset, IAsset) returns (bool) { + return true; + } + + // === Internal === + + /// @return {1} The decay since t0 + function _decay() internal view returns (uint192) { + // 1 - (1 - decay)^(block.timestamp - t0) + return FIX_ONE.minus(FIX_ONE.minus(decay).powu(uint48(block.timestamp - t0))); + } +} From de9a28024fc43c157e9f03cd010a942cefe0a70d Mon Sep 17 00:00:00 2001 From: Taylor Brent Date: Tue, 15 Oct 2024 17:27:35 -0400 Subject: [PATCH 02/40] refine --- ...sol => GeneralizedDemurrageCollateral.sol} | 94 ++++++++++--------- 1 file changed, 49 insertions(+), 45 deletions(-) rename contracts/plugins/assets/{SelfReferentialDemurrageCollateral.sol => GeneralizedDemurrageCollateral.sol} (66%) diff --git a/contracts/plugins/assets/SelfReferentialDemurrageCollateral.sol b/contracts/plugins/assets/GeneralizedDemurrageCollateral.sol similarity index 66% rename from contracts/plugins/assets/SelfReferentialDemurrageCollateral.sol rename to contracts/plugins/assets/GeneralizedDemurrageCollateral.sol index 6c1aba210..ec9e895ed 100644 --- a/contracts/plugins/assets/SelfReferentialDemurrageCollateral.sol +++ b/contracts/plugins/assets/GeneralizedDemurrageCollateral.sol @@ -6,44 +6,41 @@ import "./Asset.sol"; struct DemurrageConfig { bytes32 targetName; - uint192 decay; // {1/s} fraction of the target unit to decay each second + uint192 fee; // {1/s} per-second inflation/deflation of refPerTok/targetPerRef // - AggregatorV3Interface feed0; - AggregatorV3Interface feed1; + AggregatorV3Interface feed0; // {UoA/tok} or {target/tok} + AggregatorV3Interface feed1; // empty or {UoA/target} uint48 timeout0; // {s} uint192 error0; // {1} uint48 timeout1; // {s} uint192 error1; // {1} + // + bool isFiat; } /** - * @title SelfReferentialDemurrageCollateral - * @notice Collateral plugin for a self-referential demurrage collateral, i.e /w a management fee - * - * No default detection - * Demurrage collateral implement a management fee in the form of a decaying exponential + * @title GeneralizedDemurrageCollateral + * @notice Collateral plugin for a genneralized demurrage collateral (/w management fee) * - * refPerTok = up-only - * targetPerRef = down-only - * product = constant + * refPerTok() * targetPerRef() = refPerTokt0 * targetPerReft0, within precision * - * If 1 feed: - * - feed0 must be {UoA/tok} - * - does not implement issuance premium - * If 2 feeds: - * - feed0 must be {target/tok} - * - feed1 must be {UoA/target} - * - implements issuance premium + * refPerTok is artificially up-only + * targetPerRef is artificially down-only * - * t0 = a previous (or current) moment in time, used as reference for the decay + * 1 feed: + * - feed0 {UoA/tok} + * - apply issuance premium IFF isFiat is true + * 2 feeds: + * - feed0 {target/tok} + * - feed1 {UoA/target} + * - apply issuance premium using feed0 * - * For Target Unit X: * - tok = Tokenized X - * - ref = X @ t0 /w decay + * - ref = Inflationary X * - target = X * - UoA = USD */ -contract SelfReferentialDemurrageCollateral is ICollateral, Asset { +contract GeneralizedDemurrageCollateral is ICollateral, Asset { using FixLib for uint192; using OracleLib for AggregatorV3Interface; @@ -55,12 +52,9 @@ contract SelfReferentialDemurrageCollateral is ICollateral, Asset { // === New stuff === - uint48 public immutable t0; // {s} deployment timestamp - uint192 public immutable decay; // {1/s} fraction of the target unit to decay each second + bool internal immutable isFiat; // For each token, we maintain up to two feeds/timeouts/errors - // The data below would normally be a struct, but we want bytecode substitution - AggregatorV3Interface internal immutable feed0; // {UoA/tok} or {target/tok} AggregatorV3Interface internal immutable feed1; // empty or {UoA/target} uint48 internal immutable timeout0; // {s} @@ -68,18 +62,22 @@ contract SelfReferentialDemurrageCollateral is ICollateral, Asset { uint192 internal immutable error0; // {1} uint192 internal immutable error1; // {1} - bool internal immutable targetIsUoA; + uint48 public immutable t0; // {s} deployment timestamp + uint192 public immutable fee; // {1/s} demurrage fee; manifests as reference unit inflation /// @param priceTimeout_ {s} The number of seconds over which savedHighPrice decays to 0 /// @param maxTradeVolume_ {UoA} The max trade volume, in UoA /// @param oracleTimeout_ {s} The number of seconds until the chainlinkFeed becomes invalid + /// @param demurrageConfig.decay_ {1/s} fraction of the reference unit to inflate each second + /// @param demurrageConfig.feed0_ {UoA/tok} or {target/tok} + /// @param demurrageConfig.feed1_ empty or {UoA/target} + /// @param demurrageConfig.isFiat_ true iff {target} == {UoA} constructor( uint48 priceTimeout_, IERC20Metadata erc20_, uint192 maxTradeVolume_, uint48 oracleTimeout_, - DemurrageConfig memory demurrageConfig, - bool targetIsUoA_ + DemurrageConfig memory demurrageConfig ) Asset( priceTimeout_, @@ -90,6 +88,9 @@ contract SelfReferentialDemurrageCollateral is ICollateral, Asset { oracleTimeout_ ) { + targetName = demurrageConfig.targetName; + isFiat = demurrageConfig.isFiat; + require(address(demurrageConfig.feed0) != address(0), "missing feed0"); require(demurrageConfig.timeout0 != 0, "missing timeout0"); require(demurrageConfig.error0 > 0 && demurrageConfig.error0 < FIX_ONE, "bad error0"); @@ -99,8 +100,6 @@ contract SelfReferentialDemurrageCollateral is ICollateral, Asset { require(demurrageConfig.error1 > 0 && demurrageConfig.error1 < FIX_ONE, "bad error1"); } - t0 = uint48(block.timestamp); - decay = demurrageConfig.decay; feed0 = demurrageConfig.feed0; feed1 = demurrageConfig.feed1; timeout0 = demurrageConfig.timeout0; @@ -108,8 +107,8 @@ contract SelfReferentialDemurrageCollateral is ICollateral, Asset { error0 = demurrageConfig.error0; error1 = demurrageConfig.error1; - targetName = demurrageConfig.targetName; - targetIsUoA = targetIsUoA_; + t0 = uint48(block.timestamp); + fee = demurrageConfig.fee; } /// Can revert, used by other contract functions in order to catch errors @@ -117,7 +116,7 @@ contract SelfReferentialDemurrageCollateral is ICollateral, Asset { /// @return low {UoA/tok} The low price estimate /// @return high {UoA/tok} The high price estimate /// @return pegPrice {target/ref} The actual price observed in the peg - /// unused if only 1 feed + /// can be 0 if only 1 feed AND not fiat function tryPrice() external view @@ -128,7 +127,7 @@ contract SelfReferentialDemurrageCollateral is ICollateral, Asset { uint192 pegPrice ) { - uint192 x = feed0.price(timeout0); + uint192 x = feed0.price(timeout0); // initially {UoA/tok} uint192 xErr = error0; // Use only 1 feed if 2nd feed not defined; else multiply together @@ -137,34 +136,39 @@ contract SelfReferentialDemurrageCollateral is ICollateral, Asset { uint192 y = FIX_ONE; uint192 yErr; if (address(feed1) != address(0)) { - y = feed1.price(timeout1); // {target/tok} + y = feed1.price(timeout1); // {UoA/target} yErr = error1; // {target/ref} = {target/tok} / {ref/tok} - pegPrice = y.mul(_decay(), FLOOR); - } else if (targetIsUoA) { + pegPrice = x.mul(_feeSinceT0(), FLOOR); + } else if (isFiat) { + // apply issuance premium for fiat collateral since target == UoA + // {target/ref} = {UoA/ref} = {UoA/tok} / {ref/tok} - pegPrice = x.mul(_decay(), FLOOR); + pegPrice = x.mul(_feeSinceT0(), FLOOR); } // {UoA/tok} = {UoA/ref} * {ref/tok} low = x.mul(FIX_ONE - xErr).mul(y.mul(FIX_ONE - yErr), FLOOR); high = x.mul(FIX_ONE + xErr).mul(y.mul(FIX_ONE + yErr), CEIL); - // assert(low <= high); obviously true just by inspection + assert(low <= high); } // === Demurrage rates === + // usually targetPerRef() is constant -- in a demurrage collateral the product + // refPerTok() * targetPerRef() is kept (roughly) constant + /// @return {ref/tok} Quantity of whole reference units per whole collateral tokens function refPerTok() public view override returns (uint192) { // up-only - return FIX_ONE.div(_decay(), FLOOR); + return FIX_ONE.div(_feeSinceT0(), FLOOR); } /// @return {target/ref} Quantity of whole target units per whole reference unit in the peg function targetPerRef() public view override returns (uint192) { // down-only - return _decay(); + return _feeSinceT0(); } /// @return If the asset is an instance of ICollateral or not @@ -174,9 +178,9 @@ contract SelfReferentialDemurrageCollateral is ICollateral, Asset { // === Internal === - /// @return {1} The decay since t0 - function _decay() internal view returns (uint192) { + /// @return {1} The overall fee since t0 + function _feeSinceT0() internal view returns (uint192) { // 1 - (1 - decay)^(block.timestamp - t0) - return FIX_ONE.minus(FIX_ONE.minus(decay).powu(uint48(block.timestamp - t0))); + return FIX_ONE.minus(FIX_ONE.minus(fee).powu(uint48(block.timestamp - t0))); } } From 2ca10961d366c2209ec2c442b85d916ee0610d0b Mon Sep 17 00:00:00 2001 From: Taylor Brent Date: Tue, 15 Oct 2024 20:46:37 -0400 Subject: [PATCH 03/40] impl --- contracts/interfaces/IAsset.sol | 3 + .../plugins/assets/DemurrageCollateral.sol | 137 +++++++++++++ .../assets/GeneralizedDemurrageCollateral.sol | 186 ------------------ 3 files changed, 140 insertions(+), 186 deletions(-) create mode 100644 contracts/plugins/assets/DemurrageCollateral.sol delete mode 100644 contracts/plugins/assets/GeneralizedDemurrageCollateral.sol diff --git a/contracts/interfaces/IAsset.sol b/contracts/interfaces/IAsset.sol index 4cc223a63..76025bdd9 100644 --- a/contracts/interfaces/IAsset.sol +++ b/contracts/interfaces/IAsset.sol @@ -130,6 +130,9 @@ interface ICollateral is IAsset { // Used only in Testing. Strictly speaking a Collateral does not need to adhere to this interface interface TestICollateral is TestIAsset, ICollateral { + /// deprecated + function lotPrice() external view returns (uint192 lotLow, uint192 lotHigh); + /// @return The epoch timestamp when the collateral will default from IFFY to DISABLED function whenDefault() external view returns (uint256); diff --git a/contracts/plugins/assets/DemurrageCollateral.sol b/contracts/plugins/assets/DemurrageCollateral.sol new file mode 100644 index 000000000..462ffb578 --- /dev/null +++ b/contracts/plugins/assets/DemurrageCollateral.sol @@ -0,0 +1,137 @@ +// SPDX-License-Identifier: BlueOak-1.0.0 +pragma solidity 0.8.19; + +import "./FiatCollateral.sol"; + +struct DemurrageConfig { + bool isFiat; + uint192 fee; // {1/s} per-second inflation/deflation of refPerTok/targetPerRef + // + // optional extra feed + AggregatorV3Interface feed1; // empty or {UoA/target} + uint48 timeout1; // {s} + uint192 error1; // {1} +} + +/** + * @title DemurrageCollateral + * @notice Collateral plugin for a genneralized demurrage collateral (i.e /w management fee) + * + * 1 feed: + * - feed0 {UoA/tok} + * - apply issuance premium IFF isFiat is true + * 2 feeds: + * - feed0 {target/tok} + * - feed1 {UoA/target} + * - apply issuance premium using feed0 + * + * - tok = Tokenized X + * - ref = Virtual (inflationary) X + * - target = X + * - UoA = USD + */ +contract DemurrageCollateral is FiatCollateral { + using FixLib for uint192; + using OracleLib for AggregatorV3Interface; + + bool internal immutable isFiat; + + // For each token, we maintain up to two feeds/timeouts/errors + AggregatorV3Interface internal immutable feed0; // {UoA/tok} or {target/tok} + AggregatorV3Interface internal immutable feed1; // empty or {UoA/target} + uint48 internal immutable timeout0; // {s} + uint48 internal immutable timeout1; // {s} + uint192 internal immutable error0; // {1} + uint192 internal immutable error1; // {1} + + // immutable in spirit -- cannot be because of FiatCollateral's targetPerRef() call + // TODO would love to find a way to make these immutable + uint48 public t0; // {s} deployment timestamp + uint192 public fee; // {1/s} demurrage fee; manifests as reference unit inflation + + /// @param config.chainlinkFeed unused + /// @param config.oracleTimeout unused + /// @param config.oracleError unused + /// @param demurrageConfig.fee {1/s} fraction of the reference unit to inflate each second + /// @param demurrageConfig.feed0 {UoA/tok} or {target/tok} + /// @param demurrageConfig.feed1 empty or {UoA/target} + /// @param demurrageConfig.isFiat true iff {target} == {UoA} + constructor(CollateralConfig memory config, DemurrageConfig memory demurrageConfig) + FiatCollateral(config) + { + isFiat = demurrageConfig.isFiat; + + if (demurrageConfig.feed1 != AggregatorV3Interface(address(0))) { + require(demurrageConfig.timeout1 != 0, "missing timeout1"); + require(demurrageConfig.error1 > 0 && demurrageConfig.error1 < FIX_ONE, "bad error1"); + } + + feed0 = config.chainlinkFeed; + feed1 = demurrageConfig.feed1; + timeout0 = config.oracleTimeout; + timeout1 = demurrageConfig.timeout1; + error0 = config.oracleError; + error1 = demurrageConfig.error1; + + t0 = uint48(block.timestamp); + fee = demurrageConfig.fee; + } + + /// Can revert, used by other contract functions in order to catch errors + /// Should NOT be manipulable by MEV + /// @return low {UoA/tok} The low price estimate + /// @return high {UoA/tok} The high price estimate + /// @return pegPrice {target/ref} The unadjusted price observed in the peg + /// can be 0 if only 1 feed AND not fiat + function tryPrice() + external + view + override + returns ( + uint192 low, + uint192 high, + uint192 pegPrice + ) + { + pegPrice = FIX_ONE; // undecayed rate that won't trigger default or issuance premium + + // Use only 1 feed if 2nd feed not defined; else multiply together + // if only 1 feed: `y` is FIX_ONE and `yErr` is 0 + + uint192 x = feed0.price(timeout0); // initially {UoA/tok} + uint192 xErr = error0; + uint192 y = FIX_ONE; + uint192 yErr; + if (address(feed1) != address(0)) { + y = feed1.price(timeout1); // {UoA/target} + yErr = error1; + + // {target/ref} = {UoA/target} + pegPrice = y; // no demurrage needed + } else if (isFiat) { + // {target/ref} = {UoA/tok} + pegPrice = x; // no demurrage needed + } + + // {UoA/tok} = {UoA/ref} * {ref/tok} + low = x.mul(FIX_ONE - xErr).mul(y.mul(FIX_ONE - yErr), FLOOR); + high = x.mul(FIX_ONE + xErr).mul(y.mul(FIX_ONE + yErr), CEIL); + assert(low <= high); + } + + // === Demurrage rates === + + // invariant: targetPerRef() * refPerTok() ~= FIX_ONE + + /// @return {ref/tok} Quantity of whole reference units per whole collateral tokens + function refPerTok() public view override returns (uint192) { + // up-only + return FIX_ONE.div(targetPerRef(), FLOOR); + } + + /// @return {target/ref} Quantity of whole target units per whole reference unit in the peg + function targetPerRef() public view override returns (uint192) { + // down-only + return FIX_ONE.minus(fee).powu(uint48(block.timestamp - t0)); + } +} diff --git a/contracts/plugins/assets/GeneralizedDemurrageCollateral.sol b/contracts/plugins/assets/GeneralizedDemurrageCollateral.sol deleted file mode 100644 index ec9e895ed..000000000 --- a/contracts/plugins/assets/GeneralizedDemurrageCollateral.sol +++ /dev/null @@ -1,186 +0,0 @@ -// SPDX-License-Identifier: BlueOak-1.0.0 -pragma solidity 0.8.19; - -import "../../interfaces/IAsset.sol"; -import "./Asset.sol"; - -struct DemurrageConfig { - bytes32 targetName; - uint192 fee; // {1/s} per-second inflation/deflation of refPerTok/targetPerRef - // - AggregatorV3Interface feed0; // {UoA/tok} or {target/tok} - AggregatorV3Interface feed1; // empty or {UoA/target} - uint48 timeout0; // {s} - uint192 error0; // {1} - uint48 timeout1; // {s} - uint192 error1; // {1} - // - bool isFiat; -} - -/** - * @title GeneralizedDemurrageCollateral - * @notice Collateral plugin for a genneralized demurrage collateral (/w management fee) - * - * refPerTok() * targetPerRef() = refPerTokt0 * targetPerReft0, within precision - * - * refPerTok is artificially up-only - * targetPerRef is artificially down-only - * - * 1 feed: - * - feed0 {UoA/tok} - * - apply issuance premium IFF isFiat is true - * 2 feeds: - * - feed0 {target/tok} - * - feed1 {UoA/target} - * - apply issuance premium using feed0 - * - * - tok = Tokenized X - * - ref = Inflationary X - * - target = X - * - UoA = USD - */ -contract GeneralizedDemurrageCollateral is ICollateral, Asset { - using FixLib for uint192; - using OracleLib for AggregatorV3Interface; - - // === Old stuff === - - CollateralStatus public immutable status = CollateralStatus.SOUND; // never default - bytes32 public immutable targetName; - uint192 public savedPegPrice; // {target/ref} The peg price of the token during the last update - - // === New stuff === - - bool internal immutable isFiat; - - // For each token, we maintain up to two feeds/timeouts/errors - AggregatorV3Interface internal immutable feed0; // {UoA/tok} or {target/tok} - AggregatorV3Interface internal immutable feed1; // empty or {UoA/target} - uint48 internal immutable timeout0; // {s} - uint48 internal immutable timeout1; // {s} - uint192 internal immutable error0; // {1} - uint192 internal immutable error1; // {1} - - uint48 public immutable t0; // {s} deployment timestamp - uint192 public immutable fee; // {1/s} demurrage fee; manifests as reference unit inflation - - /// @param priceTimeout_ {s} The number of seconds over which savedHighPrice decays to 0 - /// @param maxTradeVolume_ {UoA} The max trade volume, in UoA - /// @param oracleTimeout_ {s} The number of seconds until the chainlinkFeed becomes invalid - /// @param demurrageConfig.decay_ {1/s} fraction of the reference unit to inflate each second - /// @param demurrageConfig.feed0_ {UoA/tok} or {target/tok} - /// @param demurrageConfig.feed1_ empty or {UoA/target} - /// @param demurrageConfig.isFiat_ true iff {target} == {UoA} - constructor( - uint48 priceTimeout_, - IERC20Metadata erc20_, - uint192 maxTradeVolume_, - uint48 oracleTimeout_, - DemurrageConfig memory demurrageConfig - ) - Asset( - priceTimeout_, - demurrageConfig.feed0, - demurrageConfig.error0, - erc20_, - maxTradeVolume_, - oracleTimeout_ - ) - { - targetName = demurrageConfig.targetName; - isFiat = demurrageConfig.isFiat; - - require(address(demurrageConfig.feed0) != address(0), "missing feed0"); - require(demurrageConfig.timeout0 != 0, "missing timeout0"); - require(demurrageConfig.error0 > 0 && demurrageConfig.error0 < FIX_ONE, "bad error0"); - - if (address(demurrageConfig.feed1) != address(0)) { - require(demurrageConfig.timeout1 != 0, "missing timeout1"); - require(demurrageConfig.error1 > 0 && demurrageConfig.error1 < FIX_ONE, "bad error1"); - } - - feed0 = demurrageConfig.feed0; - feed1 = demurrageConfig.feed1; - timeout0 = demurrageConfig.timeout0; - timeout1 = demurrageConfig.timeout1; - error0 = demurrageConfig.error0; - error1 = demurrageConfig.error1; - - t0 = uint48(block.timestamp); - fee = demurrageConfig.fee; - } - - /// Can revert, used by other contract functions in order to catch errors - /// Should NOT be manipulable by MEV - /// @return low {UoA/tok} The low price estimate - /// @return high {UoA/tok} The high price estimate - /// @return pegPrice {target/ref} The actual price observed in the peg - /// can be 0 if only 1 feed AND not fiat - function tryPrice() - external - view - override - returns ( - uint192 low, - uint192 high, - uint192 pegPrice - ) - { - uint192 x = feed0.price(timeout0); // initially {UoA/tok} - uint192 xErr = error0; - - // Use only 1 feed if 2nd feed not defined; else multiply together - // if only 1 feed: `y` is FIX_ONE and `yErr` is 0 - - uint192 y = FIX_ONE; - uint192 yErr; - if (address(feed1) != address(0)) { - y = feed1.price(timeout1); // {UoA/target} - yErr = error1; - - // {target/ref} = {target/tok} / {ref/tok} - pegPrice = x.mul(_feeSinceT0(), FLOOR); - } else if (isFiat) { - // apply issuance premium for fiat collateral since target == UoA - - // {target/ref} = {UoA/ref} = {UoA/tok} / {ref/tok} - pegPrice = x.mul(_feeSinceT0(), FLOOR); - } - - // {UoA/tok} = {UoA/ref} * {ref/tok} - low = x.mul(FIX_ONE - xErr).mul(y.mul(FIX_ONE - yErr), FLOOR); - high = x.mul(FIX_ONE + xErr).mul(y.mul(FIX_ONE + yErr), CEIL); - assert(low <= high); - } - - // === Demurrage rates === - - // usually targetPerRef() is constant -- in a demurrage collateral the product - // refPerTok() * targetPerRef() is kept (roughly) constant - - /// @return {ref/tok} Quantity of whole reference units per whole collateral tokens - function refPerTok() public view override returns (uint192) { - // up-only - return FIX_ONE.div(_feeSinceT0(), FLOOR); - } - - /// @return {target/ref} Quantity of whole target units per whole reference unit in the peg - function targetPerRef() public view override returns (uint192) { - // down-only - return _feeSinceT0(); - } - - /// @return If the asset is an instance of ICollateral or not - function isCollateral() external pure override(Asset, IAsset) returns (bool) { - return true; - } - - // === Internal === - - /// @return {1} The overall fee since t0 - function _feeSinceT0() internal view returns (uint192) { - // 1 - (1 - decay)^(block.timestamp - t0) - return FIX_ONE.minus(FIX_ONE.minus(fee).powu(uint48(block.timestamp - t0))); - } -} From 87219572900cb07b4656ada151c01a34fece5c84 Mon Sep 17 00:00:00 2001 From: Taylor Brent Date: Tue, 15 Oct 2024 20:46:49 -0400 Subject: [PATCH 04/40] paxg tests --- .../dtf/PAXGCollateralTestSuite.test.ts | 186 ++++++++++++++++++ .../individual-collateral/dtf/constants.ts | 14 ++ .../individual-collateral/dtf/helpers.ts | 21 ++ 3 files changed, 221 insertions(+) create mode 100644 test/plugins/individual-collateral/dtf/PAXGCollateralTestSuite.test.ts create mode 100644 test/plugins/individual-collateral/dtf/constants.ts create mode 100644 test/plugins/individual-collateral/dtf/helpers.ts diff --git a/test/plugins/individual-collateral/dtf/PAXGCollateralTestSuite.test.ts b/test/plugins/individual-collateral/dtf/PAXGCollateralTestSuite.test.ts new file mode 100644 index 000000000..673fa5ea6 --- /dev/null +++ b/test/plugins/individual-collateral/dtf/PAXGCollateralTestSuite.test.ts @@ -0,0 +1,186 @@ +import collateralTests from '../collateralTests' +import { CollateralFixtureContext, CollateralOpts, MintCollateralFunc } from '../pluginTestTypes' +import { resetFork, mintPAXG } from './helpers' +import { ethers } from 'hardhat' +import { expect } from 'chai' +import { ContractFactory, BigNumberish, BigNumber } from 'ethers' +import { MockV3Aggregator, MockV3Aggregator__factory, TestICollateral } from '../../../../typechain' +import { bn, fp } from '../../../../common/numbers' +import { ZERO_ADDRESS } from '../../../../common/constants' +import { SignerWithAddress } from '@nomiclabs/hardhat-ethers/signers' +import { + DELAY_UNTIL_DEFAULT, + PAXG, + ORACLE_ERROR, + ORACLE_TIMEOUT, + PRICE_TIMEOUT, + MAX_TRADE_VOL, + XAU_USD_PRICE_FEED, +} from './constants' + +/* + Define deployment functions +*/ + +interface PAXGCollateralOpts extends CollateralOpts { + fee?: BigNumberish +} + +export const defaultPAXGCollateralOpts: PAXGCollateralOpts = { + erc20: PAXG, + targetName: ethers.utils.formatBytes32String('XAU'), + rewardERC20: ZERO_ADDRESS, + priceTimeout: PRICE_TIMEOUT, + chainlinkFeed: XAU_USD_PRICE_FEED, + oracleTimeout: ORACLE_TIMEOUT, + oracleError: ORACLE_ERROR, + maxTradeVolume: MAX_TRADE_VOL, + fee: fp('1e-9'), // about 3.1% annually +} + +export const deployCollateral = async (opts: PAXGCollateralOpts = {}): Promise => { + opts = { ...defaultPAXGCollateralOpts, ...opts } + + const PAXGCollateralFactory: ContractFactory = await ethers.getContractFactory( + 'DemurrageCollateral' + ) + const collateral = await PAXGCollateralFactory.deploy( + { + erc20: opts.erc20, + targetName: opts.targetName, + priceTimeout: opts.priceTimeout, + chainlinkFeed: opts.chainlinkFeed, + oracleError: opts.oracleError, + oracleTimeout: opts.oracleTimeout, + maxTradeVolume: opts.maxTradeVolume, + defaultThreshold: bn('0'), + delayUntilDefault: DELAY_UNTIL_DEFAULT, + }, + { + isFiat: false, + fee: opts.fee, + feed1: ZERO_ADDRESS, + timeout1: bn(0), + error1: bn(0), + }, + { gasLimit: 2000000000 } + ) + await collateral.deployed() + // sometimes we are trying to test a negative test case and we want this to fail silently + // fortunately this syntax fails silently because our tools are terrible + await expect(collateral.refresh()) + + return collateral +} + +const chainlinkDefaultAnswer = bn('266347300000') // $2,663.473 + +type Fixture = () => Promise + +const makeCollateralFixtureContext = ( + alice: SignerWithAddress, + opts: PAXGCollateralOpts = {} +): Fixture => { + const collateralOpts = { ...defaultPAXGCollateralOpts, ...opts } + + const makeCollateralFixtureContext = async () => { + const MockV3AggregatorFactory = ( + await ethers.getContractFactory('MockV3Aggregator') + ) + + const chainlinkFeed = ( + await MockV3AggregatorFactory.deploy(8, chainlinkDefaultAnswer) + ) + collateralOpts.chainlinkFeed = chainlinkFeed.address + + const collateral = await deployCollateral(collateralOpts) + const tok = await ethers.getContractAt('IERC20Metadata', await collateral.erc20()) + + return { + alice, + collateral, + chainlinkFeed, + tok, + } + } + + return makeCollateralFixtureContext +} + +const mintCollateralTo: MintCollateralFunc = async ( + ctx: CollateralFixtureContext, + amount: BigNumberish, + user: SignerWithAddress, + recipient: string +) => { + await mintPAXG(ctx.tok, amount, recipient) +} + +// eslint-disable-next-line @typescript-eslint/no-empty-function +const reduceTargetPerRef = async () => {} + +// eslint-disable-next-line @typescript-eslint/no-empty-function +const increaseTargetPerRef = async () => {} + +// eslint-disable-next-line @typescript-eslint/no-empty-function +const reduceRefPerTok = async () => {} + +// eslint-disable-next-line @typescript-eslint/no-empty-function +const increaseRefPerTok = async () => {} + +const getExpectedPrice = async (ctx: CollateralFixtureContext): Promise => { + const clData = await ctx.chainlinkFeed.latestRoundData() + const clDecimals = await ctx.chainlinkFeed.decimals() + + const refPerTok = await ctx.collateral.refPerTok() + return clData.answer + .mul(bn(10).pow(18 - clDecimals)) + .mul(refPerTok) + .div(fp('1')) +} + +/* + Define collateral-specific tests +*/ + +// eslint-disable-next-line @typescript-eslint/no-empty-function +const collateralSpecificConstructorTests = () => {} + +// eslint-disable-next-line @typescript-eslint/no-empty-function +const collateralSpecificStatusTests = () => {} + +// eslint-disable-next-line @typescript-eslint/no-empty-function +const beforeEachRewardsTest = async () => {} + +/* + Run the test suite +*/ + +const opts = { + deployCollateral, + collateralSpecificConstructorTests, + collateralSpecificStatusTests, + beforeEachRewardsTest, + makeCollateralFixtureContext, + mintCollateralTo, + reduceTargetPerRef, + increaseTargetPerRef, + reduceRefPerTok, + increaseRefPerTok, + getExpectedPrice, + itClaimsRewards: it.skip, + itChecksTargetPerRefDefault: it.skip, + itChecksTargetPerRefDefaultUp: it.skip, + itChecksNonZeroDefaultThreshold: it.skip, + itChecksRefPerTokDefault: it.skip, + itChecksPriceChanges: it, + itChecksPriceChangesRefPerTok: it.skip, + itHasRevenueHiding: it.skip, + resetFork, + collateralName: 'PAXG Demurrage Collateral', + chainlinkDefaultAnswer, + itIsAXGCricedByPeg: true, + toleranceDivisor: bn('1e8'), // 1-part in 100 million +} + +collateralTests(opts) diff --git a/test/plugins/individual-collateral/dtf/constants.ts b/test/plugins/individual-collateral/dtf/constants.ts new file mode 100644 index 000000000..b3fa70d32 --- /dev/null +++ b/test/plugins/individual-collateral/dtf/constants.ts @@ -0,0 +1,14 @@ +import { bn, fp } from '../../../../common/numbers' +import { networkConfig } from '../../../../common/configuration' + +// Mainnet Addresses +export const XAU_USD_PRICE_FEED = networkConfig['31337'].chainlinkFeeds.XAU as string +export const PAXG = networkConfig['31337'].tokens.PAXG as string + +export const PRICE_TIMEOUT = bn('604800') // 1 week +export const ORACLE_TIMEOUT = bn('86400') // 24 hours in seconds +export const ORACLE_ERROR = fp('0.003') // 0.3% +export const DELAY_UNTIL_DEFAULT = bn('86400') // 24h +export const MAX_TRADE_VOL = fp('1e6') + +export const FORK_BLOCK = 20963623 diff --git a/test/plugins/individual-collateral/dtf/helpers.ts b/test/plugins/individual-collateral/dtf/helpers.ts new file mode 100644 index 000000000..50b9f6294 --- /dev/null +++ b/test/plugins/individual-collateral/dtf/helpers.ts @@ -0,0 +1,21 @@ +import { ethers } from 'hardhat' +import { IERC20Metadata } from '../../../../typechain' +import { whileImpersonating } from '../../../utils/impersonation' +import { BigNumberish } from 'ethers' +import { FORK_BLOCK } from './constants' +import { getResetFork } from '../helpers' + +export const mintPAXG = async (paxg: IERC20Metadata, amount: BigNumberish, recipient: string) => { + const supplyControllerAddr = '0xE25a329d385f77df5D4eD56265babe2b99A5436e' + + await whileImpersonating(supplyControllerAddr, async (supplyController) => { + const paxg2 = new ethers.Contract(paxg.address, [ + 'function increaseSupply(uint256 _value) external returns (bool success)', + ]) + + await paxg2.connect(supplyController).increaseSupply(amount) + await paxg.connect(supplyController).transfer(recipient, amount) + }) +} + +export const resetFork = getResetFork(FORK_BLOCK) From 8f879a7cede71446368aae94038e3573599ffca4 Mon Sep 17 00:00:00 2001 From: Taylor Brent Date: Tue, 15 Oct 2024 20:46:56 -0400 Subject: [PATCH 05/40] keep everything else compatible --- common/configuration.ts | 5 +++ .../individual-collateral/aave-v3/common.ts | 1 + .../ankr/AnkrEthCollateralTestSuite.test.ts | 1 + .../cbeth/CBETHCollateral.test.ts | 1 + .../cbeth/CBETHCollateralL2.test.ts | 1 + .../individual-collateral/collateralTests.ts | 35 ++++++++++++++++++- .../compoundv3/CometTestSuite.test.ts | 1 + .../dsr/SDaiCollateralTestSuite.test.ts | 1 + .../ethena/USDeFiatCollateral.test.ts | 1 + .../ethx/ETHxCollateral.test.ts | 1 + .../flux-finance/FTokenFiatCollateral.test.ts | 1 + .../frax-eth/SFrxEthTestSuite.test.ts | 1 + .../frax/SFraxCollateralTestSuite.test.ts | 1 + .../lido/L2LidoStakedEthTestSuite.test.ts | 1 + .../lido/LidoStakedEthTestSuite.test.ts | 1 + .../MetaMorphoFiatCollateral.test.ts | 1 + ...etaMorphoSelfReferentialCollateral.test.ts | 1 + .../MorphoAAVEFiatCollateral.test.ts | 1 + .../MorphoAAVENonFiatCollateral.test.ts | 1 + ...orphoAAVESelfReferentialCollateral.test.ts | 1 + .../mountain/USDMCollateral.test.ts | 1 + .../pirex-eth/ApxEthCollateral.test.ts | 1 + .../individual-collateral/pluginTestTypes.ts | 3 ++ .../RethCollateralTestSuite.test.ts | 1 + 24 files changed, 63 insertions(+), 1 deletion(-) diff --git a/common/configuration.ts b/common/configuration.ts index d88ab6218..9a561261b 100644 --- a/common/configuration.ts +++ b/common/configuration.ts @@ -116,6 +116,8 @@ export interface ITokens { // Mountain USDM?: string wUSDM?: string + + PAXG?: string } export type ITokensKeys = Array @@ -127,6 +129,7 @@ export interface IFeeds { cbETHETHexr?: string ETHUSD?: string wstETHstETH?: string + XAU?: string } export interface IPools { @@ -257,6 +260,7 @@ export const networkConfig: { [key: string]: INetworkConfig } = { sdUSDCUSDCPlus: '0x9bbF31E99F30c38a5003952206C31EEa77540BeF', USDe: '0x4c9edd5852cd905f086c759e8383e09bff1e68b3', sUSDe: '0x9D39A5DE30e57443BfF2A8307A4256c8797A3497', + PAXG: '0x45804880De22913dAFE09f4980848ECE6EcbAf78', }, chainlinkFeeds: { RSR: '0x759bBC1be8F90eE6457C44abc7d443842a976d02', @@ -286,6 +290,7 @@ export const networkConfig: { [key: string]: INetworkConfig } = { pyUSD: '0x8f1dF6D7F2db73eECE86a18b4381F4707b918FB1', apxETH: '0x19219BC90F48DeE4d5cF202E09c438FAacFd8Bea', // apxETH/ETH USDe: '0xa569d910839Ae8865Da8F8e70FfFb0cBA869F961', + XAU: '0x214eD9Da11D2fbe465a6fc601a91E62EbEc1a0D6', }, AAVE_INCENTIVES: '0xd784927Ff2f95ba542BfC824c8a8a98F3495f6b5', AAVE_EMISSIONS_MGR: '0xEE56e2B3D491590B5b31738cC34d5232F378a8D5', diff --git a/test/plugins/individual-collateral/aave-v3/common.ts b/test/plugins/individual-collateral/aave-v3/common.ts index 77e11f1be..0b095e5ae 100644 --- a/test/plugins/individual-collateral/aave-v3/common.ts +++ b/test/plugins/individual-collateral/aave-v3/common.ts @@ -213,6 +213,7 @@ export const makeTests = (defaultCollateralOpts: CollateralParams, altParams: Al itIsPricedByPeg: true, chainlinkDefaultAnswer: 1e8, itChecksPriceChanges: it, + itChecksPriceChangesRefPerTok: it, getExpectedPrice, toleranceDivisor: altParams.toleranceDivisor ?? bn('1e9'), // 1e15 adjusted for ((x + 1)/x) timestamp precision targetNetwork: altParams.targetNetwork, diff --git a/test/plugins/individual-collateral/ankr/AnkrEthCollateralTestSuite.test.ts b/test/plugins/individual-collateral/ankr/AnkrEthCollateralTestSuite.test.ts index 8a3a07a83..3c64bbe17 100644 --- a/test/plugins/individual-collateral/ankr/AnkrEthCollateralTestSuite.test.ts +++ b/test/plugins/individual-collateral/ankr/AnkrEthCollateralTestSuite.test.ts @@ -289,6 +289,7 @@ const opts = { itChecksTargetPerRefDefaultUp: it, itChecksRefPerTokDefault: it, itChecksPriceChanges: it, + itChecksPriceChangesRefPerTok: it, itHasRevenueHiding: it, itChecksNonZeroDefaultThreshold: it, resetFork, diff --git a/test/plugins/individual-collateral/cbeth/CBETHCollateral.test.ts b/test/plugins/individual-collateral/cbeth/CBETHCollateral.test.ts index cd35ee5c0..47de1fafb 100644 --- a/test/plugins/individual-collateral/cbeth/CBETHCollateral.test.ts +++ b/test/plugins/individual-collateral/cbeth/CBETHCollateral.test.ts @@ -246,6 +246,7 @@ const opts = { itChecksTargetPerRefDefaultUp: it, itChecksRefPerTokDefault: it, itChecksPriceChanges: it, + itChecksPriceChangesRefPerTok: it, itHasRevenueHiding: it, itChecksNonZeroDefaultThreshold: it, resetFork, diff --git a/test/plugins/individual-collateral/cbeth/CBETHCollateralL2.test.ts b/test/plugins/individual-collateral/cbeth/CBETHCollateralL2.test.ts index a4a9c3242..00accddee 100644 --- a/test/plugins/individual-collateral/cbeth/CBETHCollateralL2.test.ts +++ b/test/plugins/individual-collateral/cbeth/CBETHCollateralL2.test.ts @@ -277,6 +277,7 @@ const opts = { itChecksTargetPerRefDefaultUp: it, itChecksRefPerTokDefault: it, itChecksPriceChanges: it, + itChecksPriceChangesRefPerTok: it, itHasRevenueHiding: it, itChecksNonZeroDefaultThreshold: it, resetFork, diff --git a/test/plugins/individual-collateral/collateralTests.ts b/test/plugins/individual-collateral/collateralTests.ts index 66119a1fe..fcc719230 100644 --- a/test/plugins/individual-collateral/collateralTests.ts +++ b/test/plugins/individual-collateral/collateralTests.ts @@ -86,6 +86,7 @@ export default function fn( itChecksTargetPerRefDefaultUp, itChecksRefPerTokDefault, itChecksPriceChanges, + itChecksPriceChangesRefPerTok, itChecksNonZeroDefaultThreshold, itHasRevenueHiding, itIsPricedByPeg, @@ -295,7 +296,7 @@ export default function fn( } ) - itChecksPriceChanges('prices change as refPerTok changes', async () => { + itChecksPriceChangesRefPerTok('prices change as refPerTok changes', async () => { const initRefPerTok = await collateral.refPerTok() const oracleError = await collateral.oracleError() @@ -689,6 +690,7 @@ export default function fn( pctRate: fp('0.05'), // 5% }, reweightable: false, + enableIssuancePremium: true, } interface IntegrationFixture { @@ -1014,6 +1016,37 @@ export default function fn( targetUnitOracle.address, ORACLE_TIMEOUT ) + } else if (target === ethers.utils.formatBytes32String('XAU')) { + if (onBase || onArbitrum) throw new Error('PAXG only supported on mainnet') + + // PAXG + const ERC20Factory = await ethers.getContractFactory('ERC20MockDecimals') + const erc20 = await ERC20Factory.deploy('PAXG', 'PAXG', 18) + await erc20.mint(addr1.address, bn('1e30')) + + const DemurrageFactory: ContractFactory = await ethers.getContractFactory( + 'DemurrageCollateral' + ) + return await DemurrageFactory.deploy( + { + erc20: erc20.address, + targetName: ethers.utils.formatBytes32String('XAU'), + priceTimeout: PRICE_TIMEOUT, + chainlinkFeed: chainlinkFeed.address, + oracleError: ORACLE_ERROR, + oracleTimeout: ORACLE_TIMEOUT, + maxTradeVolume: MAX_UINT192, + defaultThreshold: bn('0'), + delayUntilDefault: bn('0'), + }, + { + isFiat: false, + fee: bn('0'), + feed1: ZERO_ADDRESS, + timeout1: bn(0), + error1: bn(0), + } + ) } else { throw new Error(`Unknown target: ${target}`) } diff --git a/test/plugins/individual-collateral/compoundv3/CometTestSuite.test.ts b/test/plugins/individual-collateral/compoundv3/CometTestSuite.test.ts index 78c334c90..322fad09d 100644 --- a/test/plugins/individual-collateral/compoundv3/CometTestSuite.test.ts +++ b/test/plugins/individual-collateral/compoundv3/CometTestSuite.test.ts @@ -383,6 +383,7 @@ const opts = { itChecksTargetPerRefDefaultUp: it, itChecksRefPerTokDefault: it.skip, // implemented in this file itChecksPriceChanges: it, + itChecksPriceChangesRefPerTok: it, itChecksNonZeroDefaultThreshold: it, itHasRevenueHiding: it.skip, // implemented in this file itIsPricedByPeg: true, diff --git a/test/plugins/individual-collateral/dsr/SDaiCollateralTestSuite.test.ts b/test/plugins/individual-collateral/dsr/SDaiCollateralTestSuite.test.ts index 2919f0ebc..5459736ce 100644 --- a/test/plugins/individual-collateral/dsr/SDaiCollateralTestSuite.test.ts +++ b/test/plugins/individual-collateral/dsr/SDaiCollateralTestSuite.test.ts @@ -214,6 +214,7 @@ const opts = { itChecksTargetPerRefDefaultUp: it, itChecksRefPerTokDefault: it, itChecksPriceChanges: it, + itChecksPriceChangesRefPerTok: it, itChecksNonZeroDefaultThreshold: it, itHasRevenueHiding: it.skip, resetFork, diff --git a/test/plugins/individual-collateral/ethena/USDeFiatCollateral.test.ts b/test/plugins/individual-collateral/ethena/USDeFiatCollateral.test.ts index 9624aaa70..1278ad168 100644 --- a/test/plugins/individual-collateral/ethena/USDeFiatCollateral.test.ts +++ b/test/plugins/individual-collateral/ethena/USDeFiatCollateral.test.ts @@ -210,6 +210,7 @@ const opts = { itChecksTargetPerRefDefaultUp: it, itChecksRefPerTokDefault: it, itChecksPriceChanges: it, + itChecksPriceChangesRefPerTok: it, itChecksNonZeroDefaultThreshold: it, itHasRevenueHiding: it, collateralName: 'USDe Fiat Collateral', diff --git a/test/plugins/individual-collateral/ethx/ETHxCollateral.test.ts b/test/plugins/individual-collateral/ethx/ETHxCollateral.test.ts index 10b4da87c..76e9b022b 100644 --- a/test/plugins/individual-collateral/ethx/ETHxCollateral.test.ts +++ b/test/plugins/individual-collateral/ethx/ETHxCollateral.test.ts @@ -281,6 +281,7 @@ const opts = { itChecksTargetPerRefDefaultUp: it, itChecksRefPerTokDefault: it, itChecksPriceChanges: it, + itChecksPriceChangesRefPerTok: it, itChecksNonZeroDefaultThreshold: it, itHasRevenueHiding: it, resetFork, diff --git a/test/plugins/individual-collateral/flux-finance/FTokenFiatCollateral.test.ts b/test/plugins/individual-collateral/flux-finance/FTokenFiatCollateral.test.ts index 180a88935..8d6a18ccb 100644 --- a/test/plugins/individual-collateral/flux-finance/FTokenFiatCollateral.test.ts +++ b/test/plugins/individual-collateral/flux-finance/FTokenFiatCollateral.test.ts @@ -257,6 +257,7 @@ all.forEach((curr: FTokenEnumeration) => { itChecksTargetPerRefDefaultUp: it, itChecksRefPerTokDefault: it, itChecksPriceChanges: it, + itChecksPriceChangesRefPerTok: it, itChecksNonZeroDefaultThreshold: it, itHasRevenueHiding: it, resetFork, diff --git a/test/plugins/individual-collateral/frax-eth/SFrxEthTestSuite.test.ts b/test/plugins/individual-collateral/frax-eth/SFrxEthTestSuite.test.ts index b15e1df41..045c30766 100644 --- a/test/plugins/individual-collateral/frax-eth/SFrxEthTestSuite.test.ts +++ b/test/plugins/individual-collateral/frax-eth/SFrxEthTestSuite.test.ts @@ -309,6 +309,7 @@ const opts = { itChecksTargetPerRefDefaultUp: it.skip, itChecksRefPerTokDefault: it.skip, itChecksPriceChanges: it, + itChecksPriceChangesRefPerTok: it, itHasRevenueHiding: it.skip, // implemented in this file itChecksNonZeroDefaultThreshold: it, resetFork, diff --git a/test/plugins/individual-collateral/frax/SFraxCollateralTestSuite.test.ts b/test/plugins/individual-collateral/frax/SFraxCollateralTestSuite.test.ts index 2ed9bc5e4..79db72699 100644 --- a/test/plugins/individual-collateral/frax/SFraxCollateralTestSuite.test.ts +++ b/test/plugins/individual-collateral/frax/SFraxCollateralTestSuite.test.ts @@ -197,6 +197,7 @@ const opts = { itChecksNonZeroDefaultThreshold: it, itChecksRefPerTokDefault: it, itChecksPriceChanges: it, + itChecksPriceChangesRefPerTok: it, itHasRevenueHiding: it.skip, resetFork, collateralName: 'SFraxCollateral', diff --git a/test/plugins/individual-collateral/lido/L2LidoStakedEthTestSuite.test.ts b/test/plugins/individual-collateral/lido/L2LidoStakedEthTestSuite.test.ts index 184b1090a..937cb647e 100644 --- a/test/plugins/individual-collateral/lido/L2LidoStakedEthTestSuite.test.ts +++ b/test/plugins/individual-collateral/lido/L2LidoStakedEthTestSuite.test.ts @@ -277,6 +277,7 @@ const opts = { itChecksTargetPerRefDefaultUp: it, itChecksRefPerTokDefault: it, itChecksPriceChanges: it, + itChecksPriceChangesRefPerTok: it, itChecksNonZeroDefaultThreshold: it, itHasRevenueHiding: it, resetFork: getResetFork(FORK_BLOCK_BASE), diff --git a/test/plugins/individual-collateral/lido/LidoStakedEthTestSuite.test.ts b/test/plugins/individual-collateral/lido/LidoStakedEthTestSuite.test.ts index 366c8c81c..29f16a6cf 100644 --- a/test/plugins/individual-collateral/lido/LidoStakedEthTestSuite.test.ts +++ b/test/plugins/individual-collateral/lido/LidoStakedEthTestSuite.test.ts @@ -268,6 +268,7 @@ const opts = { itChecksTargetPerRefDefaultUp: it, itChecksRefPerTokDefault: it, itChecksPriceChanges: it, + itChecksPriceChangesRefPerTok: it, itChecksNonZeroDefaultThreshold: it, itHasRevenueHiding: it, resetFork, diff --git a/test/plugins/individual-collateral/meta-morpho/MetaMorphoFiatCollateral.test.ts b/test/plugins/individual-collateral/meta-morpho/MetaMorphoFiatCollateral.test.ts index d99f599ad..bf58a3c3c 100644 --- a/test/plugins/individual-collateral/meta-morpho/MetaMorphoFiatCollateral.test.ts +++ b/test/plugins/individual-collateral/meta-morpho/MetaMorphoFiatCollateral.test.ts @@ -164,6 +164,7 @@ const makeFiatCollateralTestSuite = ( itChecksTargetPerRefDefaultUp: it, itChecksRefPerTokDefault: it, itChecksPriceChanges: it, + itChecksPriceChangesRefPerTok: it, itChecksNonZeroDefaultThreshold: it, itHasRevenueHiding: it, resetFork: getResetFork(FORK_BLOCK), diff --git a/test/plugins/individual-collateral/meta-morpho/MetaMorphoSelfReferentialCollateral.test.ts b/test/plugins/individual-collateral/meta-morpho/MetaMorphoSelfReferentialCollateral.test.ts index 85c684e8d..6635f38ea 100644 --- a/test/plugins/individual-collateral/meta-morpho/MetaMorphoSelfReferentialCollateral.test.ts +++ b/test/plugins/individual-collateral/meta-morpho/MetaMorphoSelfReferentialCollateral.test.ts @@ -167,6 +167,7 @@ const makeFiatCollateralTestSuite = ( itChecksTargetPerRefDefaultUp: it.skip, itChecksRefPerTokDefault: it, itChecksPriceChanges: it, + itChecksPriceChangesRefPerTok: it, itChecksNonZeroDefaultThreshold: it.skip, itHasRevenueHiding: it, resetFork: getResetFork(FORK_BLOCK), diff --git a/test/plugins/individual-collateral/morpho-aave/MorphoAAVEFiatCollateral.test.ts b/test/plugins/individual-collateral/morpho-aave/MorphoAAVEFiatCollateral.test.ts index 9a9b94fad..0e88789da 100644 --- a/test/plugins/individual-collateral/morpho-aave/MorphoAAVEFiatCollateral.test.ts +++ b/test/plugins/individual-collateral/morpho-aave/MorphoAAVEFiatCollateral.test.ts @@ -367,6 +367,7 @@ const makeAaveFiatCollateralTestSuite = ( itChecksTargetPerRefDefaultUp: it, itChecksRefPerTokDefault: it, itChecksPriceChanges: it, + itChecksPriceChangesRefPerTok: it, itChecksNonZeroDefaultThreshold: it, itHasRevenueHiding: it, resetFork: getResetFork(FORK_BLOCK), diff --git a/test/plugins/individual-collateral/morpho-aave/MorphoAAVENonFiatCollateral.test.ts b/test/plugins/individual-collateral/morpho-aave/MorphoAAVENonFiatCollateral.test.ts index 937ec99e7..ef459b934 100644 --- a/test/plugins/individual-collateral/morpho-aave/MorphoAAVENonFiatCollateral.test.ts +++ b/test/plugins/individual-collateral/morpho-aave/MorphoAAVENonFiatCollateral.test.ts @@ -228,6 +228,7 @@ const makeAaveNonFiatCollateralTestSuite = ( itChecksTargetPerRefDefaultUp: it, itChecksRefPerTokDefault: it, itChecksPriceChanges: it, + itChecksPriceChangesRefPerTok: it, itChecksNonZeroDefaultThreshold: it, itHasRevenueHiding: it, itIsPricedByPeg: true, diff --git a/test/plugins/individual-collateral/morpho-aave/MorphoAAVESelfReferentialCollateral.test.ts b/test/plugins/individual-collateral/morpho-aave/MorphoAAVESelfReferentialCollateral.test.ts index 81404fe20..1480aeb11 100644 --- a/test/plugins/individual-collateral/morpho-aave/MorphoAAVESelfReferentialCollateral.test.ts +++ b/test/plugins/individual-collateral/morpho-aave/MorphoAAVESelfReferentialCollateral.test.ts @@ -228,6 +228,7 @@ const opts = { itChecksTargetPerRefDefaultUp: it.skip, itChecksRefPerTokDefault: it, itChecksPriceChanges: it, + itChecksPriceChangesRefPerTok: it, itChecksNonZeroDefaultThreshold: it.skip, itHasRevenueHiding: it, resetFork: getResetFork(FORK_BLOCK), diff --git a/test/plugins/individual-collateral/mountain/USDMCollateral.test.ts b/test/plugins/individual-collateral/mountain/USDMCollateral.test.ts index 570c60345..417f7ba66 100644 --- a/test/plugins/individual-collateral/mountain/USDMCollateral.test.ts +++ b/test/plugins/individual-collateral/mountain/USDMCollateral.test.ts @@ -310,6 +310,7 @@ const opts = { itChecksTargetPerRefDefaultUp: it, itChecksRefPerTokDefault: it, itChecksPriceChanges: it, + itChecksPriceChangesRefPerTok: it, itChecksNonZeroDefaultThreshold: it, itHasRevenueHiding: it.skip, // implemented in this file collateralName: 'USDM Collateral', diff --git a/test/plugins/individual-collateral/pirex-eth/ApxEthCollateral.test.ts b/test/plugins/individual-collateral/pirex-eth/ApxEthCollateral.test.ts index db143ad60..86afe5a9a 100644 --- a/test/plugins/individual-collateral/pirex-eth/ApxEthCollateral.test.ts +++ b/test/plugins/individual-collateral/pirex-eth/ApxEthCollateral.test.ts @@ -377,6 +377,7 @@ const opts = { itChecksTargetPerRefDefaultUp: it, itChecksRefPerTokDefault: it.skip, // implemented in this file itChecksPriceChanges: it, + itChecksPriceChangesRefPerTok: it, itChecksNonZeroDefaultThreshold: it, itHasRevenueHiding: it.skip, // implemented in this file resetFork, diff --git a/test/plugins/individual-collateral/pluginTestTypes.ts b/test/plugins/individual-collateral/pluginTestTypes.ts index aa70a23c1..58cbab572 100644 --- a/test/plugins/individual-collateral/pluginTestTypes.ts +++ b/test/plugins/individual-collateral/pluginTestTypes.ts @@ -100,6 +100,9 @@ export interface CollateralTestSuiteFixtures // toggle on or off: tests that focus on price changes itChecksPriceChanges: Mocha.TestFunction | Mocha.PendingTestFunction + // toggle on or off: tests that focus on price change around refPerTok manipulation + itChecksPriceChangesRefPerTok: Mocha.TestFunction | Mocha.PendingTestFunction + // toggle on or off: tests that focus on revenue hiding (off if plugin does not hide revenue) itHasRevenueHiding: Mocha.TestFunction | Mocha.PendingTestFunction diff --git a/test/plugins/individual-collateral/rocket-eth/RethCollateralTestSuite.test.ts b/test/plugins/individual-collateral/rocket-eth/RethCollateralTestSuite.test.ts index f766a3bc0..27bd5a29d 100644 --- a/test/plugins/individual-collateral/rocket-eth/RethCollateralTestSuite.test.ts +++ b/test/plugins/individual-collateral/rocket-eth/RethCollateralTestSuite.test.ts @@ -275,6 +275,7 @@ const opts = { itChecksTargetPerRefDefaultUp: it, itChecksRefPerTokDefault: it, itChecksPriceChanges: it, + itChecksPriceChangesRefPerTok: it, itChecksNonZeroDefaultThreshold: it, itHasRevenueHiding: it, resetFork, From 46a6d386fde3cf1e3926d7ae819ce0b00667c674 Mon Sep 17 00:00:00 2001 From: Taylor Brent Date: Tue, 15 Oct 2024 21:10:10 -0400 Subject: [PATCH 06/40] demurrage collateral factory --- .../factories/DemurrageCollateralFactory.sol | 25 +++++++++++++++++++ 1 file changed, 25 insertions(+) create mode 100644 contracts/facade/factories/DemurrageCollateralFactory.sol diff --git a/contracts/facade/factories/DemurrageCollateralFactory.sol b/contracts/facade/factories/DemurrageCollateralFactory.sol new file mode 100644 index 000000000..04adddcab --- /dev/null +++ b/contracts/facade/factories/DemurrageCollateralFactory.sol @@ -0,0 +1,25 @@ +// SPDX-License-Identifier: BlueOak-1.0.0 +pragma solidity 0.8.19; + +import "../../plugins/assets/DemurrageCollateral.sol"; + +/** + * @title DemurrageCollateralFactory + */ +contract DemurrageCollateralFactory { + event DemurrageCollateralDeployed(address indexed collateral); + + bytes32 public constant USD = bytes32("USD"); + + function deployNewDemurrageCollateral( + CollateralConfig memory config, + DemurrageConfig memory demurrageConfig + ) external returns (address newCollateral) { + if (demurrageConfig.isFiat) { + require(config.targetName == USD, "isFiat only compatible with USD"); + } + + newCollateral = address(new DemurrageCollateral(config, demurrageConfig)); + emit DemurrageCollateralDeployed(newCollateral); + } +} From d579601d861cbd25c8988468e7188eacf896ef79 Mon Sep 17 00:00:00 2001 From: Taylor Brent Date: Tue, 15 Oct 2024 21:10:15 -0400 Subject: [PATCH 07/40] task for deployment --- .../create-demurrage-collateral-factory.ts | 57 +++++++++++++++++++ tasks/index.ts | 1 + 2 files changed, 58 insertions(+) create mode 100644 tasks/deployment/create-demurrage-collateral-factory.ts diff --git a/tasks/deployment/create-demurrage-collateral-factory.ts b/tasks/deployment/create-demurrage-collateral-factory.ts new file mode 100644 index 000000000..039f1897f --- /dev/null +++ b/tasks/deployment/create-demurrage-collateral-factory.ts @@ -0,0 +1,57 @@ +import { getChainId } from '../../common/blockchain-utils' +import { task, types } from 'hardhat/config' +import { DemurrageCollateralFactory } from '../../typechain' + +task('create-demurrage-collateral-factory', 'Deploys a DemurrageCollateralFactory') + .addOptionalParam('noOutput', 'Suppress output', false, types.boolean) + .setAction(async (params, hre) => { + const [wallet] = await hre.ethers.getSigners() + + const chainId = await getChainId(hre) + + if (!params.noOutput) { + console.log( + `Deploying DemurrageCollateralFactory to ${hre.network.name} (${chainId}) with burner account ${wallet.address}` + ) + } + + const FactoryFactory = await hre.ethers.getContractFactory('DemurrageCollateralFactory') + const demurrageCollateralFactory = ( + await FactoryFactory.connect(wallet).deploy() + ) + await demurrageCollateralFactory.deployed() + + if (!params.noOutput) { + console.log( + `Deployed DemurrageCollateralFactory to ${hre.network.name} (${chainId}): ${demurrageCollateralFactory.address}` + ) + } + + // Uncomment to verify + if (!params.noOutput) { + console.log('sleeping 30s') + } + + // Sleep to ensure API is in sync with chain + await new Promise((r) => setTimeout(r, 30000)) // 30s + + if (!params.noOutput) { + console.log('verifying') + } + + /** ******************** Verify DemurrageCollateralFactory ****************************************/ + console.time('Verifying DemurrageCollateralFactory') + await hre.run('verify:verify', { + address: demurrageCollateralFactory.address, + constructorArguments: [], + contract: + 'contracts/facade/factories/DemurrageCollateralFactory.sol:DemurrageCollateralFactory', + }) + console.timeEnd('Verifying DemurrageCollateralFactory') + + if (!params.noOutput) { + console.log('verified') + } + + return { demurrageCollateralFactory: demurrageCollateralFactory.address } + }) diff --git a/tasks/index.ts b/tasks/index.ts index c4c0e13c4..dc95eebb1 100644 --- a/tasks/index.ts +++ b/tasks/index.ts @@ -17,6 +17,7 @@ import './deployment/mock/deploy-mock-wbtc' import './deployment/deploy-easyauction' import './deployment/create-deployer-registry' import './deployment/create-curve-oracle-factory' +import './deployment/create-demurrage-collateral-factory' import './deployment/deploy-facade-monitor' import './deployment/empty-wallet' import './deployment/cancel-tx' From e44e0813391e7dd1d1899a550999f7c87de7dd6b Mon Sep 17 00:00:00 2001 From: Taylor Brent Date: Wed, 16 Oct 2024 14:46:23 -0400 Subject: [PATCH 08/40] cbBTC + EURC deployment scripts (base) --- common/configuration.ts | 10 ++ .../plugins/assets/DemurrageCollateral.sol | 79 +++++++++------ scripts/deploy.ts | 2 + .../phase2-assets/collaterals/deploy_cbbtc.ts | 98 +++++++++++++++++++ .../phase2-assets/collaterals/deploy_eurc.ts | 98 +++++++++++++++++++ .../dtf/PAXGCollateralTestSuite.test.ts | 4 +- .../individual-collateral/dtf/constants.ts | 6 ++ 7 files changed, 265 insertions(+), 32 deletions(-) create mode 100644 scripts/deployment/phase2-assets/collaterals/deploy_cbbtc.ts create mode 100644 scripts/deployment/phase2-assets/collaterals/deploy_eurc.ts diff --git a/common/configuration.ts b/common/configuration.ts index 9a561261b..1bea82955 100644 --- a/common/configuration.ts +++ b/common/configuration.ts @@ -117,7 +117,10 @@ export interface ITokens { USDM?: string wUSDM?: string + // Demurrage collateral PAXG?: string + cbBTC?: string + EURC?: string } export type ITokensKeys = Array @@ -130,6 +133,7 @@ export interface IFeeds { ETHUSD?: string wstETHstETH?: string XAU?: string + EUR?: string } export interface IPools { @@ -519,6 +523,8 @@ export const networkConfig: { [key: string]: INetworkConfig } = { sUSDbC: '0x4c80e24119cfb836cdf0a6b53dc23f04f7e652ca', wstETH: '0xc1CBa3fCea344f92D9239c08C0568f6F2F0ee452', STG: '0xE3B53AF74a4BF62Ae5511055290838050bf764Df', + cbBTC: '0xcbB7C0000aB88B473b1f5aFd9ef808440eed33Bf', + EURC: '0x60a3E35Cc302bFA44Cb288Bc5a4F316Fdb1adb42', }, chainlinkFeeds: { DAI: '0x591e79239a7d679378ec8c847e5038150364c78f', // 0.3%, 24hr @@ -535,6 +541,10 @@ export const networkConfig: { [key: string]: INetworkConfig } = { stETHETH: '0xf586d0728a47229e747d824a939000Cf21dEF5A0', // 0.5%, 24h ETHUSD: '0x71041dddad3595F9CEd3DcCFBe3D1F4b0a16Bb70', // 0.15%, 20min wstETHstETH: '0xB88BAc61a4Ca37C43a3725912B1f472c9A5bc061', // 0.5%, 24h + BTC: '0x64c911996D3c6aC71f9b455B1E8E7266BcbD848F', // 0.1%, 1200s + cbBTC: '0x07DA0E54543a844a80ABE69c8A12F22B3aA59f9D', // 0.5%, 24h + EURC: '0xDAe398520e2B67cd3f27aeF9Cf14D93D927f8250', // 0.3%, 24h + EUR: '0xc91D87E81faB8f93699ECf7Ee9B44D11e1D53F0F', // 0.3%, 24h }, GNOSIS_EASY_AUCTION: '0xb1875Feaeea32Bbb02DE83D81772e07E37A40f02', // mock COMET_REWARDS: '0x123964802e6ABabBE1Bc9547D72Ef1B69B00A6b1', diff --git a/contracts/plugins/assets/DemurrageCollateral.sol b/contracts/plugins/assets/DemurrageCollateral.sol index 462ffb578..c616d795e 100644 --- a/contracts/plugins/assets/DemurrageCollateral.sol +++ b/contracts/plugins/assets/DemurrageCollateral.sol @@ -4,9 +4,11 @@ pragma solidity 0.8.19; import "./FiatCollateral.sol"; struct DemurrageConfig { - bool isFiat; uint192 fee; // {1/s} per-second inflation/deflation of refPerTok/targetPerRef // + bool isFiat; // if true: {target} == {UoA} + bool targetUnitFeed0; // if true: feed0 is {target/tok} + // // optional extra feed AggregatorV3Interface feed1; // empty or {UoA/target} uint48 timeout1; // {s} @@ -17,13 +19,13 @@ struct DemurrageConfig { * @title DemurrageCollateral * @notice Collateral plugin for a genneralized demurrage collateral (i.e /w management fee) * - * 1 feed: - * - feed0 {UoA/tok} + * if only 1 feed: + * - feed0/chainlinkFeed must be {UoA/tok} * - apply issuance premium IFF isFiat is true * 2 feeds: - * - feed0 {target/tok} - * - feed1 {UoA/target} - * - apply issuance premium using feed0 + * - feed0: targetUnitFeed0 ? {target/tok} : {UoA/tok} + * - feed1: {UoA/target} + * - apply issuance premium * * - tok = Tokenized X * - ref = Virtual (inflationary) X @@ -35,9 +37,10 @@ contract DemurrageCollateral is FiatCollateral { using OracleLib for AggregatorV3Interface; bool internal immutable isFiat; + bool internal immutable targetUnitFeed0; // if true: feed0 is {target/tok} - // For each token, we maintain up to two feeds/timeouts/errors - AggregatorV3Interface internal immutable feed0; // {UoA/tok} or {target/tok} + // up to 2 feeds/timeouts/errors + AggregatorV3Interface internal immutable feed0; // targetUnitFeed0 ? {target/tok} : {UoA/tok} AggregatorV3Interface internal immutable feed1; // empty or {UoA/target} uint48 internal immutable timeout0; // {s} uint48 internal immutable timeout1; // {s} @@ -45,25 +48,28 @@ contract DemurrageCollateral is FiatCollateral { uint192 internal immutable error1; // {1} // immutable in spirit -- cannot be because of FiatCollateral's targetPerRef() call - // TODO would love to find a way to make these immutable + // TODO would love to find a way to make these immutable for gas reasons uint48 public t0; // {s} deployment timestamp uint192 public fee; // {1/s} demurrage fee; manifests as reference unit inflation - /// @param config.chainlinkFeed unused - /// @param config.oracleTimeout unused - /// @param config.oracleError unused - /// @param demurrageConfig.fee {1/s} fraction of the reference unit to inflate each second - /// @param demurrageConfig.feed0 {UoA/tok} or {target/tok} + /// @param config.chainlinkFeed => feed0: {UoA/tok} or {target/tok} + /// @param config.oracleTimeout => timeout0 + /// @param config.oracleError => error0 /// @param demurrageConfig.feed1 empty or {UoA/target} /// @param demurrageConfig.isFiat true iff {target} == {UoA} + /// @param demurrageConfig.targetUnitfeed0 true iff feed0 is {target/tok} units + /// @param demurrageConfig.fee {1/s} fraction of the reference unit to inflate each second constructor(CollateralConfig memory config, DemurrageConfig memory demurrageConfig) FiatCollateral(config) { isFiat = demurrageConfig.isFiat; + targetUnitFeed0 = demurrageConfig.targetUnitFeed0; if (demurrageConfig.feed1 != AggregatorV3Interface(address(0))) { require(demurrageConfig.timeout1 != 0, "missing timeout1"); require(demurrageConfig.error1 > 0 && demurrageConfig.error1 < FIX_ONE, "bad error1"); + } else { + require(!demurrageConfig.targetUnitFeed0, "missing UoA info"); } feed0 = config.chainlinkFeed; @@ -81,7 +87,7 @@ contract DemurrageCollateral is FiatCollateral { /// Should NOT be manipulable by MEV /// @return low {UoA/tok} The low price estimate /// @return high {UoA/tok} The high price estimate - /// @return pegPrice {target/ref} The unadjusted price observed in the peg + /// @return pegPrice {target/tok} The undecayed price observed in the peg /// can be 0 if only 1 feed AND not fiat function tryPrice() external @@ -93,29 +99,40 @@ contract DemurrageCollateral is FiatCollateral { uint192 pegPrice ) { - pegPrice = FIX_ONE; // undecayed rate that won't trigger default or issuance premium + // This plugin handles pegPrice differently than most -- since FiatCollateral saves + // valid peg ranges at deployment time, they do not account for the decay due to the + // demurrage fee + // + // To account for this, the pegPrice is returned in units of {target/tok} + // aka {target/ref} without the reference unit inflation - // Use only 1 feed if 2nd feed not defined; else multiply together - // if only 1 feed: `y` is FIX_ONE and `yErr` is 0 + pegPrice = FIX_ONE; // uninflated rate that won't trigger default or issuance premium - uint192 x = feed0.price(timeout0); // initially {UoA/tok} + uint192 x = feed0.price(timeout0); // {UoA/tok} uint192 xErr = error0; - uint192 y = FIX_ONE; - uint192 yErr; - if (address(feed1) != address(0)) { - y = feed1.price(timeout1); // {UoA/target} - yErr = error1; - // {target/ref} = {UoA/target} - pegPrice = y; // no demurrage needed + low = x.mul(FIX_ONE - xErr); // {UoA/tok} + high = x.mul(FIX_ONE + xErr); // {UoA/tok} + + if (address(feed1) != address(0)) { + if (targetUnitFeed0) { + pegPrice = x; // {target/tok} + + uint192 y = feed1.price(timeout1); // {UoA/target} + uint192 yErr = error1; + + // Multiply x and y + low = low.mul(y.mul(FIX_ONE - yErr), FLOOR); + high = high.mul(y.mul(FIX_ONE + yErr), CEIL); + } else { + // {target/tok} = {UoA/tok} / {UoA/target} + pegPrice = x.div(feed1.price(timeout1), ROUND); + } } else if (isFiat) { - // {target/ref} = {UoA/tok} - pegPrice = x; // no demurrage needed + // {target/tok} = {UoA/tok} because {target} == {UoA} + pegPrice = x; } - // {UoA/tok} = {UoA/ref} * {ref/tok} - low = x.mul(FIX_ONE - xErr).mul(y.mul(FIX_ONE - yErr), FLOOR); - high = x.mul(FIX_ONE + xErr).mul(y.mul(FIX_ONE + yErr), CEIL); assert(low <= high); } diff --git a/scripts/deploy.ts b/scripts/deploy.ts index a1347953a..341920195 100644 --- a/scripts/deploy.ts +++ b/scripts/deploy.ts @@ -103,6 +103,8 @@ async function main() { 'phase2-assets/collaterals/deploy_aave_v3_usdc.ts', 'phase2-assets/collaterals/deploy_lido_wsteth_collateral.ts', 'phase2-assets/collaterals/deploy_cbeth_collateral.ts', + 'phase2-assets/collaterals/deploy_cbbtc.ts', + 'phase2-assets/collaterals/deploy_eurc.ts', 'phase2-assets/assets/deploy_stg.ts' ) } else if (chainId == '42161' || chainId == '421614') { diff --git a/scripts/deployment/phase2-assets/collaterals/deploy_cbbtc.ts b/scripts/deployment/phase2-assets/collaterals/deploy_cbbtc.ts new file mode 100644 index 000000000..0a2f42ef9 --- /dev/null +++ b/scripts/deployment/phase2-assets/collaterals/deploy_cbbtc.ts @@ -0,0 +1,98 @@ +import fs from 'fs' +import hre from 'hardhat' +import { getChainId } from '../../../../common/blockchain-utils' +import { baseL2Chains, networkConfig } from '../../../../common/configuration' +import { bn, fp } from '../../../../common/numbers' +import { expect } from 'chai' +import { CollateralStatus } from '../../../../common/constants' +import { + getDeploymentFile, + getAssetCollDeploymentFilename, + IAssetCollDeployments, + getDeploymentFilename, + fileExists, +} from '../../common' +import { + DELAY_UNTIL_DEFAULT, + ONE_PERCENT_FEE, +} from '../../../../test/plugins/individual-collateral/dtf/constants' +import { priceTimeout } from '../../utils' +import { DemurrageCollateral } from '../../../../typechain' +import { ContractFactory } from 'ethers' + +async function main() { + // ==== Read Configuration ==== + const [deployer] = await hre.ethers.getSigners() + + const chainId = await getChainId(hre) + + console.log(`Deploying Collateral to network ${hre.network.name} (${chainId}) + with burner account: ${deployer.address}`) + + if (!networkConfig[chainId]) { + throw new Error(`Missing network configuration for ${hre.network.name}`) + } + + // Get phase1 deployment + const phase1File = getDeploymentFilename(chainId) + if (!fileExists(phase1File)) { + throw new Error(`${phase1File} doesn't exist yet. Run phase 1`) + } + // Check previous step completed + const assetCollDeploymentFilename = getAssetCollDeploymentFilename(chainId) + const assetCollDeployments = getDeploymentFile(assetCollDeploymentFilename) + + const deployedCollateral: string[] = [] + + /******** Deploy cbBTC Demurrage Collateral - cbBTC **************************/ + + if (!baseL2Chains.includes(hre.network.name)) { + throw new Error(`Unsupported chainId: ${chainId}`) + } + + const DemurrageCollateralFactory: ContractFactory = await hre.ethers.getContractFactory( + 'DemurrageCollateral' + ) + + const collateral = await DemurrageCollateralFactory.connect(deployer).deploy( + { + erc20: networkConfig[chainId].tokens.cbBTC, + targetName: hre.ethers.utils.formatBytes32String('BTC'), + priceTimeout: priceTimeout.toString(), + chainlinkFeed: networkConfig[chainId].chainlinkFeeds.cbBTC, // {UoA/tok} + oracleError: fp('0.005').toString(), // 0.5% + oracleTimeout: bn('86400').toString(), // 24 hr + maxTradeVolume: fp('1e6').toString(), // $1m, + defaultThreshold: bn('0'), + delayUntilDefault: DELAY_UNTIL_DEFAULT, + }, + { + isFiat: false, + targetUnitFeed0: false, + fee: ONE_PERCENT_FEE, + feed1: networkConfig[chainId].chainlinkFeeds.BTC, // {UoA/target} + timeout1: bn('1200'), // 20 min + error1: fp('0.001').toString(), // 0.1% + } + ) + await collateral.deployed() + + console.log(`Deployed cbBTC to ${hre.network.name} (${chainId}): ${collateral.address}`) + await (await collateral.refresh()).wait() + expect(await collateral.status()).to.equal(CollateralStatus.SOUND) + + assetCollDeployments.collateral.cbBTC = collateral.address + assetCollDeployments.erc20s.cbBTC = networkConfig[chainId].tokens.cbBTC + deployedCollateral.push(collateral.address.toString()) + + fs.writeFileSync(assetCollDeploymentFilename, JSON.stringify(assetCollDeployments, null, 2)) + + console.log(`Deployed collateral to ${hre.network.name} (${chainId}) + New deployments: ${deployedCollateral} + Deployment file: ${assetCollDeploymentFilename}`) +} + +main().catch((error) => { + console.error(error) + process.exitCode = 1 +}) diff --git a/scripts/deployment/phase2-assets/collaterals/deploy_eurc.ts b/scripts/deployment/phase2-assets/collaterals/deploy_eurc.ts new file mode 100644 index 000000000..29889ff18 --- /dev/null +++ b/scripts/deployment/phase2-assets/collaterals/deploy_eurc.ts @@ -0,0 +1,98 @@ +import fs from 'fs' +import hre from 'hardhat' +import { getChainId } from '../../../../common/blockchain-utils' +import { baseL2Chains, networkConfig } from '../../../../common/configuration' +import { bn, fp } from '../../../../common/numbers' +import { expect } from 'chai' +import { CollateralStatus } from '../../../../common/constants' +import { + getDeploymentFile, + getAssetCollDeploymentFilename, + IAssetCollDeployments, + getDeploymentFilename, + fileExists, +} from '../../common' +import { + DELAY_UNTIL_DEFAULT, + ONE_PERCENT_FEE, +} from '../../../../test/plugins/individual-collateral/dtf/constants' +import { priceTimeout } from '../../utils' +import { DemurrageCollateral } from '../../../../typechain' +import { ContractFactory } from 'ethers' + +async function main() { + // ==== Read Configuration ==== + const [deployer] = await hre.ethers.getSigners() + + const chainId = await getChainId(hre) + + console.log(`Deploying Collateral to network ${hre.network.name} (${chainId}) + with burner account: ${deployer.address}`) + + if (!networkConfig[chainId]) { + throw new Error(`Missing network configuration for ${hre.network.name}`) + } + + // Get phase1 deployment + const phase1File = getDeploymentFilename(chainId) + if (!fileExists(phase1File)) { + throw new Error(`${phase1File} doesn't exist yet. Run phase 1`) + } + // Check previous step completed + const assetCollDeploymentFilename = getAssetCollDeploymentFilename(chainId) + const assetCollDeployments = getDeploymentFile(assetCollDeploymentFilename) + + const deployedCollateral: string[] = [] + + /******** Deploy EURC Demurrage Collateral - EURC **************************/ + + if (!baseL2Chains.includes(hre.network.name)) { + throw new Error(`Unsupported chainId: ${chainId}`) + } + + const DemurrageCollateralFactory: ContractFactory = await hre.ethers.getContractFactory( + 'DemurrageCollateral' + ) + + const collateral = await DemurrageCollateralFactory.connect(deployer).deploy( + { + erc20: networkConfig[chainId].tokens.EURC, + targetName: hre.ethers.utils.formatBytes32String('EUR'), + priceTimeout: priceTimeout.toString(), + chainlinkFeed: networkConfig[chainId].chainlinkFeeds.EURC, + oracleError: fp('0.003').toString(), // 0.3% + oracleTimeout: bn('86400').toString(), // 24 hr + maxTradeVolume: fp('1e6').toString(), // $1m, + defaultThreshold: bn('0'), + delayUntilDefault: DELAY_UNTIL_DEFAULT, + }, + { + isFiat: false, + targetUnitFeed0: false, + fee: ONE_PERCENT_FEE, + feed1: networkConfig[chainId].chainlinkFeeds.EURC, // {UoA/target} + timeout1: bn('86400').toString(), // 24 hr + error1: fp('0.003').toString(), // 0.3% + } + ) + await collateral.deployed() + + console.log(`Deployed EURC to ${hre.network.name} (${chainId}): ${collateral.address}`) + await (await collateral.refresh()).wait() + expect(await collateral.status()).to.equal(CollateralStatus.SOUND) + + assetCollDeployments.collateral.EURC = collateral.address + assetCollDeployments.erc20s.EURC = networkConfig[chainId].tokens.EURC + deployedCollateral.push(collateral.address.toString()) + + fs.writeFileSync(assetCollDeploymentFilename, JSON.stringify(assetCollDeployments, null, 2)) + + console.log(`Deployed collateral to ${hre.network.name} (${chainId}) + New deployments: ${deployedCollateral} + Deployment file: ${assetCollDeploymentFilename}`) +} + +main().catch((error) => { + console.error(error) + process.exitCode = 1 +}) diff --git a/test/plugins/individual-collateral/dtf/PAXGCollateralTestSuite.test.ts b/test/plugins/individual-collateral/dtf/PAXGCollateralTestSuite.test.ts index 673fa5ea6..13b9404a2 100644 --- a/test/plugins/individual-collateral/dtf/PAXGCollateralTestSuite.test.ts +++ b/test/plugins/individual-collateral/dtf/PAXGCollateralTestSuite.test.ts @@ -11,6 +11,7 @@ import { SignerWithAddress } from '@nomiclabs/hardhat-ethers/signers' import { DELAY_UNTIL_DEFAULT, PAXG, + ONE_PERCENT_FEE, ORACLE_ERROR, ORACLE_TIMEOUT, PRICE_TIMEOUT, @@ -35,7 +36,7 @@ export const defaultPAXGCollateralOpts: PAXGCollateralOpts = { oracleTimeout: ORACLE_TIMEOUT, oracleError: ORACLE_ERROR, maxTradeVolume: MAX_TRADE_VOL, - fee: fp('1e-9'), // about 3.1% annually + fee: ONE_PERCENT_FEE, } export const deployCollateral = async (opts: PAXGCollateralOpts = {}): Promise => { @@ -58,6 +59,7 @@ export const deployCollateral = async (opts: PAXGCollateralOpts = {}): Promise Date: Wed, 16 Oct 2024 14:48:27 -0400 Subject: [PATCH 09/40] add missing kw --- test/plugins/individual-collateral/collateralTests.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/test/plugins/individual-collateral/collateralTests.ts b/test/plugins/individual-collateral/collateralTests.ts index fcc719230..638a4fe8e 100644 --- a/test/plugins/individual-collateral/collateralTests.ts +++ b/test/plugins/individual-collateral/collateralTests.ts @@ -1041,6 +1041,7 @@ export default function fn( }, { isFiat: false, + targetUnitFeed0: false, fee: bn('0'), feed1: ZERO_ADDRESS, timeout1: bn(0), From 0b1b1da904e309e25b00b68d47cd3bfc29ca730d Mon Sep 17 00:00:00 2001 From: Taylor Brent Date: Wed, 16 Oct 2024 17:00:30 -0400 Subject: [PATCH 10/40] PAXG mainnet scripts --- scripts/deploy.ts | 3 +- .../phase2-assets/collaterals/deploy_cbbtc.ts | 2 +- .../phase2-assets/collaterals/deploy_eurc.ts | 2 +- .../phase2-assets/collaterals/deploy_paxg.ts | 98 +++++++++++++++++++ 4 files changed, 102 insertions(+), 3 deletions(-) create mode 100644 scripts/deployment/phase2-assets/collaterals/deploy_paxg.ts diff --git a/scripts/deploy.ts b/scripts/deploy.ts index 341920195..89a30a347 100644 --- a/scripts/deploy.ts +++ b/scripts/deploy.ts @@ -90,7 +90,8 @@ async function main() { 'phase2-assets/collaterals/deploy_USDe.ts', 'phase2-assets/assets/deploy_crv.ts', 'phase2-assets/assets/deploy_cvx.ts', - 'phase2-assets/collaterals/deploy_pyusd.ts' + 'phase2-assets/collaterals/deploy_pyusd.ts', + 'phase2-assets/collaterals/deploy_paxg.ts' ) } else if (chainId == '8453' || chainId == '84531') { // Base L2 chains diff --git a/scripts/deployment/phase2-assets/collaterals/deploy_cbbtc.ts b/scripts/deployment/phase2-assets/collaterals/deploy_cbbtc.ts index 0a2f42ef9..527b7746b 100644 --- a/scripts/deployment/phase2-assets/collaterals/deploy_cbbtc.ts +++ b/scripts/deployment/phase2-assets/collaterals/deploy_cbbtc.ts @@ -63,7 +63,7 @@ async function main() { oracleError: fp('0.005').toString(), // 0.5% oracleTimeout: bn('86400').toString(), // 24 hr maxTradeVolume: fp('1e6').toString(), // $1m, - defaultThreshold: bn('0'), + defaultThreshold: fp('0.02').add(fp('0.005')).toString(), delayUntilDefault: DELAY_UNTIL_DEFAULT, }, { diff --git a/scripts/deployment/phase2-assets/collaterals/deploy_eurc.ts b/scripts/deployment/phase2-assets/collaterals/deploy_eurc.ts index 29889ff18..1c3d71fd3 100644 --- a/scripts/deployment/phase2-assets/collaterals/deploy_eurc.ts +++ b/scripts/deployment/phase2-assets/collaterals/deploy_eurc.ts @@ -63,7 +63,7 @@ async function main() { oracleError: fp('0.003').toString(), // 0.3% oracleTimeout: bn('86400').toString(), // 24 hr maxTradeVolume: fp('1e6').toString(), // $1m, - defaultThreshold: bn('0'), + defaultThreshold: fp('0.01').add(fp('0.003')).toString(), delayUntilDefault: DELAY_UNTIL_DEFAULT, }, { diff --git a/scripts/deployment/phase2-assets/collaterals/deploy_paxg.ts b/scripts/deployment/phase2-assets/collaterals/deploy_paxg.ts new file mode 100644 index 000000000..6279892ad --- /dev/null +++ b/scripts/deployment/phase2-assets/collaterals/deploy_paxg.ts @@ -0,0 +1,98 @@ +import fs from 'fs' +import hre from 'hardhat' +import { getChainId } from '../../../../common/blockchain-utils' +import { arbitrumL2Chains, baseL2Chains, networkConfig } from '../../../../common/configuration' +import { bn, fp } from '../../../../common/numbers' +import { expect } from 'chai' +import { CollateralStatus, ZERO_ADDRESS } from '../../../../common/constants' +import { + getDeploymentFile, + getAssetCollDeploymentFilename, + IAssetCollDeployments, + getDeploymentFilename, + fileExists, +} from '../../common' +import { + DELAY_UNTIL_DEFAULT, + ONE_PERCENT_FEE, +} from '../../../../test/plugins/individual-collateral/dtf/constants' +import { priceTimeout } from '../../utils' +import { DemurrageCollateral } from '../../../../typechain' +import { ContractFactory } from 'ethers' + +async function main() { + // ==== Read Configuration ==== + const [deployer] = await hre.ethers.getSigners() + + const chainId = await getChainId(hre) + + console.log(`Deploying Collateral to network ${hre.network.name} (${chainId}) + with burner account: ${deployer.address}`) + + if (!networkConfig[chainId]) { + throw new Error(`Missing network configuration for ${hre.network.name}`) + } + + // Get phase1 deployment + const phase1File = getDeploymentFilename(chainId) + if (!fileExists(phase1File)) { + throw new Error(`${phase1File} doesn't exist yet. Run phase 1`) + } + // Check previous step completed + const assetCollDeploymentFilename = getAssetCollDeploymentFilename(chainId) + const assetCollDeployments = getDeploymentFile(assetCollDeploymentFilename) + + const deployedCollateral: string[] = [] + + /******** Deploy PAXG Demurrage Collateral - PAXG **************************/ + + if (baseL2Chains.includes(hre.network.name) || arbitrumL2Chains.includes(hre.network.name)) { + throw new Error(`Unsupported chainId: ${chainId}`) + } + + const DemurrageCollateralFactory: ContractFactory = await hre.ethers.getContractFactory( + 'DemurrageCollateral' + ) + + const collateral = await DemurrageCollateralFactory.connect(deployer).deploy( + { + erc20: networkConfig[chainId].tokens.PAXG, + targetName: hre.ethers.utils.formatBytes32String('XAU'), + priceTimeout: priceTimeout.toString(), + chainlinkFeed: networkConfig[chainId].chainlinkFeeds.XAU, // {UoA/tok} + oracleError: fp('0.003').toString(), // 0.3% + oracleTimeout: bn('86400').toString(), // 24 hr + maxTradeVolume: fp('1e6').toString(), // $1m, + defaultThreshold: bn('0'), + delayUntilDefault: DELAY_UNTIL_DEFAULT, + }, + { + isFiat: false, + targetUnitFeed0: false, + fee: ONE_PERCENT_FEE, + feed1: ZERO_ADDRESS, + timeout1: bn('0'), + error1: bn('0'), + } + ) + await collateral.deployed() + + console.log(`Deployed PAXG to ${hre.network.name} (${chainId}): ${collateral.address}`) + await (await collateral.refresh()).wait() + expect(await collateral.status()).to.equal(CollateralStatus.SOUND) + + assetCollDeployments.collateral.PAXG = collateral.address + assetCollDeployments.erc20s.PAXG = networkConfig[chainId].tokens.PAXG + deployedCollateral.push(collateral.address.toString()) + + fs.writeFileSync(assetCollDeploymentFilename, JSON.stringify(assetCollDeployments, null, 2)) + + console.log(`Deployed collateral to ${hre.network.name} (${chainId}) + New deployments: ${deployedCollateral} + Deployment file: ${assetCollDeploymentFilename}`) +} + +main().catch((error) => { + console.error(error) + process.exitCode = 1 +}) From 0096d2509677ee7778cbfd95bd6108ad0da72c5c Mon Sep 17 00:00:00 2001 From: Taylor Brent Date: Thu, 17 Oct 2024 13:05:41 -0400 Subject: [PATCH 11/40] scripts and new addresses --- .../addresses/8453-tmp-assets-collateral.json | 10 ++- .../phase2-assets/collaterals/deploy_eurc.ts | 4 +- .../collateral-plugins/verify_cbbtc.ts | 78 ++++++++++++++++++ .../collateral-plugins/verify_eurc.ts | 78 ++++++++++++++++++ .../collateral-plugins/verify_paxg.ts | 79 +++++++++++++++++++ scripts/verify_etherscan.ts | 7 +- 6 files changed, 249 insertions(+), 7 deletions(-) create mode 100644 scripts/verification/collateral-plugins/verify_cbbtc.ts create mode 100644 scripts/verification/collateral-plugins/verify_eurc.ts create mode 100644 scripts/verification/collateral-plugins/verify_paxg.ts diff --git a/scripts/addresses/8453-tmp-assets-collateral.json b/scripts/addresses/8453-tmp-assets-collateral.json index 4602fbd2d..1cecc513d 100644 --- a/scripts/addresses/8453-tmp-assets-collateral.json +++ b/scripts/addresses/8453-tmp-assets-collateral.json @@ -11,7 +11,9 @@ "cbETH": "0x851B461a9744f4c9E996C03072cAB6f44Fa04d0D", "saBasUSDC": "0xC19f5d60e2Aca1174f3D5Fe189f0A69afaB76f50", "cUSDCv3": "0xf7a9D27c3B60c78c6F6e2c2d6ED6E8B94b352461", - "wstETH": "0x8b4374005291B8FCD14C4E947604b2FB3C660A73" + "wstETH": "0x8b4374005291B8FCD14C4E947604b2FB3C660A73", + "cbBTC": "0x1f59E25aF6ebc107B0c409784ac261de16318877", + "EURC": "0x0AD231AEDc090F15522EF8e2f9e2F4d07b5b50E4" }, "erc20s": { "COMP": "0x9e1028F5F1D5eDE59748FFceE5532509976840E0", @@ -23,6 +25,8 @@ "saBasUSDC": "0x6F6f81e5E66f503184f2202D83a79650c3285759", "STG": "0xE3B53AF74a4BF62Ae5511055290838050bf764Df", "cUSDCv3": "0x53f1Df4E5591Ae35Bf738742981669c3767241FA", - "wstETH": "0xc1CBa3fCea344f92D9239c08C0568f6F2F0ee452" + "wstETH": "0xc1CBa3fCea344f92D9239c08C0568f6F2F0ee452", + "cbBTC": "0xcbB7C0000aB88B473b1f5aFd9ef808440eed33Bf", + "EURC": "0x60a3E35Cc302bFA44Cb288Bc5a4F316Fdb1adb42" } -} +} \ No newline at end of file diff --git a/scripts/deployment/phase2-assets/collaterals/deploy_eurc.ts b/scripts/deployment/phase2-assets/collaterals/deploy_eurc.ts index 1c3d71fd3..321f5ce41 100644 --- a/scripts/deployment/phase2-assets/collaterals/deploy_eurc.ts +++ b/scripts/deployment/phase2-assets/collaterals/deploy_eurc.ts @@ -63,14 +63,14 @@ async function main() { oracleError: fp('0.003').toString(), // 0.3% oracleTimeout: bn('86400').toString(), // 24 hr maxTradeVolume: fp('1e6').toString(), // $1m, - defaultThreshold: fp('0.01').add(fp('0.003')).toString(), + defaultThreshold: fp('0.02').add(fp('0.003')).toString(), delayUntilDefault: DELAY_UNTIL_DEFAULT, }, { isFiat: false, targetUnitFeed0: false, fee: ONE_PERCENT_FEE, - feed1: networkConfig[chainId].chainlinkFeeds.EURC, // {UoA/target} + feed1: networkConfig[chainId].chainlinkFeeds.EUR, timeout1: bn('86400').toString(), // 24 hr error1: fp('0.003').toString(), // 0.3% } diff --git a/scripts/verification/collateral-plugins/verify_cbbtc.ts b/scripts/verification/collateral-plugins/verify_cbbtc.ts new file mode 100644 index 000000000..9b5d35ba6 --- /dev/null +++ b/scripts/verification/collateral-plugins/verify_cbbtc.ts @@ -0,0 +1,78 @@ +import hre from 'hardhat' +import { getChainId } from '../../../common/blockchain-utils' +import { baseL2Chains, networkConfig } from '../../../common/configuration' +import { bn, fp } from '../../../common/numbers' +import { verifyContract } from '../../deployment/utils' +import { + getDeploymentFile, + getAssetCollDeploymentFilename, + IAssetCollDeployments, + getDeploymentFilename, + fileExists, +} from '../../deployment/common' +import { + DELAY_UNTIL_DEFAULT, + ONE_PERCENT_FEE, +} from '../../../test/plugins/individual-collateral/dtf/constants' +import { priceTimeout } from '../../deployment/utils' + +async function main() { + // ==== Read Configuration ==== + const [deployer] = await hre.ethers.getSigners() + + const chainId = await getChainId(hre) + + console.log(`Deploying Collateral to network ${hre.network.name} (${chainId}) + with burner account: ${deployer.address}`) + + if (!networkConfig[chainId]) { + throw new Error(`Missing network configuration for ${hre.network.name}`) + } + + // Get phase1 deployment + const phase1File = getDeploymentFilename(chainId) + if (!fileExists(phase1File)) { + throw new Error(`${phase1File} doesn't exist yet. Run phase 1`) + } + // Check previous step completed + const assetCollDeploymentFilename = getAssetCollDeploymentFilename(chainId) + const assetCollDeployments = getDeploymentFile(assetCollDeploymentFilename) + + /******** Verify cbBTC Demurrage Collateral - cbBTC **************************/ + + if (!baseL2Chains.includes(hre.network.name)) { + throw new Error(`Unsupported chainId: ${chainId}`) + } + + await verifyContract( + chainId, + assetCollDeployments.collateral.cbBTC, + [ + { + erc20: networkConfig[chainId].tokens.cbBTC, + targetName: hre.ethers.utils.formatBytes32String('BTC'), + priceTimeout: priceTimeout.toString(), + chainlinkFeed: networkConfig[chainId].chainlinkFeeds.cbBTC, // {UoA/tok} + oracleError: fp('0.005').toString(), // 0.5% + oracleTimeout: bn('86400').toString(), // 24 hr + maxTradeVolume: fp('1e6').toString(), // $1m, + defaultThreshold: fp('0.02').add(fp('0.005')).toString(), + delayUntilDefault: DELAY_UNTIL_DEFAULT, + }, + { + isFiat: false, + targetUnitFeed0: false, + fee: ONE_PERCENT_FEE, + feed1: networkConfig[chainId].chainlinkFeeds.BTC, // {UoA/target} + timeout1: bn('1200'), // 20 min + error1: fp('0.001').toString(), // 0.1% + }, + ], + 'contracts/plugins/assets/DemurrageCollateral.sol:DemurrageCollateral' + ) +} + +main().catch((error) => { + console.error(error) + process.exitCode = 1 +}) diff --git a/scripts/verification/collateral-plugins/verify_eurc.ts b/scripts/verification/collateral-plugins/verify_eurc.ts new file mode 100644 index 000000000..a2ea29748 --- /dev/null +++ b/scripts/verification/collateral-plugins/verify_eurc.ts @@ -0,0 +1,78 @@ +import hre from 'hardhat' +import { getChainId } from '../../../common/blockchain-utils' +import { baseL2Chains, networkConfig } from '../../../common/configuration' +import { bn, fp } from '../../../common/numbers' +import { verifyContract } from '../../deployment/utils' +import { + getDeploymentFile, + getAssetCollDeploymentFilename, + IAssetCollDeployments, + getDeploymentFilename, + fileExists, +} from '../../deployment/common' +import { + DELAY_UNTIL_DEFAULT, + ONE_PERCENT_FEE, +} from '../../../test/plugins/individual-collateral/dtf/constants' +import { priceTimeout } from '../../deployment/utils' + +async function main() { + // ==== Read Configuration ==== + const [deployer] = await hre.ethers.getSigners() + + const chainId = await getChainId(hre) + + console.log(`Deploying Collateral to network ${hre.network.name} (${chainId}) + with burner account: ${deployer.address}`) + + if (!networkConfig[chainId]) { + throw new Error(`Missing network configuration for ${hre.network.name}`) + } + + // Get phase1 deployment + const phase1File = getDeploymentFilename(chainId) + if (!fileExists(phase1File)) { + throw new Error(`${phase1File} doesn't exist yet. Run phase 1`) + } + // Check previous step completed + const assetCollDeploymentFilename = getAssetCollDeploymentFilename(chainId) + const assetCollDeployments = getDeploymentFile(assetCollDeploymentFilename) + + /******** Verify EURC Demurrage Collateral - EURC **************************/ + + if (!baseL2Chains.includes(hre.network.name)) { + throw new Error(`Unsupported chainId: ${chainId}`) + } + + await verifyContract( + chainId, + assetCollDeployments.collateral.EURC, + [ + { + erc20: networkConfig[chainId].tokens.EURC, + targetName: hre.ethers.utils.formatBytes32String('EUR'), + priceTimeout: priceTimeout.toString(), + chainlinkFeed: networkConfig[chainId].chainlinkFeeds.EURC, // {UoA/tok} + oracleError: fp('0.003').toString(), // 0.3% + oracleTimeout: bn('86400').toString(), // 24 hr + maxTradeVolume: fp('1e6').toString(), // $1m, + defaultThreshold: fp('0.02').add(fp('0.003')).toString(), + delayUntilDefault: DELAY_UNTIL_DEFAULT, + }, + { + isFiat: false, + targetUnitFeed0: false, + fee: ONE_PERCENT_FEE, + feed1: networkConfig[chainId].chainlinkFeeds.EUR, // {UoA/target} + timeout1: bn('86400').toString(), // 24 hr + error1: fp('0.003').toString(), // 0.3% + }, + ], + 'contracts/plugins/assets/DemurrageCollateral.sol:DemurrageCollateral' + ) +} + +main().catch((error) => { + console.error(error) + process.exitCode = 1 +}) diff --git a/scripts/verification/collateral-plugins/verify_paxg.ts b/scripts/verification/collateral-plugins/verify_paxg.ts new file mode 100644 index 000000000..ab17035ae --- /dev/null +++ b/scripts/verification/collateral-plugins/verify_paxg.ts @@ -0,0 +1,79 @@ +import hre from 'hardhat' +import { getChainId } from '../../../common/blockchain-utils' +import { arbitrumL2Chains, baseL2Chains, networkConfig } from '../../../common/configuration' +import { bn, fp } from '../../../common/numbers' +import { ZERO_ADDRESS } from '../../../common/constants' +import { verifyContract } from '../../deployment/utils' +import { + getDeploymentFile, + getAssetCollDeploymentFilename, + IAssetCollDeployments, + getDeploymentFilename, + fileExists, +} from '../../deployment/common' +import { + DELAY_UNTIL_DEFAULT, + ONE_PERCENT_FEE, +} from '../../../test/plugins/individual-collateral/dtf/constants' +import { priceTimeout } from '../../deployment/utils' + +async function main() { + // ==== Read Configuration ==== + const [deployer] = await hre.ethers.getSigners() + + const chainId = await getChainId(hre) + + console.log(`Deploying Collateral to network ${hre.network.name} (${chainId}) + with burner account: ${deployer.address}`) + + if (!networkConfig[chainId]) { + throw new Error(`Missing network configuration for ${hre.network.name}`) + } + + // Get phase1 deployment + const phase1File = getDeploymentFilename(chainId) + if (!fileExists(phase1File)) { + throw new Error(`${phase1File} doesn't exist yet. Run phase 1`) + } + // Check previous step completed + const assetCollDeploymentFilename = getAssetCollDeploymentFilename(chainId) + const assetCollDeployments = getDeploymentFile(assetCollDeploymentFilename) + + /******** Verify PAXG Demurrage Collateral - PAXG **************************/ + + if (baseL2Chains.includes(hre.network.name) || arbitrumL2Chains.includes(hre.network.name)) { + throw new Error(`Unsupported chainId: ${chainId}`) + } + + await verifyContract( + chainId, + assetCollDeployments.collateral.PAXG, + [ + { + erc20: networkConfig[chainId].tokens.PAXG, + targetName: hre.ethers.utils.formatBytes32String('XAU'), + priceTimeout: priceTimeout.toString(), + chainlinkFeed: networkConfig[chainId].chainlinkFeeds.XAU, // {UoA/tok} + oracleError: fp('0.003').toString(), // 0.3% + oracleTimeout: bn('86400').toString(), // 24 hr + maxTradeVolume: fp('1e6').toString(), // $1m, + defaultThreshold: bn('0'), + delayUntilDefault: DELAY_UNTIL_DEFAULT, + }, + { + isFiat: false, + targetUnitFeed0: false, + fee: ONE_PERCENT_FEE, + feed1: ZERO_ADDRESS, + timeout1: bn('0'), + error1: bn('0'), + }, + ], + 'contracts/plugins/assets/DemurrageCollateral.sol:DemurrageCollateral' + ) +} + +main().catch((error) => { + console.error(error) + process.exitCode = 1 +}) diff --git a/scripts/verify_etherscan.ts b/scripts/verify_etherscan.ts index a448a9ee8..5df61ad6e 100644 --- a/scripts/verify_etherscan.ts +++ b/scripts/verify_etherscan.ts @@ -80,7 +80,8 @@ async function main() { 'collateral-plugins/verify_ethx.ts', 'collateral-plugins/verify_apxeth.ts', 'collateral-plugins/verify_USDe.ts', - 'collateral-plugins/verify_pyusd.ts' + 'collateral-plugins/verify_pyusd.ts', + 'collateral-plugins/verify_paxg.ts' ) } else if (chainId == '8453' || chainId == '84531') { // Base L2 chains @@ -89,7 +90,9 @@ async function main() { 'collateral-plugins/verify_aave_v3_usdc.ts', 'collateral-plugins/verify_wsteth.ts', 'collateral-plugins/verify_cbeth.ts', - 'assets/verify_stg.ts' + 'assets/verify_stg.ts', + 'collateral-plugins/verify_cbbtc.ts', + 'collateral-plugins/verify_eurc.ts' ) } else if (chainId == '42161' || chainId == '421614') { // Arbitrum One From 94a48ff4588963dfe13ffb7980567271b028d05f Mon Sep 17 00:00:00 2001 From: Taylor Brent Date: Thu, 17 Oct 2024 18:19:38 -0400 Subject: [PATCH 12/40] unit tests --- .../plugins/assets/DemurrageCollateral.sol | 28 +- test/scenario/DemurrageCollateral.test.ts | 377 ++++++++++++++++++ 2 files changed, 393 insertions(+), 12 deletions(-) create mode 100644 test/scenario/DemurrageCollateral.test.ts diff --git a/contracts/plugins/assets/DemurrageCollateral.sol b/contracts/plugins/assets/DemurrageCollateral.sol index c616d795e..15d0173c4 100644 --- a/contracts/plugins/assets/DemurrageCollateral.sol +++ b/contracts/plugins/assets/DemurrageCollateral.sol @@ -18,8 +18,17 @@ struct DemurrageConfig { /** * @title DemurrageCollateral * @notice Collateral plugin for a genneralized demurrage collateral (i.e /w management fee) + * Warning: Do NOT use the standard targetName() format of "USD" * - * if only 1 feed: + * DemurrageCollateral's targetName() must be contain 3 dimensions: + * 1. date + * 2. unit + * 3. annual rate + * For example: 20241017USD50% describes a USD peg of $1 on 2024-10-17 and $0.50 on 2025-10-17. + * An RToken looking to put this collateral into its basket on 2025-10-17 would use 2 units, + * if the intent were to achieve $1 in _today's_ dollars. + * + * under 1 feed: * - feed0/chainlinkFeed must be {UoA/tok} * - apply issuance premium IFF isFiat is true * 2 feeds: @@ -28,8 +37,8 @@ struct DemurrageConfig { * - apply issuance premium * * - tok = Tokenized X - * - ref = Virtual (inflationary) X - * - target = X + * - ref = Virtually inflationary X + * - target = YYYYMMDD-X-APR% * - UoA = USD */ contract DemurrageCollateral is FiatCollateral { @@ -138,17 +147,12 @@ contract DemurrageCollateral is FiatCollateral { // === Demurrage rates === - // invariant: targetPerRef() * refPerTok() ~= FIX_ONE - /// @return {ref/tok} Quantity of whole reference units per whole collateral tokens function refPerTok() public view override returns (uint192) { - // up-only - return FIX_ONE.div(targetPerRef(), FLOOR); - } + uint192 denominator = FIX_ONE.minus(fee).powu(uint48(block.timestamp - t0)); + if (denominator == 0) return FIX_MAX; - /// @return {target/ref} Quantity of whole target units per whole reference unit in the peg - function targetPerRef() public view override returns (uint192) { - // down-only - return FIX_ONE.minus(fee).powu(uint48(block.timestamp - t0)); + // up-only + return FIX_ONE.div(denominator, FLOOR); } } diff --git a/test/scenario/DemurrageCollateral.test.ts b/test/scenario/DemurrageCollateral.test.ts new file mode 100644 index 000000000..0c6e44256 --- /dev/null +++ b/test/scenario/DemurrageCollateral.test.ts @@ -0,0 +1,377 @@ +import { loadFixture, setStorageAt } from '@nomicfoundation/hardhat-network-helpers' +import { SignerWithAddress } from '@nomiclabs/hardhat-ethers/signers' +import { expect } from 'chai' +import { ContractFactory } from 'ethers' +import { ethers, upgrades } from 'hardhat' +import { IConfig } from '../../common/configuration' +import { bn, fp } from '../../common/numbers' +import { + BasketLibP1, + ERC20Mock, + IAssetRegistry, + MockV3Aggregator, + TestIBackingManager, + TestIBasketHandler, + TestIMain, + TestIRToken, + DemurrageCollateral, +} from '../../typechain' +import { advanceTime } from '../utils/time' +import { defaultFixtureNoBasket, IMPLEMENTATION, Implementation } from '../fixtures' +import { CollateralStatus, ZERO_ADDRESS } from '../../common/constants' + +const describeP1 = IMPLEMENTATION == Implementation.P1 ? describe : describe.skip + +const FIFTY_PERCENT_ANNUALLY = bn('21979552668') // 50% annually + +describeP1(`Demurrage Collateral - P${IMPLEMENTATION}`, () => { + const amt = fp('1') + + let owner: SignerWithAddress + let addr1: SignerWithAddress + + let tokens: ERC20Mock[] + let collateral: DemurrageCollateral[] + + let uoaPerTokFeed: MockV3Aggregator + let uoaPerTargetFeed: MockV3Aggregator + let targetPerTokFeed: MockV3Aggregator + + let config: IConfig + + let main: TestIMain + let backingManager: TestIBackingManager + let rToken: TestIRToken + let assetRegistry: IAssetRegistry + let bh: TestIBasketHandler + + // Perform tests for each of these decimal variations (> 18) + describe(`Demurrage Collateral`, () => { + before(async () => { + ;[owner, addr1] = await ethers.getSigners() + + // Setup Factories + const BasketLibFactory: ContractFactory = await ethers.getContractFactory('BasketLibP1') + const basketLib: BasketLibP1 = await BasketLibFactory.deploy() + const BasketHandlerFactory: ContractFactory = await ethers.getContractFactory( + 'BasketHandlerP1', + { libraries: { BasketLibP1: basketLib.address } } + ) + const DemurrageCollateralFactory: ContractFactory = await ethers.getContractFactory( + 'DemurrageCollateral' + ) + const ERC20Factory: ContractFactory = await ethers.getContractFactory('ERC20Mock') + const ChainlinkFactory: ContractFactory = await ethers.getContractFactory('MockV3Aggregator') + + // Deploy fixture + ;({ assetRegistry, backingManager, config, main, rToken } = await loadFixture( + defaultFixtureNoBasket + )) + + // Replace with reweightable basket handler + bh = await ethers.getContractAt( + 'TestIBasketHandler', + ( + await upgrades.deployProxy( + BasketHandlerFactory, + [main.address, config.warmupPeriod, true, true], + { + initializer: 'init', + kind: 'uups', + } + ) + ).address + ) + await setStorageAt(main.address, 204, bh.address) + await setStorageAt(rToken.address, 355, bh.address) + await setStorageAt(backingManager.address, 302, bh.address) + await setStorageAt(assetRegistry.address, 201, bh.address) + + /***** Replace the original 4 tokens with 4 demurrage collateral ***********/ + // The 4 versions of DemurrageCollateral: + // 1. isFiat = false: {UoA/tok} (no default detection) + // 2. isFiat = true: {UoA/tok} (/w default detection) + // 3. targetUnitFeed0 = false: {UoA/tok} and {UoA/target} (/w default detection) + // 4. targetUnitFeed0 = true: {target/tok} and {UoA/target} (/w default detection) + + tokens = ( + await Promise.all([ + ERC20Factory.deploy('NAME1', 'TKN1'), + ERC20Factory.deploy('NAME2', 'TKN2'), + ERC20Factory.deploy('NAME3', 'TKN3'), + ERC20Factory.deploy('NAME4', 'TKN4'), + ]) + ) + + uoaPerTokFeed = await ChainlinkFactory.deploy(8, bn('1e8')) + uoaPerTargetFeed = await ChainlinkFactory.deploy(8, bn('1e8')) + targetPerTokFeed = await ChainlinkFactory.deploy(8, bn('1e8')) + + collateral = await Promise.all([ + await DemurrageCollateralFactory.deploy( + { + erc20: tokens[0].address, + targetName: ethers.utils.formatBytes32String('20241017-USD-50%'), + priceTimeout: bn('604800'), + chainlinkFeed: uoaPerTokFeed.address, // {UoA/tok} + oracleError: fp('0.01').toString(), // 1% + oracleTimeout: bn('86400').toString(), // 24 hr + maxTradeVolume: fp('1e6').toString(), // $1m, + defaultThreshold: fp('0.01'), + delayUntilDefault: bn('86400'), + }, + { + isFiat: false, + targetUnitFeed0: false, + fee: FIFTY_PERCENT_ANNUALLY, + feed1: ZERO_ADDRESS, + timeout1: bn('0'), + error1: bn('0'), + } + ), + await DemurrageCollateralFactory.deploy( + { + erc20: tokens[1].address, + targetName: ethers.utils.formatBytes32String('20241017-EUR-50%'), + priceTimeout: bn('604800'), + chainlinkFeed: uoaPerTokFeed.address, // {UoA/tok} + oracleError: fp('0.01').toString(), // 1% + oracleTimeout: bn('86400').toString(), // 24 hr + maxTradeVolume: fp('1e6').toString(), // $1m, + defaultThreshold: fp('0.01'), + delayUntilDefault: bn('86400'), + }, + { + isFiat: true, + targetUnitFeed0: false, + fee: FIFTY_PERCENT_ANNUALLY, + feed1: ZERO_ADDRESS, + timeout1: bn('0'), + error1: bn('0'), + } + ), + await DemurrageCollateralFactory.deploy( + { + erc20: tokens[2].address, + targetName: ethers.utils.formatBytes32String('20241017-XAU-50%'), + priceTimeout: bn('604800'), + chainlinkFeed: uoaPerTokFeed.address, // {UoA/tok} + oracleError: fp('0.01').toString(), // 1% + oracleTimeout: bn('86400').toString(), // 24 hr + maxTradeVolume: fp('1e6').toString(), // $1m, + defaultThreshold: fp('0.01'), + delayUntilDefault: bn('86400'), + }, + { + isFiat: false, + targetUnitFeed0: false, + fee: FIFTY_PERCENT_ANNUALLY, + feed1: uoaPerTargetFeed.address, // {UoA/target} + timeout1: bn('86400').toString(), // 24 hr + error1: fp('0.01').toString(), // 1% + } + ), + await DemurrageCollateralFactory.deploy( + { + erc20: tokens[3].address, + targetName: ethers.utils.formatBytes32String('20241017-SPY-50%'), + priceTimeout: bn('604800'), + chainlinkFeed: targetPerTokFeed.address, // {target/tok} + oracleError: fp('0.01').toString(), // 1% + oracleTimeout: bn('86400').toString(), // 24 hr + maxTradeVolume: fp('1e6').toString(), // $1m, + defaultThreshold: fp('0.01'), + delayUntilDefault: bn('86400'), + }, + { + isFiat: false, + targetUnitFeed0: true, + fee: FIFTY_PERCENT_ANNUALLY, + feed1: uoaPerTargetFeed.address, // {UoA/target} + timeout1: bn('86400').toString(), // 24 hr + error1: fp('0.01').toString(), // 1% + } + ), + ]) + + for (let i = 0; i < collateral.length; i++) { + await assetRegistry.connect(owner).register(collateral[i].address) + await tokens[i].mint(addr1.address, amt) + await tokens[i].connect(addr1).approve(rToken.address, amt) + } + + await bh.connect(owner).setPrimeBasket( + tokens.map((t) => t.address), + [fp('1'), fp('1'), fp('1'), fp('1')] + ) + await bh.connect(owner).refreshBasket() + await advanceTime(Number(config.warmupPeriod) + 1) + await rToken.connect(addr1).issue(amt) + expect(await rToken.totalSupply()).to.equal(amt) + expect(await bh.status()).to.equal(CollateralStatus.SOUND) + expect(await bh.fullyCollateralized()).to.equal(true) + }) + + it('prices/pegPrices should be correct', async () => { + for (let i = 0; i < 3; i++) { + const [low, high, pegPrice] = await collateral[i].tryPrice() + expect(low.add(high).div(2)).to.equal(fp('1')) + expect(pegPrice).to.equal(fp('1')) + } + const [low, high, pegPrice] = await collateral[3].tryPrice() + expect(low.add(high).div(2)).to.equal(fp('1.0001')) // asymmetry from multiplying oracles together + expect(pegPrice).to.equal(fp('1')) + }) + + it('quantities should be correct', async () => { + const [erc20s, quantities] = await bh.quote(fp('1'), false, 2) + for (let i = 0; i < collateral.length; i++) { + expect(erc20s[i]).to.equal(tokens[i].address) + expect(quantities[i]).to.be.closeTo(fp('1'), fp('1').div(bn('1e5'))) + } + }) + + context('after 1 year', () => { + before(async () => { + await advanceTime(Number(bn('31535955'))) // 1 year - 45s + await uoaPerTokFeed.updateAnswer(bn('1e8')) + await uoaPerTargetFeed.updateAnswer(bn('1e8')) + await targetPerTokFeed.updateAnswer(bn('1e8')) + + await assetRegistry.refresh() + expect(await bh.status()).to.equal(CollateralStatus.SOUND) + expect(await bh.fullyCollateralized()).to.equal(true) + }) + + it('oracle prices shouldnt change', async () => { + for (let i = 0; i < 3; i++) { + const [low, high, pegPrice] = await collateral[i].tryPrice() + expect(low.add(high).div(2)).to.equal(fp('1')) + expect(pegPrice).to.equal(fp('1')) + } + const [low, high, pegPrice] = await collateral[3].tryPrice() + expect(low.add(high).div(2)).to.equal(fp('1.0001')) // asymmetry from multiplying oracles together + expect(pegPrice).to.equal(fp('1')) + }) + + it('RToken quantities should have decreased ~50%', async () => { + const [erc20s, quantities] = await bh.quote(fp('1'), false, 2) + for (let i = 0; i < collateral.length; i++) { + expect(erc20s[i]).to.equal(tokens[i].address) + const expected = fp('1').div(2) + expect(quantities[i]).to.be.closeTo(expected, expected.div(bn('1e6'))) + } + }) + + it('Excess should accrue as revenue', async () => { + const [bottom] = await bh.basketsHeldBy(backingManager.address) + expect(bottom).to.be.closeTo(amt.mul(2), amt.div(bn('1e3'))) + }) + + it('refreshBasket() should not restore the RToken back to $1', async () => { + const [erc20s, quantities] = await bh.quote(fp('1'), false, 2) + await expect(bh.connect(owner).refreshBasket()).to.emit(bh, 'BasketSet') + const [newERC20s, newQuantities] = await bh.quote(fp('1'), false, 2) + + expect(await bh.status()).to.equal(CollateralStatus.SOUND) + expect(await bh.fullyCollateralized()).to.equal(true) + for (let i = 0; i < collateral.length; i++) { + expect(erc20s[i]).to.equal(newERC20s[i]) + expect(quantities[i]).to.be.gt(newQuantities[i]) + expect(quantities[i]).to.be.lt(newQuantities[i].add(fp('1e-6'))) + } + }) + + it('setPrimeBasket() should not restore the RToken to genesis peg', async () => { + const [erc20s, quantities] = await bh.quote(fp('1'), false, 2) + await bh.connect(owner).setPrimeBasket( + tokens.map((t) => t.address), + [fp('1'), fp('1'), fp('1'), fp('1')] + ) + await bh.connect(owner).refreshBasket() + const [newERC20s, newQuantities] = await bh.quote(fp('1'), false, 2) + + expect(await bh.status()).to.equal(CollateralStatus.SOUND) + expect(await bh.fullyCollateralized()).to.equal(true) + for (let i = 0; i < collateral.length; i++) { + expect(erc20s[i]).to.equal(newERC20s[i]) + expect(quantities[i]).to.be.gt(newQuantities[i]) + expect(quantities[i]).to.be.lt(newQuantities[i].add(fp('1e-6'))) + } + }) + + it('prices/pegPrices should respond correctly to devaluation', async () => { + // 1. break uoaPerTokFeed + await uoaPerTokFeed.updateAnswer(bn('1e8').div(2)) + + // token1 + let [low, high, pegPrice] = await collateral[0].tryPrice() + expect(low.add(high).div(2)).to.equal(fp('0.5')) + expect(pegPrice).to.equal(fp('1')) + + // token2 + ;[low, high, pegPrice] = await collateral[1].tryPrice() + expect(low.add(high).div(2)).to.equal(fp('0.5')) + expect(pegPrice).to.equal(fp('0.5')) + + // token3 + ;[low, high, pegPrice] = await collateral[2].tryPrice() + expect(low.add(high).div(2)).to.equal(fp('0.5')) + expect(pegPrice).to.equal(fp('0.5')) + + // token4 + ;[low, high, pegPrice] = await collateral[3].tryPrice() + expect(low.add(high).div(2)).to.equal(fp('1.0001')) + expect(pegPrice).to.equal(fp('1')) + + // 2. break uoaPerTargetFeed + await uoaPerTokFeed.updateAnswer(bn('1e8')) + await uoaPerTargetFeed.updateAnswer(bn('1e8').div(2)) + + // token1 + ;[low, high, pegPrice] = await collateral[0].tryPrice() + expect(low.add(high).div(2)).to.equal(fp('1')) + expect(pegPrice).to.equal(fp('1')) + + // token2 + ;[low, high, pegPrice] = await collateral[1].tryPrice() + expect(low.add(high).div(2)).to.equal(fp('1')) + expect(pegPrice).to.equal(fp('1')) + + // token3 + ;[low, high, pegPrice] = await collateral[2].tryPrice() + expect(low.add(high).div(2)).to.equal(fp('1')) + expect(pegPrice).to.equal(fp('2')) + + // token4 + ;[low, high, pegPrice] = await collateral[3].tryPrice() + expect(low.add(high).div(2)).to.equal(fp('0.50005')) + expect(pegPrice).to.equal(fp('1')) + + // 3. break targetPerTokFeed + await uoaPerTargetFeed.updateAnswer(bn('1e8')) + await targetPerTokFeed.updateAnswer(bn('1e8').div(2)) + + // token1 + ;[low, high, pegPrice] = await collateral[0].tryPrice() + expect(low.add(high).div(2)).to.equal(fp('1')) + expect(pegPrice).to.equal(fp('1')) + + // token2 + ;[low, high, pegPrice] = await collateral[1].tryPrice() + expect(low.add(high).div(2)).to.equal(fp('1')) + expect(pegPrice).to.equal(fp('1')) + + // token3 + ;[low, high, pegPrice] = await collateral[2].tryPrice() + expect(low.add(high).div(2)).to.equal(fp('1')) + expect(pegPrice).to.equal(fp('1')) + + // token4 + ;[low, high, pegPrice] = await collateral[3].tryPrice() + expect(low.add(high).div(2)).to.equal(fp('0.50005')) + expect(pegPrice).to.equal(fp('0.5')) + }) + }) + }) +}) From 1de4dce079c30b1faa08a16b872783a4c8b2859a Mon Sep 17 00:00:00 2001 From: Taylor Brent Date: Thu, 17 Oct 2024 18:19:56 -0400 Subject: [PATCH 13/40] fix decay rate constants --- test/plugins/individual-collateral/dtf/constants.ts | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/test/plugins/individual-collateral/dtf/constants.ts b/test/plugins/individual-collateral/dtf/constants.ts index 853e3c103..047f1adde 100644 --- a/test/plugins/individual-collateral/dtf/constants.ts +++ b/test/plugins/individual-collateral/dtf/constants.ts @@ -11,10 +11,11 @@ export const ORACLE_ERROR = fp('0.003') // 0.3% export const DELAY_UNTIL_DEFAULT = bn('86400') // 24h export const MAX_TRADE_VOL = fp('1e6') -export const TWO_PERCENT_FEE = bn('627937192') // 2% annually -export const ONE_PERCENT_FEE = bn('315522921') // 1% annually -export const FIFTY_BPS_FEE = bn('158153903') // 0.5% annually -export const TWENTY_FIVE_BPS_FEE = bn('079175551') // 0.25% annually -export const TEN_BPS_FEE = bn('31693947') // 0.1% annually +// to compute: 1 - (1 - annual_fee) ^ (1/31536000) +export const TWO_PERCENT_FEE = bn('640623646') // 2% annually +export const ONE_PERCENT_FEE = bn('318694059') // 1% annually +export const FIFTY_BPS_FEE = bn('158946658') // 0.5% annually +export const TWENTY_FIVE_BPS_FEE = bn('79373738') // 0.25% annually +export const TEN_BPS_FEE = bn('31725657') // 0.1% annually export const FORK_BLOCK = 20963623 From 43daecb3e1108fe501a432bfe2a142ccfecf6cdf Mon Sep 17 00:00:00 2001 From: Taylor Brent Date: Thu, 17 Oct 2024 18:20:20 -0400 Subject: [PATCH 14/40] update scripts for full targetName approach --- scripts/deployment/common.ts | 14 ++++++++++++++ .../phase2-assets/collaterals/deploy_cbbtc.ts | 3 ++- .../phase2-assets/collaterals/deploy_eurc.ts | 3 ++- .../phase2-assets/collaterals/deploy_paxg.ts | 3 ++- .../collateral-plugins/verify_cbbtc.ts | 7 ++++++- .../verification/collateral-plugins/verify_eurc.ts | 7 ++++++- .../verification/collateral-plugins/verify_paxg.ts | 7 ++++++- 7 files changed, 38 insertions(+), 6 deletions(-) diff --git a/scripts/deployment/common.ts b/scripts/deployment/common.ts index a1f3393fd..96e65b6b7 100644 --- a/scripts/deployment/common.ts +++ b/scripts/deployment/common.ts @@ -99,3 +99,17 @@ export const writeComponentDeployment = ( console.log(` ${logDesc} Implementation: ${implAddr} ${prevAddr == implAddr ? '- SKIPPED' : ''}`) } + +export const getYYYYMMDDHHmmss = (): string => { + const now = new Date() + + const year = now.getFullYear() + const month = String(now.getMonth() + 1).padStart(2, '0') + const day = String(now.getDate()).padStart(2, '0') + + const hours = String(now.getHours()).padStart(2, '0') + const minutes = String(now.getMinutes()).padStart(2, '0') + const seconds = String(now.getSeconds()).padStart(2, '0') + + return `${year}-${month}-${day} ${hours}:${minutes}:${seconds}` +} diff --git a/scripts/deployment/phase2-assets/collaterals/deploy_cbbtc.ts b/scripts/deployment/phase2-assets/collaterals/deploy_cbbtc.ts index 527b7746b..ab7728206 100644 --- a/scripts/deployment/phase2-assets/collaterals/deploy_cbbtc.ts +++ b/scripts/deployment/phase2-assets/collaterals/deploy_cbbtc.ts @@ -6,6 +6,7 @@ import { bn, fp } from '../../../../common/numbers' import { expect } from 'chai' import { CollateralStatus } from '../../../../common/constants' import { + getYYYYMMDDHHmmss, getDeploymentFile, getAssetCollDeploymentFilename, IAssetCollDeployments, @@ -57,7 +58,7 @@ async function main() { const collateral = await DemurrageCollateralFactory.connect(deployer).deploy( { erc20: networkConfig[chainId].tokens.cbBTC, - targetName: hre.ethers.utils.formatBytes32String('BTC'), + targetName: hre.ethers.utils.formatBytes32String(`${getYYYYMMDDHHmmss()}-BTC-1%`), priceTimeout: priceTimeout.toString(), chainlinkFeed: networkConfig[chainId].chainlinkFeeds.cbBTC, // {UoA/tok} oracleError: fp('0.005').toString(), // 0.5% diff --git a/scripts/deployment/phase2-assets/collaterals/deploy_eurc.ts b/scripts/deployment/phase2-assets/collaterals/deploy_eurc.ts index 321f5ce41..b1479cb0f 100644 --- a/scripts/deployment/phase2-assets/collaterals/deploy_eurc.ts +++ b/scripts/deployment/phase2-assets/collaterals/deploy_eurc.ts @@ -6,6 +6,7 @@ import { bn, fp } from '../../../../common/numbers' import { expect } from 'chai' import { CollateralStatus } from '../../../../common/constants' import { + getYYYYMMDDHHmmss, getDeploymentFile, getAssetCollDeploymentFilename, IAssetCollDeployments, @@ -57,7 +58,7 @@ async function main() { const collateral = await DemurrageCollateralFactory.connect(deployer).deploy( { erc20: networkConfig[chainId].tokens.EURC, - targetName: hre.ethers.utils.formatBytes32String('EUR'), + targetName: hre.ethers.utils.formatBytes32String(`${getYYYYMMDDHHmmss()}-EUR-1%`), priceTimeout: priceTimeout.toString(), chainlinkFeed: networkConfig[chainId].chainlinkFeeds.EURC, oracleError: fp('0.003').toString(), // 0.3% diff --git a/scripts/deployment/phase2-assets/collaterals/deploy_paxg.ts b/scripts/deployment/phase2-assets/collaterals/deploy_paxg.ts index 6279892ad..54e0f20e3 100644 --- a/scripts/deployment/phase2-assets/collaterals/deploy_paxg.ts +++ b/scripts/deployment/phase2-assets/collaterals/deploy_paxg.ts @@ -11,6 +11,7 @@ import { IAssetCollDeployments, getDeploymentFilename, fileExists, + getYYYYMMDDHHmmss, } from '../../common' import { DELAY_UNTIL_DEFAULT, @@ -57,7 +58,7 @@ async function main() { const collateral = await DemurrageCollateralFactory.connect(deployer).deploy( { erc20: networkConfig[chainId].tokens.PAXG, - targetName: hre.ethers.utils.formatBytes32String('XAU'), + targetName: hre.ethers.utils.formatBytes32String(`${getYYYYMMDDHHmmss()}-XAU-1%`), priceTimeout: priceTimeout.toString(), chainlinkFeed: networkConfig[chainId].chainlinkFeeds.XAU, // {UoA/tok} oracleError: fp('0.003').toString(), // 0.3% diff --git a/scripts/verification/collateral-plugins/verify_cbbtc.ts b/scripts/verification/collateral-plugins/verify_cbbtc.ts index 9b5d35ba6..112a85a28 100644 --- a/scripts/verification/collateral-plugins/verify_cbbtc.ts +++ b/scripts/verification/collateral-plugins/verify_cbbtc.ts @@ -44,13 +44,18 @@ async function main() { throw new Error(`Unsupported chainId: ${chainId}`) } + const collateral = await hre.ethers.getContractAt( + 'ICollateral', + assetCollDeployments.collateral.cbBTC! + ) + await verifyContract( chainId, assetCollDeployments.collateral.cbBTC, [ { erc20: networkConfig[chainId].tokens.cbBTC, - targetName: hre.ethers.utils.formatBytes32String('BTC'), + targetName: await collateral.targetName(), priceTimeout: priceTimeout.toString(), chainlinkFeed: networkConfig[chainId].chainlinkFeeds.cbBTC, // {UoA/tok} oracleError: fp('0.005').toString(), // 0.5% diff --git a/scripts/verification/collateral-plugins/verify_eurc.ts b/scripts/verification/collateral-plugins/verify_eurc.ts index a2ea29748..759cce433 100644 --- a/scripts/verification/collateral-plugins/verify_eurc.ts +++ b/scripts/verification/collateral-plugins/verify_eurc.ts @@ -44,13 +44,18 @@ async function main() { throw new Error(`Unsupported chainId: ${chainId}`) } + const collateral = await hre.ethers.getContractAt( + 'ICollateral', + assetCollDeployments.collateral.EURC! + ) + await verifyContract( chainId, assetCollDeployments.collateral.EURC, [ { erc20: networkConfig[chainId].tokens.EURC, - targetName: hre.ethers.utils.formatBytes32String('EUR'), + targetName: await collateral.targetName(), priceTimeout: priceTimeout.toString(), chainlinkFeed: networkConfig[chainId].chainlinkFeeds.EURC, // {UoA/tok} oracleError: fp('0.003').toString(), // 0.3% diff --git a/scripts/verification/collateral-plugins/verify_paxg.ts b/scripts/verification/collateral-plugins/verify_paxg.ts index ab17035ae..f99deaba2 100644 --- a/scripts/verification/collateral-plugins/verify_paxg.ts +++ b/scripts/verification/collateral-plugins/verify_paxg.ts @@ -45,13 +45,18 @@ async function main() { throw new Error(`Unsupported chainId: ${chainId}`) } + const collateral = await hre.ethers.getContractAt( + 'ICollateral', + assetCollDeployments.collateral.PAXG! + ) + await verifyContract( chainId, assetCollDeployments.collateral.PAXG, [ { erc20: networkConfig[chainId].tokens.PAXG, - targetName: hre.ethers.utils.formatBytes32String('XAU'), + targetName: await collateral.targetName(), priceTimeout: priceTimeout.toString(), chainlinkFeed: networkConfig[chainId].chainlinkFeeds.XAU, // {UoA/tok} oracleError: fp('0.003').toString(), // 0.3% From 7dd6317af500912cb6281fcce7ec860b1dfb2fb1 Mon Sep 17 00:00:00 2001 From: Taylor Brent Date: Thu, 17 Oct 2024 19:34:27 -0400 Subject: [PATCH 15/40] init demurrage docs --- docs/demurrage-collateral.md | 53 ++++++++++++++++++++++++++++++++++++ 1 file changed, 53 insertions(+) create mode 100644 docs/demurrage-collateral.md diff --git a/docs/demurrage-collateral.md b/docs/demurrage-collateral.md new file mode 100644 index 000000000..ee5ede053 --- /dev/null +++ b/docs/demurrage-collateral.md @@ -0,0 +1,53 @@ +# Demurrage Collateral Plugins + +**Demurrage** is a general term for a per-unit-time fee on assets-under-management (aka management fees) + +## Background + +Many assets on-chain do not have yield. While the Reserve Protocol is compatible with non-yielding assets, this introduces downsides: an RToken naively composed entirely of non-yielding collateral assets lacks RSR overcollateralization and governance. + +In this case a revenue stream can be created by composing a synthetic reference unit that refers to a falling quantity of the collateral token. This causes the reference unit to become inflationary with respect to the collateral unit, resulting in a monotonically increasing `refPerTok()` by definition. + +There are side-effects to the `targetName`, however the rest of the collateral plugin remains the same. + +### Reference Unit (inflationary) + +The reference unit becomes naturally inflationary, resulting in a `refPerTok` of: + +``` +refPerTok(): 1 / (1 - demurrage_rate_per_second) ^ t + where t is seconds since 01/01/2020 00:00:00 UTC +``` + +The timestamp of 01/01/2020 00:00:00 GMT+0000 is chosen arbitrarily. It's not important what this value is, but there are benefits to using a common anchor (and 1970 is too far). + +In unix time this is `1640995200` + +### Target Unit + +``` +targetPerRef(): 1 +``` + +The target unit must be named in a way that distinguishes it from the non-demurrage version of itself. We suggest the following naming scheme: + +`DMR{annual_demurrage_in_basis_points}{token_symbol}` or `DMR100USD`, for example + +The `DMR` prefix is short for demurrage; the `annual_demurrage_in_basis_points` is a number such as 100 for 1% annually; the `token_symbol` is the symbol of what would have otherwise been the target unit had the collateral been purely SelfReferential. + +Collateral can only be automatically substituted in the basket with collateral that share the same target unit. This unfortuna +tely means that a standard WETH collateral would not be in the same class as our demurrage ETH collateral, unless the WETH collateral were also demurrage-based, and at the same rate. + +### Setting the basket weights + +For demurrage collateral, the prime basket weights are in units of January 1st 2020 collateral, not today's collateral. It doesn't matter if the collateral wasn't around in 2020 -- when setting the basket weights the setter must take into account how much demurrage has occurred since January 1st 2020. + +For example, say an asset has had 5% total demurrage since January 1st 2020 and you want to (on today's date) create a basket of that is worth $1: the correct basket weight would be `1 / 0.95 = ~1.0526`. + +To calculate total demurrage since 2020-01-01 00:00:00 UTC, use: + +``` +fee() ^ (seconds_since_2020_01_01) +``` + +(where `fee()` is the per-second demurrage rate, usually found on the `DemurrageCollateral` contract) From 4268cb13d10ba8b4e97520f441143370631a1d9b Mon Sep 17 00:00:00 2001 From: Taylor Brent Date: Thu, 17 Oct 2024 19:38:44 -0400 Subject: [PATCH 16/40] fix target unit naming --- .../plugins/assets/DemurrageCollateral.sol | 35 ++++++++----------- docs/demurrage-collateral.md | 17 +++++---- scripts/deployment/common.ts | 14 -------- .../phase2-assets/collaterals/deploy_cbbtc.ts | 3 +- .../phase2-assets/collaterals/deploy_eurc.ts | 3 +- .../phase2-assets/collaterals/deploy_paxg.ts | 3 +- .../dtf/PAXGCollateralTestSuite.test.ts | 2 +- test/scenario/DemurrageCollateral.test.ts | 8 ++--- 8 files changed, 32 insertions(+), 53 deletions(-) diff --git a/contracts/plugins/assets/DemurrageCollateral.sol b/contracts/plugins/assets/DemurrageCollateral.sol index 15d0173c4..71dd7538b 100644 --- a/contracts/plugins/assets/DemurrageCollateral.sol +++ b/contracts/plugins/assets/DemurrageCollateral.sol @@ -4,7 +4,7 @@ pragma solidity 0.8.19; import "./FiatCollateral.sol"; struct DemurrageConfig { - uint192 fee; // {1/s} per-second inflation/deflation of refPerTok/targetPerRef + uint192 fee; // {1/s} per-second deflation of the target unit // bool isFiat; // if true: {target} == {UoA} bool targetUnitFeed0; // if true: feed0 is {target/tok} @@ -18,15 +18,8 @@ struct DemurrageConfig { /** * @title DemurrageCollateral * @notice Collateral plugin for a genneralized demurrage collateral (i.e /w management fee) - * Warning: Do NOT use the standard targetName() format of "USD" - * - * DemurrageCollateral's targetName() must be contain 3 dimensions: - * 1. date - * 2. unit - * 3. annual rate - * For example: 20241017USD50% describes a USD peg of $1 on 2024-10-17 and $0.50 on 2025-10-17. - * An RToken looking to put this collateral into its basket on 2025-10-17 would use 2 units, - * if the intent were to achieve $1 in _today's_ dollars. + * Warning: Do NOT use the standard targetName() format + * - Use: DMR{annual_demurrage_in_basis_points}{token_symbol} * * under 1 feed: * - feed0/chainlinkFeed must be {UoA/tok} @@ -37,8 +30,8 @@ struct DemurrageConfig { * - apply issuance premium * * - tok = Tokenized X - * - ref = Virtually inflationary X - * - target = YYYYMMDD-X-APR% + * - ref = Decayed X (since 2020-01-01 00:00:00 UTC) + * - target = Decayed X (since 2020-01-01 00:00:00 UTC) * - UoA = USD */ contract DemurrageCollateral is FiatCollateral { @@ -59,7 +52,7 @@ contract DemurrageCollateral is FiatCollateral { // immutable in spirit -- cannot be because of FiatCollateral's targetPerRef() call // TODO would love to find a way to make these immutable for gas reasons uint48 public t0; // {s} deployment timestamp - uint192 public fee; // {1/s} demurrage fee; manifests as reference unit inflation + uint192 public fee; // {1/s} demurrage fee; target unit deflation /// @param config.chainlinkFeed => feed0: {UoA/tok} or {target/tok} /// @param config.oracleTimeout => timeout0 @@ -67,7 +60,7 @@ contract DemurrageCollateral is FiatCollateral { /// @param demurrageConfig.feed1 empty or {UoA/target} /// @param demurrageConfig.isFiat true iff {target} == {UoA} /// @param demurrageConfig.targetUnitfeed0 true iff feed0 is {target/tok} units - /// @param demurrageConfig.fee {1/s} fraction of the reference unit to inflate each second + /// @param demurrageConfig.fee {1/s} fraction of the target unit to deflate each second constructor(CollateralConfig memory config, DemurrageConfig memory demurrageConfig) FiatCollateral(config) { @@ -96,8 +89,7 @@ contract DemurrageCollateral is FiatCollateral { /// Should NOT be manipulable by MEV /// @return low {UoA/tok} The low price estimate /// @return high {UoA/tok} The high price estimate - /// @return pegPrice {target/tok} The undecayed price observed in the peg - /// can be 0 if only 1 feed AND not fiat + /// @return pegPrice {target/tok} The un-decayed pegPrice function tryPrice() external view @@ -110,12 +102,11 @@ contract DemurrageCollateral is FiatCollateral { { // This plugin handles pegPrice differently than most -- since FiatCollateral saves // valid peg ranges at deployment time, they do not account for the decay due to the - // demurrage fee + // demurrage fee. // - // To account for this, the pegPrice is returned in units of {target/tok} - // aka {target/ref} without the reference unit inflation + // The pegPrice should not account for demurrage - pegPrice = FIX_ONE; // uninflated rate that won't trigger default or issuance premium + pegPrice = FIX_ONE; // undecayed rate that won't trigger default or issuance premium uint192 x = feed0.price(timeout0); // {UoA/tok} uint192 xErr = error0; @@ -149,8 +140,10 @@ contract DemurrageCollateral is FiatCollateral { /// @return {ref/tok} Quantity of whole reference units per whole collateral tokens function refPerTok() public view override returns (uint192) { + // Monotonically increasing due to target unit (and reference unit) deflation + uint192 denominator = FIX_ONE.minus(fee).powu(uint48(block.timestamp - t0)); - if (denominator == 0) return FIX_MAX; + if (denominator == 0) return FIX_MAX; // TODO // up-only return FIX_ONE.div(denominator, FLOOR); diff --git a/docs/demurrage-collateral.md b/docs/demurrage-collateral.md index ee5ede053..279fecf3c 100644 --- a/docs/demurrage-collateral.md +++ b/docs/demurrage-collateral.md @@ -29,20 +29,19 @@ In unix time this is `1640995200` targetPerRef(): 1 ``` -The target unit must be named in a way that distinguishes it from the non-demurrage version of itself. We suggest the following naming scheme: - `DMR{annual_demurrage_in_basis_points}{token_symbol}` or `DMR100USD`, for example -The `DMR` prefix is short for demurrage; the `annual_demurrage_in_basis_points` is a number such as 100 for 1% annually; the `token_symbol` is the symbol of what would have otherwise been the target unit had the collateral been purely SelfReferential. +1. The `DMR` prefix is short for demurrage +2. The `annual_demurrage_in_basis_points` is a number such as 100 for 1% annually +3. The `token_symbol` is the symbol of what would have otherwise been the target unit had the collateral been purely SelfReferential -Collateral can only be automatically substituted in the basket with collateral that share the same target unit. This unfortuna -tely means that a standard WETH collateral would not be in the same class as our demurrage ETH collateral, unless the WETH collateral were also demurrage-based, and at the same rate. +Collateral can only be automatically substituted in the basket with collateral that share the _exact_ same target unit. This unfortunately means a standard WETH collateral cannot be backup for a demurrage ETH collateral. Both the unit type and rate must be identical in order for two collateral to be in the same target unit class. ### Setting the basket weights -For demurrage collateral, the prime basket weights are in units of January 1st 2020 collateral, not today's collateral. It doesn't matter if the collateral wasn't around in 2020 -- when setting the basket weights the setter must take into account how much demurrage has occurred since January 1st 2020. +Prime basket weights are in units of January 1st 2020 collateral, not today's collateral. It doesn't matter if the collateral wasn't around in 2020 -- when setting the basket weights the setter must take into account how much demurrage has occurred since January 1st 2020. -For example, say an asset has had 5% total demurrage since January 1st 2020 and you want to (on today's date) create a basket of that is worth $1: the correct basket weight would be `1 / 0.95 = ~1.0526`. +For example, say an asset has had 5% total demurrage since January 1st 2020 and you want to (on today's date) create a basket of that is worth $1: the correct basket weight to provide to `setPrimeBasket()` would be `1 / 0.95 = ~1.0526`. To calculate total demurrage since 2020-01-01 00:00:00 UTC, use: @@ -51,3 +50,7 @@ fee() ^ (seconds_since_2020_01_01) ``` (where `fee()` is the per-second demurrage rate, usually found on the `DemurrageCollateral` contract) + +### Implementation + +[DemurrageCollateral.sol](../contracts/plugins/assets/DemurrageCollateral.sol) implements a generalized demurrage collateral plugin that should support almost all use-cases diff --git a/scripts/deployment/common.ts b/scripts/deployment/common.ts index 96e65b6b7..a1f3393fd 100644 --- a/scripts/deployment/common.ts +++ b/scripts/deployment/common.ts @@ -99,17 +99,3 @@ export const writeComponentDeployment = ( console.log(` ${logDesc} Implementation: ${implAddr} ${prevAddr == implAddr ? '- SKIPPED' : ''}`) } - -export const getYYYYMMDDHHmmss = (): string => { - const now = new Date() - - const year = now.getFullYear() - const month = String(now.getMonth() + 1).padStart(2, '0') - const day = String(now.getDate()).padStart(2, '0') - - const hours = String(now.getHours()).padStart(2, '0') - const minutes = String(now.getMinutes()).padStart(2, '0') - const seconds = String(now.getSeconds()).padStart(2, '0') - - return `${year}-${month}-${day} ${hours}:${minutes}:${seconds}` -} diff --git a/scripts/deployment/phase2-assets/collaterals/deploy_cbbtc.ts b/scripts/deployment/phase2-assets/collaterals/deploy_cbbtc.ts index ab7728206..63ef1b77d 100644 --- a/scripts/deployment/phase2-assets/collaterals/deploy_cbbtc.ts +++ b/scripts/deployment/phase2-assets/collaterals/deploy_cbbtc.ts @@ -6,7 +6,6 @@ import { bn, fp } from '../../../../common/numbers' import { expect } from 'chai' import { CollateralStatus } from '../../../../common/constants' import { - getYYYYMMDDHHmmss, getDeploymentFile, getAssetCollDeploymentFilename, IAssetCollDeployments, @@ -58,7 +57,7 @@ async function main() { const collateral = await DemurrageCollateralFactory.connect(deployer).deploy( { erc20: networkConfig[chainId].tokens.cbBTC, - targetName: hre.ethers.utils.formatBytes32String(`${getYYYYMMDDHHmmss()}-BTC-1%`), + targetName: hre.ethers.utils.formatBytes32String('DMR100BTC'), priceTimeout: priceTimeout.toString(), chainlinkFeed: networkConfig[chainId].chainlinkFeeds.cbBTC, // {UoA/tok} oracleError: fp('0.005').toString(), // 0.5% diff --git a/scripts/deployment/phase2-assets/collaterals/deploy_eurc.ts b/scripts/deployment/phase2-assets/collaterals/deploy_eurc.ts index b1479cb0f..b3559219b 100644 --- a/scripts/deployment/phase2-assets/collaterals/deploy_eurc.ts +++ b/scripts/deployment/phase2-assets/collaterals/deploy_eurc.ts @@ -6,7 +6,6 @@ import { bn, fp } from '../../../../common/numbers' import { expect } from 'chai' import { CollateralStatus } from '../../../../common/constants' import { - getYYYYMMDDHHmmss, getDeploymentFile, getAssetCollDeploymentFilename, IAssetCollDeployments, @@ -58,7 +57,7 @@ async function main() { const collateral = await DemurrageCollateralFactory.connect(deployer).deploy( { erc20: networkConfig[chainId].tokens.EURC, - targetName: hre.ethers.utils.formatBytes32String(`${getYYYYMMDDHHmmss()}-EUR-1%`), + targetName: hre.ethers.utils.formatBytes32String('DMR100EUR'), priceTimeout: priceTimeout.toString(), chainlinkFeed: networkConfig[chainId].chainlinkFeeds.EURC, oracleError: fp('0.003').toString(), // 0.3% diff --git a/scripts/deployment/phase2-assets/collaterals/deploy_paxg.ts b/scripts/deployment/phase2-assets/collaterals/deploy_paxg.ts index 54e0f20e3..95333b35e 100644 --- a/scripts/deployment/phase2-assets/collaterals/deploy_paxg.ts +++ b/scripts/deployment/phase2-assets/collaterals/deploy_paxg.ts @@ -11,7 +11,6 @@ import { IAssetCollDeployments, getDeploymentFilename, fileExists, - getYYYYMMDDHHmmss, } from '../../common' import { DELAY_UNTIL_DEFAULT, @@ -58,7 +57,7 @@ async function main() { const collateral = await DemurrageCollateralFactory.connect(deployer).deploy( { erc20: networkConfig[chainId].tokens.PAXG, - targetName: hre.ethers.utils.formatBytes32String(`${getYYYYMMDDHHmmss()}-XAU-1%`), + targetName: hre.ethers.utils.formatBytes32String('DMR100XAU'), priceTimeout: priceTimeout.toString(), chainlinkFeed: networkConfig[chainId].chainlinkFeeds.XAU, // {UoA/tok} oracleError: fp('0.003').toString(), // 0.3% diff --git a/test/plugins/individual-collateral/dtf/PAXGCollateralTestSuite.test.ts b/test/plugins/individual-collateral/dtf/PAXGCollateralTestSuite.test.ts index 13b9404a2..d7dfd9d67 100644 --- a/test/plugins/individual-collateral/dtf/PAXGCollateralTestSuite.test.ts +++ b/test/plugins/individual-collateral/dtf/PAXGCollateralTestSuite.test.ts @@ -29,7 +29,7 @@ interface PAXGCollateralOpts extends CollateralOpts { export const defaultPAXGCollateralOpts: PAXGCollateralOpts = { erc20: PAXG, - targetName: ethers.utils.formatBytes32String('XAU'), + targetName: `DMR100XAU`, rewardERC20: ZERO_ADDRESS, priceTimeout: PRICE_TIMEOUT, chainlinkFeed: XAU_USD_PRICE_FEED, diff --git a/test/scenario/DemurrageCollateral.test.ts b/test/scenario/DemurrageCollateral.test.ts index 0c6e44256..836ed43d3 100644 --- a/test/scenario/DemurrageCollateral.test.ts +++ b/test/scenario/DemurrageCollateral.test.ts @@ -111,7 +111,7 @@ describeP1(`Demurrage Collateral - P${IMPLEMENTATION}`, () => { await DemurrageCollateralFactory.deploy( { erc20: tokens[0].address, - targetName: ethers.utils.formatBytes32String('20241017-USD-50%'), + targetName: ethers.utils.formatBytes32String('DMR5000USD'), priceTimeout: bn('604800'), chainlinkFeed: uoaPerTokFeed.address, // {UoA/tok} oracleError: fp('0.01').toString(), // 1% @@ -132,7 +132,7 @@ describeP1(`Demurrage Collateral - P${IMPLEMENTATION}`, () => { await DemurrageCollateralFactory.deploy( { erc20: tokens[1].address, - targetName: ethers.utils.formatBytes32String('20241017-EUR-50%'), + targetName: ethers.utils.formatBytes32String('DMR5000EUR'), priceTimeout: bn('604800'), chainlinkFeed: uoaPerTokFeed.address, // {UoA/tok} oracleError: fp('0.01').toString(), // 1% @@ -153,7 +153,7 @@ describeP1(`Demurrage Collateral - P${IMPLEMENTATION}`, () => { await DemurrageCollateralFactory.deploy( { erc20: tokens[2].address, - targetName: ethers.utils.formatBytes32String('20241017-XAU-50%'), + targetName: ethers.utils.formatBytes32String('DMR5000XAU'), priceTimeout: bn('604800'), chainlinkFeed: uoaPerTokFeed.address, // {UoA/tok} oracleError: fp('0.01').toString(), // 1% @@ -174,7 +174,7 @@ describeP1(`Demurrage Collateral - P${IMPLEMENTATION}`, () => { await DemurrageCollateralFactory.deploy( { erc20: tokens[3].address, - targetName: ethers.utils.formatBytes32String('20241017-SPY-50%'), + targetName: ethers.utils.formatBytes32String('DMR5000SPY'), priceTimeout: bn('604800'), chainlinkFeed: targetPerTokFeed.address, // {target/tok} oracleError: fp('0.01').toString(), // 1% From 98fe039476f996e86c09c69f7332a1be358836ca Mon Sep 17 00:00:00 2001 From: Taylor Brent Date: Thu, 17 Oct 2024 19:41:51 -0400 Subject: [PATCH 17/40] improve test --- test/scenario/DemurrageCollateral.test.ts | 17 ++++++++++++++++- 1 file changed, 16 insertions(+), 1 deletion(-) diff --git a/test/scenario/DemurrageCollateral.test.ts b/test/scenario/DemurrageCollateral.test.ts index 836ed43d3..a37f35d08 100644 --- a/test/scenario/DemurrageCollateral.test.ts +++ b/test/scenario/DemurrageCollateral.test.ts @@ -300,77 +300,92 @@ describeP1(`Demurrage Collateral - P${IMPLEMENTATION}`, () => { } }) - it('prices/pegPrices should respond correctly to devaluation', async () => { + it('should detect default and propagate through to prices/pegPrices correctly', async () => { // 1. break uoaPerTokFeed await uoaPerTokFeed.updateAnswer(bn('1e8').div(2)) + await assetRegistry.refresh() // token1 let [low, high, pegPrice] = await collateral[0].tryPrice() expect(low.add(high).div(2)).to.equal(fp('0.5')) expect(pegPrice).to.equal(fp('1')) + expect(await collateral[0].status()).to.equal(CollateralStatus.SOUND) // token2 ;[low, high, pegPrice] = await collateral[1].tryPrice() expect(low.add(high).div(2)).to.equal(fp('0.5')) expect(pegPrice).to.equal(fp('0.5')) + expect(await collateral[1].status()).to.equal(CollateralStatus.IFFY) // token3 ;[low, high, pegPrice] = await collateral[2].tryPrice() expect(low.add(high).div(2)).to.equal(fp('0.5')) expect(pegPrice).to.equal(fp('0.5')) + expect(await collateral[2].status()).to.equal(CollateralStatus.IFFY) // token4 ;[low, high, pegPrice] = await collateral[3].tryPrice() expect(low.add(high).div(2)).to.equal(fp('1.0001')) expect(pegPrice).to.equal(fp('1')) + expect(await collateral[3].status()).to.equal(CollateralStatus.SOUND) // 2. break uoaPerTargetFeed await uoaPerTokFeed.updateAnswer(bn('1e8')) await uoaPerTargetFeed.updateAnswer(bn('1e8').div(2)) + await assetRegistry.refresh() // token1 ;[low, high, pegPrice] = await collateral[0].tryPrice() expect(low.add(high).div(2)).to.equal(fp('1')) expect(pegPrice).to.equal(fp('1')) + expect(await collateral[0].status()).to.equal(CollateralStatus.SOUND) // token2 ;[low, high, pegPrice] = await collateral[1].tryPrice() expect(low.add(high).div(2)).to.equal(fp('1')) expect(pegPrice).to.equal(fp('1')) + expect(await collateral[1].status()).to.equal(CollateralStatus.SOUND) // token3 ;[low, high, pegPrice] = await collateral[2].tryPrice() expect(low.add(high).div(2)).to.equal(fp('1')) expect(pegPrice).to.equal(fp('2')) + expect(await collateral[2].status()).to.equal(CollateralStatus.IFFY) // token4 ;[low, high, pegPrice] = await collateral[3].tryPrice() expect(low.add(high).div(2)).to.equal(fp('0.50005')) expect(pegPrice).to.equal(fp('1')) + expect(await collateral[3].status()).to.equal(CollateralStatus.SOUND) // 3. break targetPerTokFeed await uoaPerTargetFeed.updateAnswer(bn('1e8')) await targetPerTokFeed.updateAnswer(bn('1e8').div(2)) + await assetRegistry.refresh() // token1 ;[low, high, pegPrice] = await collateral[0].tryPrice() expect(low.add(high).div(2)).to.equal(fp('1')) expect(pegPrice).to.equal(fp('1')) + expect(await collateral[0].status()).to.equal(CollateralStatus.SOUND) // token2 ;[low, high, pegPrice] = await collateral[1].tryPrice() expect(low.add(high).div(2)).to.equal(fp('1')) expect(pegPrice).to.equal(fp('1')) + expect(await collateral[1].status()).to.equal(CollateralStatus.SOUND) // token3 ;[low, high, pegPrice] = await collateral[2].tryPrice() expect(low.add(high).div(2)).to.equal(fp('1')) expect(pegPrice).to.equal(fp('1')) + expect(await collateral[2].status()).to.equal(CollateralStatus.SOUND) // token4 ;[low, high, pegPrice] = await collateral[3].tryPrice() expect(low.add(high).div(2)).to.equal(fp('0.50005')) expect(pegPrice).to.equal(fp('0.5')) + expect(await collateral[3].status()).to.equal(CollateralStatus.IFFY) }) }) }) From e74e612894caa6ee99fddd90ddbc44517c5bb9d0 Mon Sep 17 00:00:00 2001 From: Taylor Brent Date: Thu, 17 Oct 2024 19:43:31 -0400 Subject: [PATCH 18/40] GMT --- contracts/plugins/assets/DemurrageCollateral.sol | 4 ++-- docs/demurrage-collateral.md | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/contracts/plugins/assets/DemurrageCollateral.sol b/contracts/plugins/assets/DemurrageCollateral.sol index 71dd7538b..54aa4e1ac 100644 --- a/contracts/plugins/assets/DemurrageCollateral.sol +++ b/contracts/plugins/assets/DemurrageCollateral.sol @@ -30,8 +30,8 @@ struct DemurrageConfig { * - apply issuance premium * * - tok = Tokenized X - * - ref = Decayed X (since 2020-01-01 00:00:00 UTC) - * - target = Decayed X (since 2020-01-01 00:00:00 UTC) + * - ref = Decayed X (since 2020-01-01 00:00:00 GMT+0000) + * - target = Decayed X (since 2020-01-01 00:00:00 GMT+0000) * - UoA = USD */ contract DemurrageCollateral is FiatCollateral { diff --git a/docs/demurrage-collateral.md b/docs/demurrage-collateral.md index 279fecf3c..207b1de0e 100644 --- a/docs/demurrage-collateral.md +++ b/docs/demurrage-collateral.md @@ -16,7 +16,7 @@ The reference unit becomes naturally inflationary, resulting in a `refPerTok` of ``` refPerTok(): 1 / (1 - demurrage_rate_per_second) ^ t - where t is seconds since 01/01/2020 00:00:00 UTC + where t is seconds since 01/01/2020 00:00:00 GMT+0000 ``` The timestamp of 01/01/2020 00:00:00 GMT+0000 is chosen arbitrarily. It's not important what this value is, but there are benefits to using a common anchor (and 1970 is too far). From 930216672d5258eeb40f4ad63afa7ffce5c1d4de Mon Sep 17 00:00:00 2001 From: Taylor Brent Date: Thu, 17 Oct 2024 19:47:48 -0400 Subject: [PATCH 19/40] 2020 -> 2024 --- contracts/plugins/assets/DemurrageCollateral.sol | 4 ++-- docs/demurrage-collateral.md | 14 +++++++------- 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/contracts/plugins/assets/DemurrageCollateral.sol b/contracts/plugins/assets/DemurrageCollateral.sol index 54aa4e1ac..e42365a85 100644 --- a/contracts/plugins/assets/DemurrageCollateral.sol +++ b/contracts/plugins/assets/DemurrageCollateral.sol @@ -30,8 +30,8 @@ struct DemurrageConfig { * - apply issuance premium * * - tok = Tokenized X - * - ref = Decayed X (since 2020-01-01 00:00:00 GMT+0000) - * - target = Decayed X (since 2020-01-01 00:00:00 GMT+0000) + * - ref = Decayed X (since 2024-01-01 00:00:00 GMT+0000) + * - target = Decayed X (since 2024-01-01 00:00:00 GMT+0000) * - UoA = USD */ contract DemurrageCollateral is FiatCollateral { diff --git a/docs/demurrage-collateral.md b/docs/demurrage-collateral.md index 207b1de0e..cf3340237 100644 --- a/docs/demurrage-collateral.md +++ b/docs/demurrage-collateral.md @@ -16,10 +16,10 @@ The reference unit becomes naturally inflationary, resulting in a `refPerTok` of ``` refPerTok(): 1 / (1 - demurrage_rate_per_second) ^ t - where t is seconds since 01/01/2020 00:00:00 GMT+0000 + where t is seconds since 01/01/2024 00:00:00 GMT+0000 ``` -The timestamp of 01/01/2020 00:00:00 GMT+0000 is chosen arbitrarily. It's not important what this value is, but there are benefits to using a common anchor (and 1970 is too far). +The timestamp of 01/01/2024 00:00:00 GMT+0000 is chosen arbitrarily. It's not important what this value is, but there are benefits to using a common anchor (and 1970 is too far). In unix time this is `1640995200` @@ -39,17 +39,17 @@ Collateral can only be automatically substituted in the basket with collateral t ### Setting the basket weights -Prime basket weights are in units of January 1st 2020 collateral, not today's collateral. It doesn't matter if the collateral wasn't around in 2020 -- when setting the basket weights the setter must take into account how much demurrage has occurred since January 1st 2020. +Prime basket weights are in units of January 1st 2024 collateral, not today's collateral. It doesn't matter if the collateral wasn't around in Jan 2024 -- when setting the basket weights the setter must take into account how much demurrage has occurred since January 1st 2024. -For example, say an asset has had 5% total demurrage since January 1st 2020 and you want to (on today's date) create a basket of that is worth $1: the correct basket weight to provide to `setPrimeBasket()` would be `1 / 0.95 = ~1.0526`. +For example, say an asset has had 2% total demurrage since January 1st 2024 and you want to (on today's date) create a basket of that is worth $1: the correct basket weight to provide to `setPrimeBasket()` would be `1 / (1 - 0.02) = ~1.0204`. -To calculate total demurrage since 2020-01-01 00:00:00 UTC, use: +To calculate total demurrage since 2024-01-01 00:00:00 UTC, use: ``` -fee() ^ (seconds_since_2020_01_01) +fee() ^ (seconds_since_2024_01_01) ``` -(where `fee()` is the per-second demurrage rate, usually found on the `DemurrageCollateral` contract) +(where `fee()` is the per-second demurrage rate found on the `DemurrageCollateral` contract below) ### Implementation From 9c226ff48f067b7e82e762010f90d4d733c411f3 Mon Sep 17 00:00:00 2001 From: Taylor Brent Date: Thu, 17 Oct 2024 19:49:35 -0400 Subject: [PATCH 20/40] fix unix time --- docs/demurrage-collateral.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/demurrage-collateral.md b/docs/demurrage-collateral.md index cf3340237..82a1a4ae2 100644 --- a/docs/demurrage-collateral.md +++ b/docs/demurrage-collateral.md @@ -19,9 +19,9 @@ refPerTok(): 1 / (1 - demurrage_rate_per_second) ^ t where t is seconds since 01/01/2024 00:00:00 GMT+0000 ``` -The timestamp of 01/01/2024 00:00:00 GMT+0000 is chosen arbitrarily. It's not important what this value is, but there are benefits to using a common anchor (and 1970 is too far). +The timestamp of 01/01/2024 00:00:00 GMT+0000 is chosen arbitrarily. It's not important what this value is, but there are benefits to using a common anchor (and 1970 is wastefully far). -In unix time this is `1640995200` +In unix time this is `1704085200` ### Target Unit From 949e99ad2ea4776d8301a63cefae890021f6796b Mon Sep 17 00:00:00 2001 From: Taylor Brent Date: Thu, 17 Oct 2024 19:51:42 -0400 Subject: [PATCH 21/40] new addresses (base) --- scripts/addresses/8453-tmp-assets-collateral.json | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/scripts/addresses/8453-tmp-assets-collateral.json b/scripts/addresses/8453-tmp-assets-collateral.json index 1cecc513d..9b5fcc8af 100644 --- a/scripts/addresses/8453-tmp-assets-collateral.json +++ b/scripts/addresses/8453-tmp-assets-collateral.json @@ -12,8 +12,8 @@ "saBasUSDC": "0xC19f5d60e2Aca1174f3D5Fe189f0A69afaB76f50", "cUSDCv3": "0xf7a9D27c3B60c78c6F6e2c2d6ED6E8B94b352461", "wstETH": "0x8b4374005291B8FCD14C4E947604b2FB3C660A73", - "cbBTC": "0x1f59E25aF6ebc107B0c409784ac261de16318877", - "EURC": "0x0AD231AEDc090F15522EF8e2f9e2F4d07b5b50E4" + "EURC": "0x7321485aA1D0439296B882bBc85Eb0BD350F8381", + "cbBTC": "0x06f7D10f5842fc5816DF9A9DD65f84481B1490E3" }, "erc20s": { "COMP": "0x9e1028F5F1D5eDE59748FFceE5532509976840E0", @@ -26,7 +26,7 @@ "STG": "0xE3B53AF74a4BF62Ae5511055290838050bf764Df", "cUSDCv3": "0x53f1Df4E5591Ae35Bf738742981669c3767241FA", "wstETH": "0xc1CBa3fCea344f92D9239c08C0568f6F2F0ee452", - "cbBTC": "0xcbB7C0000aB88B473b1f5aFd9ef808440eed33Bf", - "EURC": "0x60a3E35Cc302bFA44Cb288Bc5a4F316Fdb1adb42" + "EURC": "0x60a3E35Cc302bFA44Cb288Bc5a4F316Fdb1adb42", + "cbBTC": "0xcbB7C0000aB88B473b1f5aFd9ef808440eed33Bf" } } \ No newline at end of file From a816738005c73e425eb97e4768fa83ab91409580 Mon Sep 17 00:00:00 2001 From: Taylor Brent Date: Thu, 17 Oct 2024 21:09:22 -0400 Subject: [PATCH 22/40] fix tests --- .../dtf/PAXGCollateralTestSuite.test.ts | 2 +- test/scenario/DemurrageCollateral.test.ts | 19 +++++++++---------- 2 files changed, 10 insertions(+), 11 deletions(-) diff --git a/test/plugins/individual-collateral/dtf/PAXGCollateralTestSuite.test.ts b/test/plugins/individual-collateral/dtf/PAXGCollateralTestSuite.test.ts index d7dfd9d67..f6543bcd9 100644 --- a/test/plugins/individual-collateral/dtf/PAXGCollateralTestSuite.test.ts +++ b/test/plugins/individual-collateral/dtf/PAXGCollateralTestSuite.test.ts @@ -29,7 +29,7 @@ interface PAXGCollateralOpts extends CollateralOpts { export const defaultPAXGCollateralOpts: PAXGCollateralOpts = { erc20: PAXG, - targetName: `DMR100XAU`, + targetName: ethers.utils.formatBytes32String('DMR100XAU'), rewardERC20: ZERO_ADDRESS, priceTimeout: PRICE_TIMEOUT, chainlinkFeed: XAU_USD_PRICE_FEED, diff --git a/test/scenario/DemurrageCollateral.test.ts b/test/scenario/DemurrageCollateral.test.ts index a37f35d08..05a0ca9a6 100644 --- a/test/scenario/DemurrageCollateral.test.ts +++ b/test/scenario/DemurrageCollateral.test.ts @@ -45,11 +45,15 @@ describeP1(`Demurrage Collateral - P${IMPLEMENTATION}`, () => { let assetRegistry: IAssetRegistry let bh: TestIBasketHandler - // Perform tests for each of these decimal variations (> 18) - describe(`Demurrage Collateral`, () => { - before(async () => { + describe('Demurrage Collateral', () => { + beforeEach(async () => { ;[owner, addr1] = await ethers.getSigners() + // Deploy fixture + ;({ assetRegistry, backingManager, config, main, rToken } = await loadFixture( + defaultFixtureNoBasket + )) + // Setup Factories const BasketLibFactory: ContractFactory = await ethers.getContractFactory('BasketLibP1') const basketLib: BasketLibP1 = await BasketLibFactory.deploy() @@ -63,11 +67,6 @@ describeP1(`Demurrage Collateral - P${IMPLEMENTATION}`, () => { const ERC20Factory: ContractFactory = await ethers.getContractFactory('ERC20Mock') const ChainlinkFactory: ContractFactory = await ethers.getContractFactory('MockV3Aggregator') - // Deploy fixture - ;({ assetRegistry, backingManager, config, main, rToken } = await loadFixture( - defaultFixtureNoBasket - )) - // Replace with reweightable basket handler bh = await ethers.getContractAt( 'TestIBasketHandler', @@ -232,7 +231,7 @@ describeP1(`Demurrage Collateral - P${IMPLEMENTATION}`, () => { }) context('after 1 year', () => { - before(async () => { + beforeEach(async () => { await advanceTime(Number(bn('31535955'))) // 1 year - 45s await uoaPerTokFeed.updateAnswer(bn('1e8')) await uoaPerTargetFeed.updateAnswer(bn('1e8')) @@ -268,7 +267,7 @@ describeP1(`Demurrage Collateral - P${IMPLEMENTATION}`, () => { expect(bottom).to.be.closeTo(amt.mul(2), amt.div(bn('1e3'))) }) - it('refreshBasket() should not restore the RToken back to $1', async () => { + it('refreshBasket() should not restore the RToken back genesis peg', async () => { const [erc20s, quantities] = await bh.quote(fp('1'), false, 2) await expect(bh.connect(owner).refreshBasket()).to.emit(bh, 'BasketSet') const [newERC20s, newQuantities] = await bh.quote(fp('1'), false, 2) From 8b224ca7a3d47636e84ec5d828037fe0ea4b8d82 Mon Sep 17 00:00:00 2001 From: Taylor Brent Date: Fri, 18 Oct 2024 13:07:32 -0400 Subject: [PATCH 23/40] fix tests --- test/plugins/individual-collateral/collateralTests.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/test/plugins/individual-collateral/collateralTests.ts b/test/plugins/individual-collateral/collateralTests.ts index 638a4fe8e..e934bcbaa 100644 --- a/test/plugins/individual-collateral/collateralTests.ts +++ b/test/plugins/individual-collateral/collateralTests.ts @@ -1016,7 +1016,7 @@ export default function fn( targetUnitOracle.address, ORACLE_TIMEOUT ) - } else if (target === ethers.utils.formatBytes32String('XAU')) { + } else if (target.indexOf(ethers.utils.formatBytes32String('XAU'))) { if (onBase || onArbitrum) throw new Error('PAXG only supported on mainnet') // PAXG @@ -1030,7 +1030,7 @@ export default function fn( return await DemurrageFactory.deploy( { erc20: erc20.address, - targetName: ethers.utils.formatBytes32String('XAU'), + targetName: target, priceTimeout: PRICE_TIMEOUT, chainlinkFeed: chainlinkFeed.address, oracleError: ORACLE_ERROR, From f80b5a92e4aedfd95a9805b6fd9fe446f49ac074 Mon Sep 17 00:00:00 2001 From: Taylor Brent Date: Mon, 21 Oct 2024 22:04:47 -0400 Subject: [PATCH 24/40] deployment + verification scripts for ARB --- .../collaterals/deploy_arb_100.ts | 100 ++++++++++++++++++ .../collateral-plugins/verify_arb_100.ts | 81 ++++++++++++++ 2 files changed, 181 insertions(+) create mode 100644 scripts/deployment/phase2-assets/collaterals/deploy_arb_100.ts create mode 100644 scripts/verification/collateral-plugins/verify_arb_100.ts diff --git a/scripts/deployment/phase2-assets/collaterals/deploy_arb_100.ts b/scripts/deployment/phase2-assets/collaterals/deploy_arb_100.ts new file mode 100644 index 000000000..61de7ccad --- /dev/null +++ b/scripts/deployment/phase2-assets/collaterals/deploy_arb_100.ts @@ -0,0 +1,100 @@ +import fs from 'fs' +import hre from 'hardhat' +import { getChainId } from '../../../../common/blockchain-utils' +import { arbitrumL2Chains, networkConfig } from '../../../../common/configuration' +import { bn, fp } from '../../../../common/numbers' +import { expect } from 'chai' +import { CollateralStatus, ZERO_ADDRESS } from '../../../../common/constants' +import { + getDeploymentFile, + getAssetCollDeploymentFilename, + IAssetCollDeployments, + getDeploymentFilename, + fileExists, +} from '../../common' +import { + DELAY_UNTIL_DEFAULT, + ONE_PERCENT_FEE, +} from '../../../../test/plugins/individual-collateral/dtf/constants' +import { priceTimeout, getArbOracleError } from '../../utils' +import { DemurrageCollateral } from '../../../../typechain' +import { ContractFactory } from 'ethers' + +async function main() { + // ==== Read Configuration ==== + const [deployer] = await hre.ethers.getSigners() + + const chainId = await getChainId(hre) + + console.log(`Deploying Collateral to network ${hre.network.name} (${chainId}) + with burner account: ${deployer.address}`) + + if (!networkConfig[chainId]) { + throw new Error(`Missing network configuration for ${hre.network.name}`) + } + + // Get phase1 deployment + const phase1File = getDeploymentFilename(chainId) + if (!fileExists(phase1File)) { + throw new Error(`${phase1File} doesn't exist yet. Run phase 1`) + } + // Check previous step completed + const assetCollDeploymentFilename = getAssetCollDeploymentFilename(chainId) + const assetCollDeployments = getDeploymentFile(assetCollDeploymentFilename) + + const deployedCollateral: string[] = [] + + /******** Deploy ARB Demurrage Collateral - ARB **************************/ + + if (!arbitrumL2Chains.includes(hre.network.name)) { + throw new Error(`Unsupported chainId: ${chainId}`) + } + + const DemurrageCollateralFactory: ContractFactory = await hre.ethers.getContractFactory( + 'DemurrageCollateral' + ) + + const oracleError = getArbOracleError(hre.network.name) + + const collateral = await DemurrageCollateralFactory.connect(deployer).deploy( + { + erc20: networkConfig[chainId].tokens.ARB, + targetName: hre.ethers.utils.formatBytes32String('DMR100ARB'), + priceTimeout: priceTimeout.toString(), + chainlinkFeed: networkConfig[chainId].chainlinkFeeds.ARB, // {UoA/tok} + oracleError: oracleError.toString(), + oracleTimeout: bn('86400').toString(), // 24 hr + maxTradeVolume: fp('1e6').toString(), // $1m, + defaultThreshold: bn('0'), + delayUntilDefault: DELAY_UNTIL_DEFAULT, + }, + { + isFiat: false, + targetUnitFeed0: false, + fee: ONE_PERCENT_FEE, + feed1: ZERO_ADDRESS, + timeout1: bn('0'), + error1: bn('0'), + } + ) + await collateral.deployed() + + console.log(`Deployed ARB to ${hre.network.name} (${chainId}): ${collateral.address}`) + await (await collateral.refresh()).wait() + expect(await collateral.status()).to.equal(CollateralStatus.SOUND) + + assetCollDeployments.collateral.DMR100ARB = collateral.address + assetCollDeployments.erc20s.DMR100ARB = networkConfig[chainId].tokens.ARB + deployedCollateral.push(collateral.address.toString()) + + fs.writeFileSync(assetCollDeploymentFilename, JSON.stringify(assetCollDeployments, null, 2)) + + console.log(`Deployed collateral to ${hre.network.name} (${chainId}) + New deployments: ${deployedCollateral} + Deployment file: ${assetCollDeploymentFilename}`) +} + +main().catch((error) => { + console.error(error) + process.exitCode = 1 +}) diff --git a/scripts/verification/collateral-plugins/verify_arb_100.ts b/scripts/verification/collateral-plugins/verify_arb_100.ts new file mode 100644 index 000000000..44b21a904 --- /dev/null +++ b/scripts/verification/collateral-plugins/verify_arb_100.ts @@ -0,0 +1,81 @@ +import hre from 'hardhat' +import { getChainId } from '../../../common/blockchain-utils' +import { arbitrumL2Chains, networkConfig } from '../../../common/configuration' +import { bn, fp } from '../../../common/numbers' +import { ZERO_ADDRESS } from '../../../common/constants' +import { verifyContract } from '../../deployment/utils' +import { + getDeploymentFile, + getAssetCollDeploymentFilename, + IAssetCollDeployments, + getDeploymentFilename, + fileExists, +} from '../../deployment/common' +import { + DELAY_UNTIL_DEFAULT, + ONE_PERCENT_FEE, +} from '../../../test/plugins/individual-collateral/dtf/constants' +import { priceTimeout, getArbOracleError } from '../../deployment/utils' + +async function main() { + // ==== Read Configuration ==== + const [deployer] = await hre.ethers.getSigners() + + const chainId = await getChainId(hre) + + console.log(`Deploying Collateral to network ${hre.network.name} (${chainId}) + with burner account: ${deployer.address}`) + + if (!networkConfig[chainId]) { + throw new Error(`Missing network configuration for ${hre.network.name}`) + } + + // Get phase1 deployment + const phase1File = getDeploymentFilename(chainId) + if (!fileExists(phase1File)) { + throw new Error(`${phase1File} doesn't exist yet. Run phase 1`) + } + // Check previous step completed + const assetCollDeploymentFilename = getAssetCollDeploymentFilename(chainId) + const assetCollDeployments = getDeploymentFile(assetCollDeploymentFilename) + + /******** Verify ARB Demurrage Collateral - ARB **************************/ + + if (!arbitrumL2Chains.includes(hre.network.name)) { + throw new Error(`Unsupported chainId: ${chainId}`) + } + + const oracleError = getArbOracleError(hre.network.name) + + await verifyContract( + chainId, + assetCollDeployments.collateral.ARB, + [ + { + erc20: networkConfig[chainId].tokens.ARB, + targetName: hre.ethers.utils.formatBytes32String('DMR100ARB'), + priceTimeout: priceTimeout.toString(), + chainlinkFeed: networkConfig[chainId].chainlinkFeeds.ARB, // {UoA/tok} + oracleError: oracleError.toString(), + oracleTimeout: bn('86400').toString(), // 24 hr + maxTradeVolume: fp('1e6').toString(), // $1m, + defaultThreshold: bn('0'), + delayUntilDefault: DELAY_UNTIL_DEFAULT, + }, + { + isFiat: false, + targetUnitFeed0: false, + fee: ONE_PERCENT_FEE, + feed1: ZERO_ADDRESS, + timeout1: bn('0'), + error1: bn('0'), + }, + ], + 'contracts/plugins/assets/DemurrageCollateral.sol:DemurrageCollateral' + ) +} + +main().catch((error) => { + console.error(error) + process.exitCode = 1 +}) From 1893208ccb2499c719848c31536e47983f8bc27c Mon Sep 17 00:00:00 2001 From: Taylor Brent Date: Mon, 21 Oct 2024 22:05:12 -0400 Subject: [PATCH 25/40] refactor 100 basis point tier out to prepare for more --- common/configuration.ts | 8 +++++++- scripts/deploy.ts | 9 +++++---- scripts/deployment/common.ts | 12 +++++++++--- .../{deploy_cbbtc.ts => deploy_cbbtc_100.ts} | 4 ++-- .../{deploy_eurc.ts => deploy_eurc_100.ts} | 4 ++-- .../{deploy_paxg.ts => deploy_paxg_100.ts} | 4 ++-- .../{verify_cbbtc.ts => verify_cbbtc_100.ts} | 0 .../{verify_eurc.ts => verify_eurc_100.ts} | 0 .../{verify_paxg.ts => verify_paxg_100.ts} | 0 scripts/verify_etherscan.ts | 9 +++++---- 10 files changed, 32 insertions(+), 18 deletions(-) rename scripts/deployment/phase2-assets/collaterals/{deploy_cbbtc.ts => deploy_cbbtc_100.ts} (95%) rename scripts/deployment/phase2-assets/collaterals/{deploy_eurc.ts => deploy_eurc_100.ts} (95%) rename scripts/deployment/phase2-assets/collaterals/{deploy_paxg.ts => deploy_paxg_100.ts} (95%) rename scripts/verification/collateral-plugins/{verify_cbbtc.ts => verify_cbbtc_100.ts} (100%) rename scripts/verification/collateral-plugins/{verify_eurc.ts => verify_eurc_100.ts} (100%) rename scripts/verification/collateral-plugins/{verify_paxg.ts => verify_paxg_100.ts} (100%) diff --git a/common/configuration.ts b/common/configuration.ts index 1bea82955..e50322faf 100644 --- a/common/configuration.ts +++ b/common/configuration.ts @@ -117,7 +117,6 @@ export interface ITokens { USDM?: string wUSDM?: string - // Demurrage collateral PAXG?: string cbBTC?: string EURC?: string @@ -125,6 +124,13 @@ export interface ITokens { export type ITokensKeys = Array +export interface IDemurrageCollateral { + DMR100PAXG?: string + DMR100cbBTC?: string + DMR100EURC?: string + DMR100ARB?: string +} + export interface IFeeds { stETHETH?: string stETHUSD?: string diff --git a/scripts/deploy.ts b/scripts/deploy.ts index 89a30a347..276622608 100644 --- a/scripts/deploy.ts +++ b/scripts/deploy.ts @@ -91,7 +91,7 @@ async function main() { 'phase2-assets/assets/deploy_crv.ts', 'phase2-assets/assets/deploy_cvx.ts', 'phase2-assets/collaterals/deploy_pyusd.ts', - 'phase2-assets/collaterals/deploy_paxg.ts' + 'phase2-assets/collaterals/deploy_paxg_100.ts' ) } else if (chainId == '8453' || chainId == '84531') { // Base L2 chains @@ -104,8 +104,8 @@ async function main() { 'phase2-assets/collaterals/deploy_aave_v3_usdc.ts', 'phase2-assets/collaterals/deploy_lido_wsteth_collateral.ts', 'phase2-assets/collaterals/deploy_cbeth_collateral.ts', - 'phase2-assets/collaterals/deploy_cbbtc.ts', - 'phase2-assets/collaterals/deploy_eurc.ts', + 'phase2-assets/collaterals/deploy_cbbtc_100.ts', + 'phase2-assets/collaterals/deploy_eurc_100.ts', 'phase2-assets/assets/deploy_stg.ts' ) } else if (chainId == '42161' || chainId == '421614') { @@ -120,7 +120,8 @@ async function main() { 'phase2-assets/collaterals/deploy_convex_crvusd_usdc_collateral.ts', 'phase2-assets/collaterals/deploy_convex_crvusd_usdt_collateral.ts', 'phase2-assets/collaterals/deploy_usdm.ts', - 'phase2-assets/assets/deploy_arb.ts' + 'phase2-assets/assets/deploy_arb.ts', + 'phase2-assets/assets/deploy_arb_100.ts' ) } diff --git a/scripts/deployment/common.ts b/scripts/deployment/common.ts index a1f3393fd..b9fba844d 100644 --- a/scripts/deployment/common.ts +++ b/scripts/deployment/common.ts @@ -1,5 +1,11 @@ import fs from 'fs' -import { ITokens, IComponents, IImplementations, IPools } from '../../common/configuration' +import { + IDemurrageCollateral, + ITokens, + IComponents, + IImplementations, + IPools, +} from '../../common/configuration' // This file is intended to have minimal imports, so that it can be used from tasks if necessary @@ -32,8 +38,8 @@ export interface IDeployments { export interface IAssetCollDeployments { assets: ITokens - collateral: ITokens & IPools - erc20s: ITokens & IPools + collateral: ITokens & IPools & IDemurrageCollateral + erc20s: ITokens & IPools & IDemurrageCollateral } export interface IRTokenDeployments { diff --git a/scripts/deployment/phase2-assets/collaterals/deploy_cbbtc.ts b/scripts/deployment/phase2-assets/collaterals/deploy_cbbtc_100.ts similarity index 95% rename from scripts/deployment/phase2-assets/collaterals/deploy_cbbtc.ts rename to scripts/deployment/phase2-assets/collaterals/deploy_cbbtc_100.ts index 63ef1b77d..6a19f2eda 100644 --- a/scripts/deployment/phase2-assets/collaterals/deploy_cbbtc.ts +++ b/scripts/deployment/phase2-assets/collaterals/deploy_cbbtc_100.ts @@ -81,8 +81,8 @@ async function main() { await (await collateral.refresh()).wait() expect(await collateral.status()).to.equal(CollateralStatus.SOUND) - assetCollDeployments.collateral.cbBTC = collateral.address - assetCollDeployments.erc20s.cbBTC = networkConfig[chainId].tokens.cbBTC + assetCollDeployments.collateral.DMR100cbBTC = collateral.address + assetCollDeployments.erc20s.DMR100cbBTC = networkConfig[chainId].tokens.cbBTC deployedCollateral.push(collateral.address.toString()) fs.writeFileSync(assetCollDeploymentFilename, JSON.stringify(assetCollDeployments, null, 2)) diff --git a/scripts/deployment/phase2-assets/collaterals/deploy_eurc.ts b/scripts/deployment/phase2-assets/collaterals/deploy_eurc_100.ts similarity index 95% rename from scripts/deployment/phase2-assets/collaterals/deploy_eurc.ts rename to scripts/deployment/phase2-assets/collaterals/deploy_eurc_100.ts index b3559219b..67c6a6d31 100644 --- a/scripts/deployment/phase2-assets/collaterals/deploy_eurc.ts +++ b/scripts/deployment/phase2-assets/collaterals/deploy_eurc_100.ts @@ -81,8 +81,8 @@ async function main() { await (await collateral.refresh()).wait() expect(await collateral.status()).to.equal(CollateralStatus.SOUND) - assetCollDeployments.collateral.EURC = collateral.address - assetCollDeployments.erc20s.EURC = networkConfig[chainId].tokens.EURC + assetCollDeployments.collateral.DMR100EURC = collateral.address + assetCollDeployments.erc20s.DMR100EURC = networkConfig[chainId].tokens.EURC deployedCollateral.push(collateral.address.toString()) fs.writeFileSync(assetCollDeploymentFilename, JSON.stringify(assetCollDeployments, null, 2)) diff --git a/scripts/deployment/phase2-assets/collaterals/deploy_paxg.ts b/scripts/deployment/phase2-assets/collaterals/deploy_paxg_100.ts similarity index 95% rename from scripts/deployment/phase2-assets/collaterals/deploy_paxg.ts rename to scripts/deployment/phase2-assets/collaterals/deploy_paxg_100.ts index 95333b35e..83a8d8b20 100644 --- a/scripts/deployment/phase2-assets/collaterals/deploy_paxg.ts +++ b/scripts/deployment/phase2-assets/collaterals/deploy_paxg_100.ts @@ -81,8 +81,8 @@ async function main() { await (await collateral.refresh()).wait() expect(await collateral.status()).to.equal(CollateralStatus.SOUND) - assetCollDeployments.collateral.PAXG = collateral.address - assetCollDeployments.erc20s.PAXG = networkConfig[chainId].tokens.PAXG + assetCollDeployments.collateral.DMR100PAXG = collateral.address + assetCollDeployments.erc20s.DMR100PAXG = networkConfig[chainId].tokens.PAXG deployedCollateral.push(collateral.address.toString()) fs.writeFileSync(assetCollDeploymentFilename, JSON.stringify(assetCollDeployments, null, 2)) diff --git a/scripts/verification/collateral-plugins/verify_cbbtc.ts b/scripts/verification/collateral-plugins/verify_cbbtc_100.ts similarity index 100% rename from scripts/verification/collateral-plugins/verify_cbbtc.ts rename to scripts/verification/collateral-plugins/verify_cbbtc_100.ts diff --git a/scripts/verification/collateral-plugins/verify_eurc.ts b/scripts/verification/collateral-plugins/verify_eurc_100.ts similarity index 100% rename from scripts/verification/collateral-plugins/verify_eurc.ts rename to scripts/verification/collateral-plugins/verify_eurc_100.ts diff --git a/scripts/verification/collateral-plugins/verify_paxg.ts b/scripts/verification/collateral-plugins/verify_paxg_100.ts similarity index 100% rename from scripts/verification/collateral-plugins/verify_paxg.ts rename to scripts/verification/collateral-plugins/verify_paxg_100.ts diff --git a/scripts/verify_etherscan.ts b/scripts/verify_etherscan.ts index 5df61ad6e..6a53ad73e 100644 --- a/scripts/verify_etherscan.ts +++ b/scripts/verify_etherscan.ts @@ -80,8 +80,8 @@ async function main() { 'collateral-plugins/verify_ethx.ts', 'collateral-plugins/verify_apxeth.ts', 'collateral-plugins/verify_USDe.ts', - 'collateral-plugins/verify_pyusd.ts', - 'collateral-plugins/verify_paxg.ts' + 'collateral-plugins/verify_pyusd_100.ts', + 'collateral-plugins/verify_paxg_100.ts' ) } else if (chainId == '8453' || chainId == '84531') { // Base L2 chains @@ -92,7 +92,7 @@ async function main() { 'collateral-plugins/verify_cbeth.ts', 'assets/verify_stg.ts', 'collateral-plugins/verify_cbbtc.ts', - 'collateral-plugins/verify_eurc.ts' + 'collateral-plugins/verify_eurc_100.ts' ) } else if (chainId == '42161' || chainId == '421614') { // Arbitrum One @@ -101,7 +101,8 @@ async function main() { 'collateral-plugins/verify_cusdcv3.ts', 'collateral-plugins/verify_convex_crvusd_usdc.ts', 'collateral-plugins/verify_convex_crvusd_usdt.ts', - 'collateral-plugins/verify_usdm.ts' + 'collateral-plugins/verify_usdm.ts', + 'collateral-plugins/verify_arb_100.ts' ) } From 0a7d58df8f5778c0001e7f306421c406e7d03d24 Mon Sep 17 00:00:00 2001 From: Taylor Brent Date: Mon, 21 Oct 2024 22:25:00 -0400 Subject: [PATCH 26/40] docs --- docs/demurrage-collateral.md | 41 ++++++++++++++++++++++++++++++------ 1 file changed, 34 insertions(+), 7 deletions(-) diff --git a/docs/demurrage-collateral.md b/docs/demurrage-collateral.md index 82a1a4ae2..9dcfc26bd 100644 --- a/docs/demurrage-collateral.md +++ b/docs/demurrage-collateral.md @@ -6,9 +6,22 @@ Many assets on-chain do not have yield. While the Reserve Protocol is compatible with non-yielding assets, this introduces downsides: an RToken naively composed entirely of non-yielding collateral assets lacks RSR overcollateralization and governance. -In this case a revenue stream can be created by composing a synthetic reference unit that refers to a falling quantity of the collateral token. This causes the reference unit to become inflationary with respect to the collateral unit, resulting in a monotonically increasing `refPerTok()` by definition. +In this case a revenue stream can be created by composing an inflationary reference + target units that refer to a falling quantity of the token unit. This results in a monotonically increasing `refPerTok()` that can be consumed by the protocol to measure appreciation. -There are side-effects to the `targetName`, however the rest of the collateral plugin remains the same. +There are side-effects to the `targetName`, however the rest of the collateral plugin remains much the same. + +In principle demurrage can be added to any type of collateral, even already yield-bearing collateral. + +**Units** + +```solidity +/** + * - tok = Tokenized X + * - ref = Decayed X (since 2024-01-01 00:00:00 GMT+0000) + * - target = Decayed X (since 2024-01-01 00:00:00 GMT+0000) + * - UoA = USD + */ +``` ### Reference Unit (inflationary) @@ -23,34 +36,48 @@ The timestamp of 01/01/2024 00:00:00 GMT+0000 is chosen arbitrarily. It's not im In unix time this is `1704085200` -### Target Unit +### Target Unit (inflationary) + +The reference unit maintains a 1:1 rate against the target unit ``` targetPerRef(): 1 ``` +As a naming convention, we suggest: `DMR{annual_demurrage_in_basis_points}{token_symbol}` or `DMR100USD`, for example 1. The `DMR` prefix is short for demurrage 2. The `annual_demurrage_in_basis_points` is a number such as 100 for 1% annually -3. The `token_symbol` is the symbol of what would have otherwise been the target unit had the collateral been purely SelfReferential +3. The `token_symbol` is the symbol of the unit absent any demurrage Collateral can only be automatically substituted in the basket with collateral that share the _exact_ same target unit. This unfortunately means a standard WETH collateral cannot be backup for a demurrage ETH collateral. Both the unit type and rate must be identical in order for two collateral to be in the same target unit class. +This also means there can be multiple demurrage collateral for a single token. We refer to these as tiers. + ### Setting the basket weights Prime basket weights are in units of January 1st 2024 collateral, not today's collateral. It doesn't matter if the collateral wasn't around in Jan 2024 -- when setting the basket weights the setter must take into account how much demurrage has occurred since January 1st 2024. For example, say an asset has had 2% total demurrage since January 1st 2024 and you want to (on today's date) create a basket of that is worth $1: the correct basket weight to provide to `setPrimeBasket()` would be `1 / (1 - 0.02) = ~1.0204`. -To calculate total demurrage since 2024-01-01 00:00:00 UTC, use: +To calculate total demurrage: ``` -fee() ^ (seconds_since_2024_01_01) +per_second_fee ^ (seconds) ``` -(where `fee()` is the per-second demurrage rate found on the `DemurrageCollateral` contract below) +Switching between tiers requires calculating total demurrage for both tiers in order to ensure the basket change does not accidentally appreciate or depreciate the RToken. ### Implementation [DemurrageCollateral.sol](../contracts/plugins/assets/DemurrageCollateral.sol) implements a generalized demurrage collateral plugin that should support almost all use-cases + +Sample usage: + +- [deploy_cbbtc_100.ts](../scripts/deployment/phase2-assets/collaterals/deploy_cbbtc_100.ts) +- [deploy_eurc_100.ts](../scripts/deployment/phase2-assets/collaterals/deploy_eurc_100.ts) +- [deploy_paxg_100.ts](../scripts/deployment/phase2-assets/collaterals/deploy_paxg_100.ts) +- [deploy_arb_100.ts](../scripts/deployment/phase2-assets/collaterals/deploy_arb_100.ts) + +TODO link to demurrage collateral factory address after deployment From fe1d6db89cf4fa5ec2b26453852f9da787cd63ad Mon Sep 17 00:00:00 2001 From: Taylor Brent Date: Tue, 22 Oct 2024 16:12:33 -0400 Subject: [PATCH 27/40] record deployments in factory --- contracts/facade/factories/DemurrageCollateralFactory.sol | 3 +++ 1 file changed, 3 insertions(+) diff --git a/contracts/facade/factories/DemurrageCollateralFactory.sol b/contracts/facade/factories/DemurrageCollateralFactory.sol index 04adddcab..cc21e8d8e 100644 --- a/contracts/facade/factories/DemurrageCollateralFactory.sol +++ b/contracts/facade/factories/DemurrageCollateralFactory.sol @@ -9,6 +9,8 @@ import "../../plugins/assets/DemurrageCollateral.sol"; contract DemurrageCollateralFactory { event DemurrageCollateralDeployed(address indexed collateral); + mapping(address coll => uint192 feePerSecond) public demurrageDeployments; + bytes32 public constant USD = bytes32("USD"); function deployNewDemurrageCollateral( @@ -20,6 +22,7 @@ contract DemurrageCollateralFactory { } newCollateral = address(new DemurrageCollateral(config, demurrageConfig)); + demurrageDeployments[newCollateral] = demurrageConfig.fee; emit DemurrageCollateralDeployed(newCollateral); } } From 0c0ef5f61907269011bf44696c1d9813663ff6fb Mon Sep 17 00:00:00 2001 From: Taylor Brent Date: Tue, 22 Oct 2024 17:05:44 -0400 Subject: [PATCH 28/40] fix t0 --- contracts/plugins/assets/DemurrageCollateral.sol | 5 ++--- docs/demurrage-collateral.md | 4 ++-- 2 files changed, 4 insertions(+), 5 deletions(-) diff --git a/contracts/plugins/assets/DemurrageCollateral.sol b/contracts/plugins/assets/DemurrageCollateral.sol index e42365a85..8745de362 100644 --- a/contracts/plugins/assets/DemurrageCollateral.sol +++ b/contracts/plugins/assets/DemurrageCollateral.sol @@ -38,6 +38,8 @@ contract DemurrageCollateral is FiatCollateral { using FixLib for uint192; using OracleLib for AggregatorV3Interface; + uint48 public constant t0 = 1704067200; // {s} Jan 1st 2024 00:00:00 GMT+0000 + bool internal immutable isFiat; bool internal immutable targetUnitFeed0; // if true: feed0 is {target/tok} @@ -50,8 +52,6 @@ contract DemurrageCollateral is FiatCollateral { uint192 internal immutable error1; // {1} // immutable in spirit -- cannot be because of FiatCollateral's targetPerRef() call - // TODO would love to find a way to make these immutable for gas reasons - uint48 public t0; // {s} deployment timestamp uint192 public fee; // {1/s} demurrage fee; target unit deflation /// @param config.chainlinkFeed => feed0: {UoA/tok} or {target/tok} @@ -81,7 +81,6 @@ contract DemurrageCollateral is FiatCollateral { error0 = config.oracleError; error1 = demurrageConfig.error1; - t0 = uint48(block.timestamp); fee = demurrageConfig.fee; } diff --git a/docs/demurrage-collateral.md b/docs/demurrage-collateral.md index 9dcfc26bd..ccb9a7ebd 100644 --- a/docs/demurrage-collateral.md +++ b/docs/demurrage-collateral.md @@ -34,7 +34,7 @@ refPerTok(): 1 / (1 - demurrage_rate_per_second) ^ t The timestamp of 01/01/2024 00:00:00 GMT+0000 is chosen arbitrarily. It's not important what this value is, but there are benefits to using a common anchor (and 1970 is wastefully far). -In unix time this is `1704085200` +In unix time this is `1704067200` ### Target Unit (inflationary) @@ -64,7 +64,7 @@ For example, say an asset has had 2% total demurrage since January 1st 2024 and To calculate total demurrage: ``` -per_second_fee ^ (seconds) +1 - (1 - per_second_fee) ^ (seconds) ``` Switching between tiers requires calculating total demurrage for both tiers in order to ensure the basket change does not accidentally appreciate or depreciate the RToken. From 6dbc0e735959e9e2d13d66c96e840c1ed57a1e7c Mon Sep 17 00:00:00 2001 From: Taylor Brent Date: Tue, 22 Oct 2024 17:05:54 -0400 Subject: [PATCH 29/40] fix scenario tests and test multiple rates in basket at once --- test/scenario/DemurrageCollateral.test.ts | 79 +++++++++++++++-------- 1 file changed, 53 insertions(+), 26 deletions(-) diff --git a/test/scenario/DemurrageCollateral.test.ts b/test/scenario/DemurrageCollateral.test.ts index 05a0ca9a6..53d8328b7 100644 --- a/test/scenario/DemurrageCollateral.test.ts +++ b/test/scenario/DemurrageCollateral.test.ts @@ -1,10 +1,18 @@ import { loadFixture, setStorageAt } from '@nomicfoundation/hardhat-network-helpers' import { SignerWithAddress } from '@nomiclabs/hardhat-ethers/signers' +import { getLatestBlockTimestamp } from '../utils/time' import { expect } from 'chai' -import { ContractFactory } from 'ethers' +import { BigNumber, ContractFactory } from 'ethers' +import { makeDecayFn } from '../utils/rewards' import { ethers, upgrades } from 'hardhat' import { IConfig } from '../../common/configuration' import { bn, fp } from '../../common/numbers' +import { + TEN_BPS_FEE, + ONE_PERCENT_FEE, + TWO_PERCENT_FEE, + FIFTY_BPS_FEE, +} from '../plugins/individual-collateral/dtf/constants' import { BasketLibP1, ERC20Mock, @@ -22,8 +30,6 @@ import { CollateralStatus, ZERO_ADDRESS } from '../../common/constants' const describeP1 = IMPLEMENTATION == Implementation.P1 ? describe : describe.skip -const FIFTY_PERCENT_ANNUALLY = bn('21979552668') // 50% annually - describeP1(`Demurrage Collateral - P${IMPLEMENTATION}`, () => { const amt = fp('1') @@ -32,6 +38,7 @@ describeP1(`Demurrage Collateral - P${IMPLEMENTATION}`, () => { let tokens: ERC20Mock[] let collateral: DemurrageCollateral[] + let initialWeights: BigNumber[] let uoaPerTokFeed: MockV3Aggregator let uoaPerTargetFeed: MockV3Aggregator @@ -45,6 +52,15 @@ describeP1(`Demurrage Collateral - P${IMPLEMENTATION}`, () => { let assetRegistry: IAssetRegistry let bh: TestIBasketHandler + const calcBasketWeight = async ( + coll: DemurrageCollateral, + decayedAmt: BigNumber + ): Promise => { + const elapsed = (await getLatestBlockTimestamp()) - (await coll.t0()) + const decayFn = makeDecayFn(await coll.fee()) + return fp('1e18').div(decayFn(decayedAmt, elapsed)) + } + describe('Demurrage Collateral', () => { beforeEach(async () => { ;[owner, addr1] = await ethers.getSigners() @@ -110,7 +126,7 @@ describeP1(`Demurrage Collateral - P${IMPLEMENTATION}`, () => { await DemurrageCollateralFactory.deploy( { erc20: tokens[0].address, - targetName: ethers.utils.formatBytes32String('DMR5000USD'), + targetName: ethers.utils.formatBytes32String('DMR10USD'), priceTimeout: bn('604800'), chainlinkFeed: uoaPerTokFeed.address, // {UoA/tok} oracleError: fp('0.01').toString(), // 1% @@ -122,7 +138,7 @@ describeP1(`Demurrage Collateral - P${IMPLEMENTATION}`, () => { { isFiat: false, targetUnitFeed0: false, - fee: FIFTY_PERCENT_ANNUALLY, + fee: TEN_BPS_FEE, feed1: ZERO_ADDRESS, timeout1: bn('0'), error1: bn('0'), @@ -131,7 +147,7 @@ describeP1(`Demurrage Collateral - P${IMPLEMENTATION}`, () => { await DemurrageCollateralFactory.deploy( { erc20: tokens[1].address, - targetName: ethers.utils.formatBytes32String('DMR5000EUR'), + targetName: ethers.utils.formatBytes32String('DMR50EUR'), priceTimeout: bn('604800'), chainlinkFeed: uoaPerTokFeed.address, // {UoA/tok} oracleError: fp('0.01').toString(), // 1% @@ -143,7 +159,7 @@ describeP1(`Demurrage Collateral - P${IMPLEMENTATION}`, () => { { isFiat: true, targetUnitFeed0: false, - fee: FIFTY_PERCENT_ANNUALLY, + fee: FIFTY_BPS_FEE, feed1: ZERO_ADDRESS, timeout1: bn('0'), error1: bn('0'), @@ -152,7 +168,7 @@ describeP1(`Demurrage Collateral - P${IMPLEMENTATION}`, () => { await DemurrageCollateralFactory.deploy( { erc20: tokens[2].address, - targetName: ethers.utils.formatBytes32String('DMR5000XAU'), + targetName: ethers.utils.formatBytes32String('DMR100XAU'), priceTimeout: bn('604800'), chainlinkFeed: uoaPerTokFeed.address, // {UoA/tok} oracleError: fp('0.01').toString(), // 1% @@ -164,7 +180,7 @@ describeP1(`Demurrage Collateral - P${IMPLEMENTATION}`, () => { { isFiat: false, targetUnitFeed0: false, - fee: FIFTY_PERCENT_ANNUALLY, + fee: ONE_PERCENT_FEE, feed1: uoaPerTargetFeed.address, // {UoA/target} timeout1: bn('86400').toString(), // 24 hr error1: fp('0.01').toString(), // 1% @@ -173,7 +189,7 @@ describeP1(`Demurrage Collateral - P${IMPLEMENTATION}`, () => { await DemurrageCollateralFactory.deploy( { erc20: tokens[3].address, - targetName: ethers.utils.formatBytes32String('DMR5000SPY'), + targetName: ethers.utils.formatBytes32String('DMR200SPY'), priceTimeout: bn('604800'), chainlinkFeed: targetPerTokFeed.address, // {target/tok} oracleError: fp('0.01').toString(), // 1% @@ -185,7 +201,7 @@ describeP1(`Demurrage Collateral - P${IMPLEMENTATION}`, () => { { isFiat: false, targetUnitFeed0: true, - fee: FIFTY_PERCENT_ANNUALLY, + fee: TWO_PERCENT_FEE, feed1: uoaPerTargetFeed.address, // {UoA/target} timeout1: bn('86400').toString(), // 24 hr error1: fp('0.01').toString(), // 1% @@ -199,9 +215,11 @@ describeP1(`Demurrage Collateral - P${IMPLEMENTATION}`, () => { await tokens[i].connect(addr1).approve(rToken.address, amt) } + initialWeights = await Promise.all(collateral.map((coll) => calcBasketWeight(coll, fp('1')))) + await bh.connect(owner).setPrimeBasket( tokens.map((t) => t.address), - [fp('1'), fp('1'), fp('1'), fp('1')] + initialWeights ) await bh.connect(owner).refreshBasket() await advanceTime(Number(config.warmupPeriod) + 1) @@ -222,7 +240,7 @@ describeP1(`Demurrage Collateral - P${IMPLEMENTATION}`, () => { expect(pegPrice).to.equal(fp('1')) }) - it('quantities should be correct', async () => { + it('quantities in basket should start out near fp(1)', async () => { const [erc20s, quantities] = await bh.quote(fp('1'), false, 2) for (let i = 0; i < collateral.length; i++) { expect(erc20s[i]).to.equal(tokens[i].address) @@ -232,7 +250,7 @@ describeP1(`Demurrage Collateral - P${IMPLEMENTATION}`, () => { context('after 1 year', () => { beforeEach(async () => { - await advanceTime(Number(bn('31535955'))) // 1 year - 45s + await advanceTime(Number(bn('31535940'))) // 1 year - 60s await uoaPerTokFeed.updateAnswer(bn('1e8')) await uoaPerTargetFeed.updateAnswer(bn('1e8')) await targetPerTokFeed.updateAnswer(bn('1e8')) @@ -253,20 +271,16 @@ describeP1(`Demurrage Collateral - P${IMPLEMENTATION}`, () => { expect(pegPrice).to.equal(fp('1')) }) - it('RToken quantities should have decreased ~50%', async () => { + it('RToken quantities should decrease correctly per fee tier: [0.1%, 0.50%, 1%, 2%]', async () => { + const expected = [fp('0.999'), fp('0.995'), fp('0.99'), fp('0.98')] + const [erc20s, quantities] = await bh.quote(fp('1'), false, 2) for (let i = 0; i < collateral.length; i++) { expect(erc20s[i]).to.equal(tokens[i].address) - const expected = fp('1').div(2) - expect(quantities[i]).to.be.closeTo(expected, expected.div(bn('1e6'))) + expect(quantities[i]).to.be.closeTo(expected[i], expected[i].div(bn('1e6'))) } }) - it('Excess should accrue as revenue', async () => { - const [bottom] = await bh.basketsHeldBy(backingManager.address) - expect(bottom).to.be.closeTo(amt.mul(2), amt.div(bn('1e3'))) - }) - it('refreshBasket() should not restore the RToken back genesis peg', async () => { const [erc20s, quantities] = await bh.quote(fp('1'), false, 2) await expect(bh.connect(owner).refreshBasket()).to.emit(bh, 'BasketSet') @@ -282,11 +296,8 @@ describeP1(`Demurrage Collateral - P${IMPLEMENTATION}`, () => { }) it('setPrimeBasket() should not restore the RToken to genesis peg', async () => { + // First try refreshBasket() in isolation const [erc20s, quantities] = await bh.quote(fp('1'), false, 2) - await bh.connect(owner).setPrimeBasket( - tokens.map((t) => t.address), - [fp('1'), fp('1'), fp('1'), fp('1')] - ) await bh.connect(owner).refreshBasket() const [newERC20s, newQuantities] = await bh.quote(fp('1'), false, 2) @@ -297,6 +308,22 @@ describeP1(`Demurrage Collateral - P${IMPLEMENTATION}`, () => { expect(quantities[i]).to.be.gt(newQuantities[i]) expect(quantities[i]).to.be.lt(newQuantities[i].add(fp('1e-6'))) } + + // Then try refreshBasket() after setPrimeBasket() + await bh.connect(owner).setPrimeBasket( + tokens.map((t) => t.address), + initialWeights + ) + await bh.connect(owner).refreshBasket() + const [newerERC20s, newerQuantities] = await bh.quote(fp('1'), false, 2) + + expect(await bh.status()).to.equal(CollateralStatus.SOUND) + expect(await bh.fullyCollateralized()).to.equal(true) + for (let i = 0; i < collateral.length; i++) { + expect(erc20s[i]).to.equal(newerERC20s[i]) + expect(quantities[i]).to.be.gt(newerQuantities[i]) + expect(quantities[i]).to.be.lt(newerQuantities[i].add(fp('1e-6'))) + } }) it('should detect default and propagate through to prices/pegPrices correctly', async () => { From 8660b09db5c9ea279b666980488fc919b2f7a679 Mon Sep 17 00:00:00 2001 From: Taylor Brent Date: Tue, 22 Oct 2024 17:07:12 -0400 Subject: [PATCH 30/40] lint --- contracts/facade/factories/DemurrageCollateralFactory.sol | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/contracts/facade/factories/DemurrageCollateralFactory.sol b/contracts/facade/factories/DemurrageCollateralFactory.sol index cc21e8d8e..220b75377 100644 --- a/contracts/facade/factories/DemurrageCollateralFactory.sol +++ b/contracts/facade/factories/DemurrageCollateralFactory.sol @@ -9,7 +9,8 @@ import "../../plugins/assets/DemurrageCollateral.sol"; contract DemurrageCollateralFactory { event DemurrageCollateralDeployed(address indexed collateral); - mapping(address coll => uint192 feePerSecond) public demurrageDeployments; + // collateral address => fee per second + mapping(address => uint192) public demurrageDeployments; bytes32 public constant USD = bytes32("USD"); From d06ad18adaf17cc95fcce0fe28e15c1067405534 Mon Sep 17 00:00:00 2001 From: Taylor Brent Date: Tue, 22 Oct 2024 18:04:20 -0400 Subject: [PATCH 31/40] test revenue processing under even DMR rates --- test/scenario/DemurrageCollateral.test.ts | 784 ++++++++++++++-------- 1 file changed, 511 insertions(+), 273 deletions(-) diff --git a/test/scenario/DemurrageCollateral.test.ts b/test/scenario/DemurrageCollateral.test.ts index 53d8328b7..93e428373 100644 --- a/test/scenario/DemurrageCollateral.test.ts +++ b/test/scenario/DemurrageCollateral.test.ts @@ -7,6 +7,7 @@ import { makeDecayFn } from '../utils/rewards' import { ethers, upgrades } from 'hardhat' import { IConfig } from '../../common/configuration' import { bn, fp } from '../../common/numbers' +import { setOraclePrice } from '../utils/oracles' import { TEN_BPS_FEE, ONE_PERCENT_FEE, @@ -14,13 +15,16 @@ import { FIFTY_BPS_FEE, } from '../plugins/individual-collateral/dtf/constants' import { + Asset, BasketLibP1, ERC20Mock, IAssetRegistry, + RTokenAsset, MockV3Aggregator, TestIBackingManager, TestIBasketHandler, TestIMain, + TestIRevenueTrader, TestIRToken, DemurrageCollateral, } from '../../typechain' @@ -51,6 +55,10 @@ describeP1(`Demurrage Collateral - P${IMPLEMENTATION}`, () => { let rToken: TestIRToken let assetRegistry: IAssetRegistry let bh: TestIBasketHandler + let rTokenTrader: TestIRevenueTrader + let rsrTrader: TestIRevenueTrader + let rsrAsset: Asset + let rTokenAsset: RTokenAsset const calcBasketWeight = async ( coll: DemurrageCollateral, @@ -66,9 +74,8 @@ describeP1(`Demurrage Collateral - P${IMPLEMENTATION}`, () => { ;[owner, addr1] = await ethers.getSigners() // Deploy fixture - ;({ assetRegistry, backingManager, config, main, rToken } = await loadFixture( - defaultFixtureNoBasket - )) + ;({ assetRegistry, backingManager, config, main, rToken, rTokenTrader, rsrTrader, rsrAsset } = + await loadFixture(defaultFixtureNoBasket)) // Setup Factories const BasketLibFactory: ContractFactory = await ethers.getContractFactory('BasketLibP1') @@ -77,11 +84,6 @@ describeP1(`Demurrage Collateral - P${IMPLEMENTATION}`, () => { 'BasketHandlerP1', { libraries: { BasketLibP1: basketLib.address } } ) - const DemurrageCollateralFactory: ContractFactory = await ethers.getContractFactory( - 'DemurrageCollateral' - ) - const ERC20Factory: ContractFactory = await ethers.getContractFactory('ERC20Mock') - const ChainlinkFactory: ContractFactory = await ethers.getContractFactory('MockV3Aggregator') // Replace with reweightable basket handler bh = await ethers.getContractAt( @@ -102,165 +104,152 @@ describeP1(`Demurrage Collateral - P${IMPLEMENTATION}`, () => { await setStorageAt(backingManager.address, 302, bh.address) await setStorageAt(assetRegistry.address, 201, bh.address) - /***** Replace the original 4 tokens with 4 demurrage collateral ***********/ - // The 4 versions of DemurrageCollateral: - // 1. isFiat = false: {UoA/tok} (no default detection) - // 2. isFiat = true: {UoA/tok} (/w default detection) - // 3. targetUnitFeed0 = false: {UoA/tok} and {UoA/target} (/w default detection) - // 4. targetUnitFeed0 = true: {target/tok} and {UoA/target} (/w default detection) - - tokens = ( - await Promise.all([ - ERC20Factory.deploy('NAME1', 'TKN1'), - ERC20Factory.deploy('NAME2', 'TKN2'), - ERC20Factory.deploy('NAME3', 'TKN3'), - ERC20Factory.deploy('NAME4', 'TKN4'), - ]) - ) - - uoaPerTokFeed = await ChainlinkFactory.deploy(8, bn('1e8')) - uoaPerTargetFeed = await ChainlinkFactory.deploy(8, bn('1e8')) - targetPerTokFeed = await ChainlinkFactory.deploy(8, bn('1e8')) - - collateral = await Promise.all([ - await DemurrageCollateralFactory.deploy( - { - erc20: tokens[0].address, - targetName: ethers.utils.formatBytes32String('DMR10USD'), - priceTimeout: bn('604800'), - chainlinkFeed: uoaPerTokFeed.address, // {UoA/tok} - oracleError: fp('0.01').toString(), // 1% - oracleTimeout: bn('86400').toString(), // 24 hr - maxTradeVolume: fp('1e6').toString(), // $1m, - defaultThreshold: fp('0.01'), - delayUntilDefault: bn('86400'), - }, - { - isFiat: false, - targetUnitFeed0: false, - fee: TEN_BPS_FEE, - feed1: ZERO_ADDRESS, - timeout1: bn('0'), - error1: bn('0'), - } - ), - await DemurrageCollateralFactory.deploy( - { - erc20: tokens[1].address, - targetName: ethers.utils.formatBytes32String('DMR50EUR'), - priceTimeout: bn('604800'), - chainlinkFeed: uoaPerTokFeed.address, // {UoA/tok} - oracleError: fp('0.01').toString(), // 1% - oracleTimeout: bn('86400').toString(), // 24 hr - maxTradeVolume: fp('1e6').toString(), // $1m, - defaultThreshold: fp('0.01'), - delayUntilDefault: bn('86400'), - }, - { - isFiat: true, - targetUnitFeed0: false, - fee: FIFTY_BPS_FEE, - feed1: ZERO_ADDRESS, - timeout1: bn('0'), - error1: bn('0'), - } - ), - await DemurrageCollateralFactory.deploy( - { - erc20: tokens[2].address, - targetName: ethers.utils.formatBytes32String('DMR100XAU'), - priceTimeout: bn('604800'), - chainlinkFeed: uoaPerTokFeed.address, // {UoA/tok} - oracleError: fp('0.01').toString(), // 1% - oracleTimeout: bn('86400').toString(), // 24 hr - maxTradeVolume: fp('1e6').toString(), // $1m, - defaultThreshold: fp('0.01'), - delayUntilDefault: bn('86400'), - }, - { - isFiat: false, - targetUnitFeed0: false, - fee: ONE_PERCENT_FEE, - feed1: uoaPerTargetFeed.address, // {UoA/target} - timeout1: bn('86400').toString(), // 24 hr - error1: fp('0.01').toString(), // 1% - } - ), - await DemurrageCollateralFactory.deploy( - { - erc20: tokens[3].address, - targetName: ethers.utils.formatBytes32String('DMR200SPY'), - priceTimeout: bn('604800'), - chainlinkFeed: targetPerTokFeed.address, // {target/tok} - oracleError: fp('0.01').toString(), // 1% - oracleTimeout: bn('86400').toString(), // 24 hr - maxTradeVolume: fp('1e6').toString(), // $1m, - defaultThreshold: fp('0.01'), - delayUntilDefault: bn('86400'), - }, - { - isFiat: false, - targetUnitFeed0: true, - fee: TWO_PERCENT_FEE, - feed1: uoaPerTargetFeed.address, // {UoA/target} - timeout1: bn('86400').toString(), // 24 hr - error1: fp('0.01').toString(), // 1% - } - ), - ]) + // Update RTokenAsset + const RTokenAssetFactory: ContractFactory = await ethers.getContractFactory('RTokenAsset') + rTokenAsset = await RTokenAssetFactory.deploy(rToken.address, fp('1e6')) + await assetRegistry.connect(owner).swapRegistered(rTokenAsset.address) + }) - for (let i = 0; i < collateral.length; i++) { - await assetRegistry.connect(owner).register(collateral[i].address) - await tokens[i].mint(addr1.address, amt) - await tokens[i].connect(addr1).approve(rToken.address, amt) - } + context('Asymmetric DMRs', () => { + beforeEach(async () => { + const DemurrageCollateralFactory: ContractFactory = await ethers.getContractFactory( + 'DemurrageCollateral' + ) + const ERC20Factory: ContractFactory = await ethers.getContractFactory('ERC20Mock') + const ChainlinkFactory: ContractFactory = await ethers.getContractFactory( + 'MockV3Aggregator' + ) - initialWeights = await Promise.all(collateral.map((coll) => calcBasketWeight(coll, fp('1')))) + /***** Replace the original 4 tokens with 4 demurrage collateral with asymmetric DMRs ***********/ + // The 4 versions of DemurrageCollateral, each with different DMR rate: + // 1. isFiat = false: {UoA/tok} (no default detection) + // 2. isFiat = true: {UoA/tok} (/w default detection) + // 3. targetUnitFeed0 = false: {UoA/tok} and {UoA/target} (/w default detection) + // 4. targetUnitFeed0 = true: {target/tok} and {UoA/target} (/w default detection) + + tokens = ( + await Promise.all([ + ERC20Factory.deploy('NAME1', 'TKN1'), + ERC20Factory.deploy('NAME2', 'TKN2'), + ERC20Factory.deploy('NAME3', 'TKN3'), + ERC20Factory.deploy('NAME4', 'TKN4'), + ]) + ) - await bh.connect(owner).setPrimeBasket( - tokens.map((t) => t.address), - initialWeights - ) - await bh.connect(owner).refreshBasket() - await advanceTime(Number(config.warmupPeriod) + 1) - await rToken.connect(addr1).issue(amt) - expect(await rToken.totalSupply()).to.equal(amt) - expect(await bh.status()).to.equal(CollateralStatus.SOUND) - expect(await bh.fullyCollateralized()).to.equal(true) - }) + uoaPerTokFeed = await ChainlinkFactory.deploy(8, bn('1e8')) + uoaPerTargetFeed = await ChainlinkFactory.deploy(8, bn('1e8')) + targetPerTokFeed = await ChainlinkFactory.deploy(8, bn('1e8')) - it('prices/pegPrices should be correct', async () => { - for (let i = 0; i < 3; i++) { - const [low, high, pegPrice] = await collateral[i].tryPrice() - expect(low.add(high).div(2)).to.equal(fp('1')) - expect(pegPrice).to.equal(fp('1')) - } - const [low, high, pegPrice] = await collateral[3].tryPrice() - expect(low.add(high).div(2)).to.equal(fp('1.0001')) // asymmetry from multiplying oracles together - expect(pegPrice).to.equal(fp('1')) - }) + collateral = await Promise.all([ + await DemurrageCollateralFactory.deploy( + { + erc20: tokens[0].address, + targetName: ethers.utils.formatBytes32String('DMR10USD'), + priceTimeout: bn('604800'), + chainlinkFeed: uoaPerTokFeed.address, // {UoA/tok} + oracleError: fp('0.01').toString(), // 1% + oracleTimeout: bn('86400').toString(), // 24 hr + maxTradeVolume: fp('1e6').toString(), // $1m, + defaultThreshold: fp('0.01'), + delayUntilDefault: bn('86400'), + }, + { + isFiat: false, + targetUnitFeed0: false, + fee: TEN_BPS_FEE, + feed1: ZERO_ADDRESS, + timeout1: bn('0'), + error1: bn('0'), + } + ), + await DemurrageCollateralFactory.deploy( + { + erc20: tokens[1].address, + targetName: ethers.utils.formatBytes32String('DMR50EUR'), + priceTimeout: bn('604800'), + chainlinkFeed: uoaPerTokFeed.address, // {UoA/tok} + oracleError: fp('0.01').toString(), // 1% + oracleTimeout: bn('86400').toString(), // 24 hr + maxTradeVolume: fp('1e6').toString(), // $1m, + defaultThreshold: fp('0.01'), + delayUntilDefault: bn('86400'), + }, + { + isFiat: true, + targetUnitFeed0: false, + fee: FIFTY_BPS_FEE, + feed1: ZERO_ADDRESS, + timeout1: bn('0'), + error1: bn('0'), + } + ), + await DemurrageCollateralFactory.deploy( + { + erc20: tokens[2].address, + targetName: ethers.utils.formatBytes32String('DMR100XAU'), + priceTimeout: bn('604800'), + chainlinkFeed: uoaPerTokFeed.address, // {UoA/tok} + oracleError: fp('0.01').toString(), // 1% + oracleTimeout: bn('86400').toString(), // 24 hr + maxTradeVolume: fp('1e6').toString(), // $1m, + defaultThreshold: fp('0.01'), + delayUntilDefault: bn('86400'), + }, + { + isFiat: false, + targetUnitFeed0: false, + fee: ONE_PERCENT_FEE, + feed1: uoaPerTargetFeed.address, // {UoA/target} + timeout1: bn('86400').toString(), // 24 hr + error1: fp('0.01').toString(), // 1% + } + ), + await DemurrageCollateralFactory.deploy( + { + erc20: tokens[3].address, + targetName: ethers.utils.formatBytes32String('DMR200SPY'), + priceTimeout: bn('604800'), + chainlinkFeed: targetPerTokFeed.address, // {target/tok} + oracleError: fp('0.01').toString(), // 1% + oracleTimeout: bn('86400').toString(), // 24 hr + maxTradeVolume: fp('1e6').toString(), // $1m, + defaultThreshold: fp('0.01'), + delayUntilDefault: bn('86400'), + }, + { + isFiat: false, + targetUnitFeed0: true, + fee: TWO_PERCENT_FEE, + feed1: uoaPerTargetFeed.address, // {UoA/target} + timeout1: bn('86400').toString(), // 24 hr + error1: fp('0.01').toString(), // 1% + } + ), + ]) - it('quantities in basket should start out near fp(1)', async () => { - const [erc20s, quantities] = await bh.quote(fp('1'), false, 2) - for (let i = 0; i < collateral.length; i++) { - expect(erc20s[i]).to.equal(tokens[i].address) - expect(quantities[i]).to.be.closeTo(fp('1'), fp('1').div(bn('1e5'))) - } - }) + for (let i = 0; i < collateral.length; i++) { + await assetRegistry.connect(owner).register(collateral[i].address) + await tokens[i].mint(addr1.address, amt) + await tokens[i].connect(addr1).approve(rToken.address, amt) + } - context('after 1 year', () => { - beforeEach(async () => { - await advanceTime(Number(bn('31535940'))) // 1 year - 60s - await uoaPerTokFeed.updateAnswer(bn('1e8')) - await uoaPerTargetFeed.updateAnswer(bn('1e8')) - await targetPerTokFeed.updateAnswer(bn('1e8')) + initialWeights = await Promise.all( + collateral.map((coll) => calcBasketWeight(coll, fp('1'))) + ) - await assetRegistry.refresh() + await bh.connect(owner).setPrimeBasket( + tokens.map((t) => t.address), + initialWeights + ) + await bh.connect(owner).refreshBasket() + await advanceTime(Number(config.warmupPeriod) + 1) + await rToken.connect(addr1).issue(amt) + expect(await rToken.totalSupply()).to.equal(amt) expect(await bh.status()).to.equal(CollateralStatus.SOUND) expect(await bh.fullyCollateralized()).to.equal(true) }) - it('oracle prices shouldnt change', async () => { + it('prices/pegPrices should be correct', async () => { for (let i = 0; i < 3; i++) { const [low, high, pegPrice] = await collateral[i].tryPrice() expect(low.add(high).div(2)).to.equal(fp('1')) @@ -271,147 +260,396 @@ describeP1(`Demurrage Collateral - P${IMPLEMENTATION}`, () => { expect(pegPrice).to.equal(fp('1')) }) - it('RToken quantities should decrease correctly per fee tier: [0.1%, 0.50%, 1%, 2%]', async () => { - const expected = [fp('0.999'), fp('0.995'), fp('0.99'), fp('0.98')] - + it('quantities in basket should start out near fp(1)', async () => { const [erc20s, quantities] = await bh.quote(fp('1'), false, 2) for (let i = 0; i < collateral.length; i++) { expect(erc20s[i]).to.equal(tokens[i].address) - expect(quantities[i]).to.be.closeTo(expected[i], expected[i].div(bn('1e6'))) + expect(quantities[i]).to.be.closeTo(fp('1'), fp('1').div(bn('1e5'))) } }) - it('refreshBasket() should not restore the RToken back genesis peg', async () => { - const [erc20s, quantities] = await bh.quote(fp('1'), false, 2) - await expect(bh.connect(owner).refreshBasket()).to.emit(bh, 'BasketSet') - const [newERC20s, newQuantities] = await bh.quote(fp('1'), false, 2) + context('after 1 year', () => { + beforeEach(async () => { + await advanceTime(Number(bn('31535940'))) // 1 year - 60s + await uoaPerTokFeed.updateAnswer(bn('1e8')) + await uoaPerTargetFeed.updateAnswer(bn('1e8')) + await targetPerTokFeed.updateAnswer(bn('1e8')) + await setOraclePrice(rsrAsset.address, bn('1e8')) + + await assetRegistry.refresh() + expect(await bh.status()).to.equal(CollateralStatus.SOUND) + expect(await bh.fullyCollateralized()).to.equal(true) + }) + + it('oracle prices shouldnt change', async () => { + for (let i = 0; i < 3; i++) { + const [low, high, pegPrice] = await collateral[i].tryPrice() + expect(low.add(high).div(2)).to.equal(fp('1')) + expect(pegPrice).to.equal(fp('1')) + } + const [low, high, pegPrice] = await collateral[3].tryPrice() + expect(low.add(high).div(2)).to.equal(fp('1.0001')) // asymmetry from multiplying oracles together + expect(pegPrice).to.equal(fp('1')) + }) - expect(await bh.status()).to.equal(CollateralStatus.SOUND) - expect(await bh.fullyCollateralized()).to.equal(true) - for (let i = 0; i < collateral.length; i++) { - expect(erc20s[i]).to.equal(newERC20s[i]) - expect(quantities[i]).to.be.gt(newQuantities[i]) - expect(quantities[i]).to.be.lt(newQuantities[i].add(fp('1e-6'))) - } + it('RToken quantities should decrease correctly per fee tier: [0.1%, 0.50%, 1%, 2%]', async () => { + const expected = [fp('0.999'), fp('0.995'), fp('0.99'), fp('0.98')] + + const [erc20s, quantities] = await bh.quote(fp('1'), false, 2) + for (let i = 0; i < collateral.length; i++) { + expect(erc20s[i]).to.equal(tokens[i].address) + expect(quantities[i]).to.be.closeTo(expected[i], expected[i].div(bn('1e6'))) + } + }) + + it('refreshBasket() should not restore the RToken to genesis peg', async () => { + const [erc20s, quantities] = await bh.quote(fp('1'), false, 2) + await expect(bh.connect(owner).refreshBasket()).to.emit(bh, 'BasketSet') + const [newERC20s, newQuantities] = await bh.quote(fp('1'), false, 2) + + expect(await bh.status()).to.equal(CollateralStatus.SOUND) + expect(await bh.fullyCollateralized()).to.equal(true) + for (let i = 0; i < collateral.length; i++) { + expect(erc20s[i]).to.equal(newERC20s[i]) + expect(quantities[i]).to.be.gt(newQuantities[i]) + expect(quantities[i]).to.be.lt(newQuantities[i].add(fp('1e-6'))) + } + }) + + it('setPrimeBasket() should not restore the RToken to genesis peg', async () => { + // First try refreshBasket() in isolation + const [erc20s, quantities] = await bh.quote(fp('1'), false, 2) + await bh.connect(owner).refreshBasket() + const [newERC20s, newQuantities] = await bh.quote(fp('1'), false, 2) + + expect(await bh.status()).to.equal(CollateralStatus.SOUND) + expect(await bh.fullyCollateralized()).to.equal(true) + for (let i = 0; i < collateral.length; i++) { + expect(erc20s[i]).to.equal(newERC20s[i]) + expect(quantities[i]).to.be.gt(newQuantities[i]) + expect(quantities[i]).to.be.lt(newQuantities[i].add(fp('1e-6'))) + } + + // Then try refreshBasket() after setPrimeBasket() + await bh.connect(owner).setPrimeBasket( + tokens.map((t) => t.address), + initialWeights + ) + await bh.connect(owner).refreshBasket() + const [newerERC20s, newerQuantities] = await bh.quote(fp('1'), false, 2) + + expect(await bh.status()).to.equal(CollateralStatus.SOUND) + expect(await bh.fullyCollateralized()).to.equal(true) + for (let i = 0; i < collateral.length; i++) { + expect(erc20s[i]).to.equal(newerERC20s[i]) + expect(quantities[i]).to.be.gt(newerQuantities[i]) + expect(quantities[i]).to.be.lt(newerQuantities[i].add(fp('1e-6'))) + } + }) + + it('should detect default and propagate through to prices/pegPrices correctly', async () => { + // 1. break uoaPerTokFeed + await uoaPerTokFeed.updateAnswer(bn('1e8').div(2)) + await assetRegistry.refresh() + + // token1 + let [low, high, pegPrice] = await collateral[0].tryPrice() + expect(low.add(high).div(2)).to.equal(fp('0.5')) + expect(pegPrice).to.equal(fp('1')) + expect(await collateral[0].status()).to.equal(CollateralStatus.SOUND) + + // token2 + ;[low, high, pegPrice] = await collateral[1].tryPrice() + expect(low.add(high).div(2)).to.equal(fp('0.5')) + expect(pegPrice).to.equal(fp('0.5')) + expect(await collateral[1].status()).to.equal(CollateralStatus.IFFY) + + // token3 + ;[low, high, pegPrice] = await collateral[2].tryPrice() + expect(low.add(high).div(2)).to.equal(fp('0.5')) + expect(pegPrice).to.equal(fp('0.5')) + expect(await collateral[2].status()).to.equal(CollateralStatus.IFFY) + + // token4 + ;[low, high, pegPrice] = await collateral[3].tryPrice() + expect(low.add(high).div(2)).to.equal(fp('1.0001')) + expect(pegPrice).to.equal(fp('1')) + expect(await collateral[3].status()).to.equal(CollateralStatus.SOUND) + + // 2. break uoaPerTargetFeed + await uoaPerTokFeed.updateAnswer(bn('1e8')) + await uoaPerTargetFeed.updateAnswer(bn('1e8').div(2)) + await assetRegistry.refresh() + + // token1 + ;[low, high, pegPrice] = await collateral[0].tryPrice() + expect(low.add(high).div(2)).to.equal(fp('1')) + expect(pegPrice).to.equal(fp('1')) + expect(await collateral[0].status()).to.equal(CollateralStatus.SOUND) + + // token2 + ;[low, high, pegPrice] = await collateral[1].tryPrice() + expect(low.add(high).div(2)).to.equal(fp('1')) + expect(pegPrice).to.equal(fp('1')) + expect(await collateral[1].status()).to.equal(CollateralStatus.SOUND) + + // token3 + ;[low, high, pegPrice] = await collateral[2].tryPrice() + expect(low.add(high).div(2)).to.equal(fp('1')) + expect(pegPrice).to.equal(fp('2')) + expect(await collateral[2].status()).to.equal(CollateralStatus.IFFY) + + // token4 + ;[low, high, pegPrice] = await collateral[3].tryPrice() + expect(low.add(high).div(2)).to.equal(fp('0.50005')) + expect(pegPrice).to.equal(fp('1')) + expect(await collateral[3].status()).to.equal(CollateralStatus.SOUND) + + // 3. break targetPerTokFeed + await uoaPerTargetFeed.updateAnswer(bn('1e8')) + await targetPerTokFeed.updateAnswer(bn('1e8').div(2)) + await assetRegistry.refresh() + + // token1 + ;[low, high, pegPrice] = await collateral[0].tryPrice() + expect(low.add(high).div(2)).to.equal(fp('1')) + expect(pegPrice).to.equal(fp('1')) + expect(await collateral[0].status()).to.equal(CollateralStatus.SOUND) + + // token2 + ;[low, high, pegPrice] = await collateral[1].tryPrice() + expect(low.add(high).div(2)).to.equal(fp('1')) + expect(pegPrice).to.equal(fp('1')) + expect(await collateral[1].status()).to.equal(CollateralStatus.SOUND) + + // token3 + ;[low, high, pegPrice] = await collateral[2].tryPrice() + expect(low.add(high).div(2)).to.equal(fp('1')) + expect(pegPrice).to.equal(fp('1')) + expect(await collateral[2].status()).to.equal(CollateralStatus.SOUND) + + // token4 + ;[low, high, pegPrice] = await collateral[3].tryPrice() + expect(low.add(high).div(2)).to.equal(fp('0.50005')) + expect(pegPrice).to.equal(fp('0.5')) + expect(await collateral[3].status()).to.equal(CollateralStatus.IFFY) + }) + + it('should open revenue auctions with asymmetric tokens and minted RToken', async () => { + // Forward balances + const all = tokens.map((t) => t.address) + all.push(rToken.address) + await backingManager.forwardRevenue(all) + + // 0th token will have no balance because smallest DMR; rest should have some + for (let i = 1; i < tokens.length; i++) { + expect(await tokens[i].balanceOf(rTokenTrader.address)).to.be.gt(0) + expect(await tokens[i].balanceOf(rsrTrader.address)).to.be.gt(0) + } + expect(await rToken.balanceOf(rTokenTrader.address)).to.be.gt(0) + expect(await rToken.balanceOf(rsrTrader.address)).to.be.gt(0) + + // RTokenTrader should be able to open auctions for tokens[1], tokens[2], and tokens[3], as well as distribute RToken + await rTokenTrader.manageTokens( + [tokens[1].address, tokens[2].address, tokens[3].address, rToken.address], + [0, 0, 0, 0] + ) + expect(await rTokenTrader.tradesOpen()).to.equal(3) + await expect(rTokenTrader.manageTokens([tokens[0].address], [0])).to.be.revertedWith( + '0 balance' + ) + + // RSRTrader should be able to open auctions for tokens[1], tokens[2], and tokens[3], and RToken + await rsrTrader.manageTokens( + [tokens[1].address, tokens[2].address, tokens[3].address, rToken.address], + [0, 0, 0, 0] + ) + expect(await rsrTrader.tradesOpen()).to.equal(4) + await expect(rsrTrader.manageTokens([tokens[0].address], [0])).to.be.revertedWith( + '0 balance' + ) + }) }) + }) - it('setPrimeBasket() should not restore the RToken to genesis peg', async () => { - // First try refreshBasket() in isolation - const [erc20s, quantities] = await bh.quote(fp('1'), false, 2) - await bh.connect(owner).refreshBasket() - const [newERC20s, newQuantities] = await bh.quote(fp('1'), false, 2) + context('Symmetric DMRs', () => { + beforeEach(async () => { + const DemurrageCollateralFactory: ContractFactory = await ethers.getContractFactory( + 'DemurrageCollateral' + ) + const ERC20Factory: ContractFactory = await ethers.getContractFactory('ERC20Mock') + const ChainlinkFactory: ContractFactory = await ethers.getContractFactory( + 'MockV3Aggregator' + ) + + /***** Replace the original 4 tokens with demurrage collateral with the same DMR rate ***********/ + // The 4 versions of DemurrageCollateral: + // 1. isFiat = false: {UoA/tok} (no default detection) + // 2. isFiat = true: {UoA/tok} (/w default detection) + // 3. targetUnitFeed0 = false: {UoA/tok} and {UoA/target} (/w default detection) + // 4. targetUnitFeed0 = true: {target/tok} and {UoA/target} (/w default detection) + + tokens = ( + await Promise.all([ + ERC20Factory.deploy('NAME1', 'TKN1'), + ERC20Factory.deploy('NAME2', 'TKN2'), + ERC20Factory.deploy('NAME3', 'TKN3'), + ERC20Factory.deploy('NAME4', 'TKN4'), + ]) + ) + + uoaPerTokFeed = await ChainlinkFactory.deploy(8, bn('1e8')) + uoaPerTargetFeed = await ChainlinkFactory.deploy(8, bn('1e8')) + targetPerTokFeed = await ChainlinkFactory.deploy(8, bn('1e8')) + + collateral = await Promise.all([ + await DemurrageCollateralFactory.deploy( + { + erc20: tokens[0].address, + targetName: ethers.utils.formatBytes32String('DMR100USD'), + priceTimeout: bn('604800'), + chainlinkFeed: uoaPerTokFeed.address, // {UoA/tok} + oracleError: fp('0.01').toString(), // 1% + oracleTimeout: bn('86400').toString(), // 24 hr + maxTradeVolume: fp('1e6').toString(), // $1m, + defaultThreshold: fp('0.01'), + delayUntilDefault: bn('86400'), + }, + { + isFiat: false, + targetUnitFeed0: false, + fee: ONE_PERCENT_FEE, + feed1: ZERO_ADDRESS, + timeout1: bn('0'), + error1: bn('0'), + } + ), + await DemurrageCollateralFactory.deploy( + { + erc20: tokens[1].address, + targetName: ethers.utils.formatBytes32String('DMR100EUR'), + priceTimeout: bn('604800'), + chainlinkFeed: uoaPerTokFeed.address, // {UoA/tok} + oracleError: fp('0.01').toString(), // 1% + oracleTimeout: bn('86400').toString(), // 24 hr + maxTradeVolume: fp('1e6').toString(), // $1m, + defaultThreshold: fp('0.01'), + delayUntilDefault: bn('86400'), + }, + { + isFiat: true, + targetUnitFeed0: false, + fee: ONE_PERCENT_FEE, + feed1: ZERO_ADDRESS, + timeout1: bn('0'), + error1: bn('0'), + } + ), + await DemurrageCollateralFactory.deploy( + { + erc20: tokens[2].address, + targetName: ethers.utils.formatBytes32String('DMR100XAU'), + priceTimeout: bn('604800'), + chainlinkFeed: uoaPerTokFeed.address, // {UoA/tok} + oracleError: fp('0.01').toString(), // 1% + oracleTimeout: bn('86400').toString(), // 24 hr + maxTradeVolume: fp('1e6').toString(), // $1m, + defaultThreshold: fp('0.01'), + delayUntilDefault: bn('86400'), + }, + { + isFiat: false, + targetUnitFeed0: false, + fee: ONE_PERCENT_FEE, + feed1: uoaPerTargetFeed.address, // {UoA/target} + timeout1: bn('86400').toString(), // 24 hr + error1: fp('0.01').toString(), // 1% + } + ), + await DemurrageCollateralFactory.deploy( + { + erc20: tokens[3].address, + targetName: ethers.utils.formatBytes32String('DMR100SPY'), + priceTimeout: bn('604800'), + chainlinkFeed: targetPerTokFeed.address, // {target/tok} + oracleError: fp('0.01').toString(), // 1% + oracleTimeout: bn('86400').toString(), // 24 hr + maxTradeVolume: fp('1e6').toString(), // $1m, + defaultThreshold: fp('0.01'), + delayUntilDefault: bn('86400'), + }, + { + isFiat: false, + targetUnitFeed0: true, + fee: ONE_PERCENT_FEE, + feed1: uoaPerTargetFeed.address, // {UoA/target} + timeout1: bn('86400').toString(), // 24 hr + error1: fp('0.01').toString(), // 1% + } + ), + ]) - expect(await bh.status()).to.equal(CollateralStatus.SOUND) - expect(await bh.fullyCollateralized()).to.equal(true) for (let i = 0; i < collateral.length; i++) { - expect(erc20s[i]).to.equal(newERC20s[i]) - expect(quantities[i]).to.be.gt(newQuantities[i]) - expect(quantities[i]).to.be.lt(newQuantities[i].add(fp('1e-6'))) + await assetRegistry.connect(owner).register(collateral[i].address) + await tokens[i].mint(addr1.address, amt) + await tokens[i].connect(addr1).approve(rToken.address, amt) } - // Then try refreshBasket() after setPrimeBasket() + initialWeights = await Promise.all( + collateral.map((coll) => calcBasketWeight(coll, fp('1'))) + ) + await bh.connect(owner).setPrimeBasket( tokens.map((t) => t.address), initialWeights ) await bh.connect(owner).refreshBasket() - const [newerERC20s, newerQuantities] = await bh.quote(fp('1'), false, 2) - + await advanceTime(Number(config.warmupPeriod) + 1) + await rToken.connect(addr1).issue(amt) + expect(await rToken.totalSupply()).to.equal(amt) expect(await bh.status()).to.equal(CollateralStatus.SOUND) expect(await bh.fullyCollateralized()).to.equal(true) - for (let i = 0; i < collateral.length; i++) { - expect(erc20s[i]).to.equal(newerERC20s[i]) - expect(quantities[i]).to.be.gt(newerQuantities[i]) - expect(quantities[i]).to.be.lt(newerQuantities[i].add(fp('1e-6'))) - } }) - it('should detect default and propagate through to prices/pegPrices correctly', async () => { - // 1. break uoaPerTokFeed - await uoaPerTokFeed.updateAnswer(bn('1e8').div(2)) - await assetRegistry.refresh() - - // token1 - let [low, high, pegPrice] = await collateral[0].tryPrice() - expect(low.add(high).div(2)).to.equal(fp('0.5')) - expect(pegPrice).to.equal(fp('1')) - expect(await collateral[0].status()).to.equal(CollateralStatus.SOUND) - - // token2 - ;[low, high, pegPrice] = await collateral[1].tryPrice() - expect(low.add(high).div(2)).to.equal(fp('0.5')) - expect(pegPrice).to.equal(fp('0.5')) - expect(await collateral[1].status()).to.equal(CollateralStatus.IFFY) - - // token3 - ;[low, high, pegPrice] = await collateral[2].tryPrice() - expect(low.add(high).div(2)).to.equal(fp('0.5')) - expect(pegPrice).to.equal(fp('0.5')) - expect(await collateral[2].status()).to.equal(CollateralStatus.IFFY) - - // token4 - ;[low, high, pegPrice] = await collateral[3].tryPrice() - expect(low.add(high).div(2)).to.equal(fp('1.0001')) - expect(pegPrice).to.equal(fp('1')) - expect(await collateral[3].status()).to.equal(CollateralStatus.SOUND) - - // 2. break uoaPerTargetFeed - await uoaPerTokFeed.updateAnswer(bn('1e8')) - await uoaPerTargetFeed.updateAnswer(bn('1e8').div(2)) - await assetRegistry.refresh() - - // token1 - ;[low, high, pegPrice] = await collateral[0].tryPrice() - expect(low.add(high).div(2)).to.equal(fp('1')) - expect(pegPrice).to.equal(fp('1')) - expect(await collateral[0].status()).to.equal(CollateralStatus.SOUND) - - // token2 - ;[low, high, pegPrice] = await collateral[1].tryPrice() - expect(low.add(high).div(2)).to.equal(fp('1')) - expect(pegPrice).to.equal(fp('1')) - expect(await collateral[1].status()).to.equal(CollateralStatus.SOUND) - - // token3 - ;[low, high, pegPrice] = await collateral[2].tryPrice() - expect(low.add(high).div(2)).to.equal(fp('1')) - expect(pegPrice).to.equal(fp('2')) - expect(await collateral[2].status()).to.equal(CollateralStatus.IFFY) - - // token4 - ;[low, high, pegPrice] = await collateral[3].tryPrice() - expect(low.add(high).div(2)).to.equal(fp('0.50005')) - expect(pegPrice).to.equal(fp('1')) - expect(await collateral[3].status()).to.equal(CollateralStatus.SOUND) - - // 3. break targetPerTokFeed - await uoaPerTargetFeed.updateAnswer(bn('1e8')) - await targetPerTokFeed.updateAnswer(bn('1e8').div(2)) - await assetRegistry.refresh() - - // token1 - ;[low, high, pegPrice] = await collateral[0].tryPrice() - expect(low.add(high).div(2)).to.equal(fp('1')) - expect(pegPrice).to.equal(fp('1')) - expect(await collateral[0].status()).to.equal(CollateralStatus.SOUND) - - // token2 - ;[low, high, pegPrice] = await collateral[1].tryPrice() - expect(low.add(high).div(2)).to.equal(fp('1')) - expect(pegPrice).to.equal(fp('1')) - expect(await collateral[1].status()).to.equal(CollateralStatus.SOUND) + context('after 1 year', () => { + beforeEach(async () => { + await advanceTime(Number(bn('31535940'))) // 1 year - 60s + await uoaPerTokFeed.updateAnswer(bn('1e8')) + await uoaPerTargetFeed.updateAnswer(bn('1e8')) + await targetPerTokFeed.updateAnswer(bn('1e8')) + await setOraclePrice(rsrAsset.address, bn('1e8')) + + await assetRegistry.refresh() + expect(await bh.status()).to.equal(CollateralStatus.SOUND) + expect(await bh.fullyCollateralized()).to.equal(true) + }) + + it('should open revenue auctions with minted RToken', async () => { + // Forward balances + const all = tokens.map((t) => t.address) + all.push(rToken.address) + await backingManager.forwardRevenue(all) + + // No tokens should have balances at traders + for (let i = 0; i < tokens.length; i++) { + expect(await tokens[i].balanceOf(rTokenTrader.address)).to.equal(0) + expect(await tokens[i].balanceOf(rsrTrader.address)).to.equal(0) + } - // token3 - ;[low, high, pegPrice] = await collateral[2].tryPrice() - expect(low.add(high).div(2)).to.equal(fp('1')) - expect(pegPrice).to.equal(fp('1')) - expect(await collateral[2].status()).to.equal(CollateralStatus.SOUND) + // RTokenTrader should distribute its RToken + await rTokenTrader.manageTokens([rToken.address], [0]) + expect(await rTokenTrader.tradesOpen()).to.equal(0) + await expect(rTokenTrader.manageTokens([tokens[3].address], [0])).to.be.revertedWith( + '0 balance' + ) - // token4 - ;[low, high, pegPrice] = await collateral[3].tryPrice() - expect(low.add(high).div(2)).to.equal(fp('0.50005')) - expect(pegPrice).to.equal(fp('0.5')) - expect(await collateral[3].status()).to.equal(CollateralStatus.IFFY) + // RSRTrader should be able to open auctions for RToken + await rsrTrader.manageTokens([rToken.address], [0]) + expect(await rsrTrader.tradesOpen()).to.equal(1) + await expect(rsrTrader.manageTokens([tokens[3].address], [0])).to.be.revertedWith( + '0 balance' + ) + }) }) }) }) From 106ebddc8f184695f87a2a5857abb91e5ca1f3a8 Mon Sep 17 00:00:00 2001 From: Taylor Brent Date: Tue, 22 Oct 2024 20:13:08 -0400 Subject: [PATCH 32/40] fix tests --- .../dtf/PAXGCollateralTestSuite.test.ts | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/test/plugins/individual-collateral/dtf/PAXGCollateralTestSuite.test.ts b/test/plugins/individual-collateral/dtf/PAXGCollateralTestSuite.test.ts index f6543bcd9..c5666d164 100644 --- a/test/plugins/individual-collateral/dtf/PAXGCollateralTestSuite.test.ts +++ b/test/plugins/individual-collateral/dtf/PAXGCollateralTestSuite.test.ts @@ -133,12 +133,7 @@ const increaseRefPerTok = async () => {} const getExpectedPrice = async (ctx: CollateralFixtureContext): Promise => { const clData = await ctx.chainlinkFeed.latestRoundData() const clDecimals = await ctx.chainlinkFeed.decimals() - - const refPerTok = await ctx.collateral.refPerTok() - return clData.answer - .mul(bn(10).pow(18 - clDecimals)) - .mul(refPerTok) - .div(fp('1')) + return clData.answer.mul(bn(10).pow(18 - clDecimals)) } /* From f29033021dd3e4bf28250334a0db7b0fba0557aa Mon Sep 17 00:00:00 2001 From: Taylor Brent Date: Tue, 22 Oct 2024 20:42:57 -0400 Subject: [PATCH 33/40] docs --- docs/demurrage-collateral.md | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/docs/demurrage-collateral.md b/docs/demurrage-collateral.md index ccb9a7ebd..952f6a0b7 100644 --- a/docs/demurrage-collateral.md +++ b/docs/demurrage-collateral.md @@ -59,16 +59,12 @@ This also means there can be multiple demurrage collateral for a single token. W Prime basket weights are in units of January 1st 2024 collateral, not today's collateral. It doesn't matter if the collateral wasn't around in Jan 2024 -- when setting the basket weights the setter must take into account how much demurrage has occurred since January 1st 2024. -For example, say an asset has had 2% total demurrage since January 1st 2024 and you want to (on today's date) create a basket of that is worth $1: the correct basket weight to provide to `setPrimeBasket()` would be `1 / (1 - 0.02) = ~1.0204`. - -To calculate total demurrage: +This is identical to the calculation for the `refPerTok()` function in the [DemurrageCollateral.sol](../contracts/plugins/assets/DemurrageCollateral.sol) contract, but calculating for an arbitrary timestamp. ``` -1 - (1 - per_second_fee) ^ (seconds) +weight = 1 / (1 - fee) ^ seconds; ``` -Switching between tiers requires calculating total demurrage for both tiers in order to ensure the basket change does not accidentally appreciate or depreciate the RToken. - ### Implementation [DemurrageCollateral.sol](../contracts/plugins/assets/DemurrageCollateral.sol) implements a generalized demurrage collateral plugin that should support almost all use-cases From 61dc3d2f1a0cdd73e9dda5bc44b9bb026984e7c7 Mon Sep 17 00:00:00 2001 From: Taylor Brent Date: Tue, 22 Oct 2024 20:45:56 -0400 Subject: [PATCH 34/40] new base addresses --- scripts/addresses/8453-tmp-assets-collateral.json | 10 +++++----- .../base-3.4.0/8453-tmp-assets-collateral.json | 8 ++++++-- 2 files changed, 11 insertions(+), 7 deletions(-) diff --git a/scripts/addresses/8453-tmp-assets-collateral.json b/scripts/addresses/8453-tmp-assets-collateral.json index 9b5fcc8af..b761e0e44 100644 --- a/scripts/addresses/8453-tmp-assets-collateral.json +++ b/scripts/addresses/8453-tmp-assets-collateral.json @@ -12,8 +12,8 @@ "saBasUSDC": "0xC19f5d60e2Aca1174f3D5Fe189f0A69afaB76f50", "cUSDCv3": "0xf7a9D27c3B60c78c6F6e2c2d6ED6E8B94b352461", "wstETH": "0x8b4374005291B8FCD14C4E947604b2FB3C660A73", - "EURC": "0x7321485aA1D0439296B882bBc85Eb0BD350F8381", - "cbBTC": "0x06f7D10f5842fc5816DF9A9DD65f84481B1490E3" + "DMR100cbBTC": "0x27Bb9158A6727aBdcaF6A1B983FFCA94E41d5616", + "DMR100EURC": "0x3a55cEE81eaDaB7F64430ff0148F547baFD993fb" }, "erc20s": { "COMP": "0x9e1028F5F1D5eDE59748FFceE5532509976840E0", @@ -26,7 +26,7 @@ "STG": "0xE3B53AF74a4BF62Ae5511055290838050bf764Df", "cUSDCv3": "0x53f1Df4E5591Ae35Bf738742981669c3767241FA", "wstETH": "0xc1CBa3fCea344f92D9239c08C0568f6F2F0ee452", - "EURC": "0x60a3E35Cc302bFA44Cb288Bc5a4F316Fdb1adb42", - "cbBTC": "0xcbB7C0000aB88B473b1f5aFd9ef808440eed33Bf" + "DMR100cbBTC": "0xcbB7C0000aB88B473b1f5aFd9ef808440eed33Bf", + "DMR100EURC": "0x60a3E35Cc302bFA44Cb288Bc5a4F316Fdb1adb42" } -} \ No newline at end of file +} diff --git a/scripts/addresses/base-3.4.0/8453-tmp-assets-collateral.json b/scripts/addresses/base-3.4.0/8453-tmp-assets-collateral.json index 4602fbd2d..b761e0e44 100644 --- a/scripts/addresses/base-3.4.0/8453-tmp-assets-collateral.json +++ b/scripts/addresses/base-3.4.0/8453-tmp-assets-collateral.json @@ -11,7 +11,9 @@ "cbETH": "0x851B461a9744f4c9E996C03072cAB6f44Fa04d0D", "saBasUSDC": "0xC19f5d60e2Aca1174f3D5Fe189f0A69afaB76f50", "cUSDCv3": "0xf7a9D27c3B60c78c6F6e2c2d6ED6E8B94b352461", - "wstETH": "0x8b4374005291B8FCD14C4E947604b2FB3C660A73" + "wstETH": "0x8b4374005291B8FCD14C4E947604b2FB3C660A73", + "DMR100cbBTC": "0x27Bb9158A6727aBdcaF6A1B983FFCA94E41d5616", + "DMR100EURC": "0x3a55cEE81eaDaB7F64430ff0148F547baFD993fb" }, "erc20s": { "COMP": "0x9e1028F5F1D5eDE59748FFceE5532509976840E0", @@ -23,6 +25,8 @@ "saBasUSDC": "0x6F6f81e5E66f503184f2202D83a79650c3285759", "STG": "0xE3B53AF74a4BF62Ae5511055290838050bf764Df", "cUSDCv3": "0x53f1Df4E5591Ae35Bf738742981669c3767241FA", - "wstETH": "0xc1CBa3fCea344f92D9239c08C0568f6F2F0ee452" + "wstETH": "0xc1CBa3fCea344f92D9239c08C0568f6F2F0ee452", + "DMR100cbBTC": "0xcbB7C0000aB88B473b1f5aFd9ef808440eed33Bf", + "DMR100EURC": "0x60a3E35Cc302bFA44Cb288Bc5a4F316Fdb1adb42" } } From 02ec0097050e549225b1dd4dbb0b78d0f81d4518 Mon Sep 17 00:00:00 2001 From: Taylor Brent Date: Wed, 23 Oct 2024 17:53:44 -0400 Subject: [PATCH 35/40] verificaton scripts + nits --- contracts/plugins/assets/DemurrageCollateral.sol | 4 ++-- docs/demurrage-collateral.md | 2 ++ scripts/verification/collateral-plugins/verify_arb_100.ts | 2 +- scripts/verification/collateral-plugins/verify_cbbtc_100.ts | 4 ++-- scripts/verification/collateral-plugins/verify_eurc_100.ts | 4 ++-- scripts/verification/collateral-plugins/verify_paxg_100.ts | 4 ++-- 6 files changed, 11 insertions(+), 9 deletions(-) diff --git a/contracts/plugins/assets/DemurrageCollateral.sol b/contracts/plugins/assets/DemurrageCollateral.sol index 8745de362..74e4beceb 100644 --- a/contracts/plugins/assets/DemurrageCollateral.sol +++ b/contracts/plugins/assets/DemurrageCollateral.sol @@ -38,7 +38,7 @@ contract DemurrageCollateral is FiatCollateral { using FixLib for uint192; using OracleLib for AggregatorV3Interface; - uint48 public constant t0 = 1704067200; // {s} Jan 1st 2024 00:00:00 GMT+0000 + uint48 public constant T0 = 1704067200; // {s} Jan 1st 2024 00:00:00 GMT+0000 bool internal immutable isFiat; bool internal immutable targetUnitFeed0; // if true: feed0 is {target/tok} @@ -141,7 +141,7 @@ contract DemurrageCollateral is FiatCollateral { function refPerTok() public view override returns (uint192) { // Monotonically increasing due to target unit (and reference unit) deflation - uint192 denominator = FIX_ONE.minus(fee).powu(uint48(block.timestamp - t0)); + uint192 denominator = FIX_ONE.minus(fee).powu(uint48(block.timestamp - T0)); if (denominator == 0) return FIX_MAX; // TODO // up-only diff --git a/docs/demurrage-collateral.md b/docs/demurrage-collateral.md index 952f6a0b7..e10213c25 100644 --- a/docs/demurrage-collateral.md +++ b/docs/demurrage-collateral.md @@ -65,6 +65,8 @@ This is identical to the calculation for the `refPerTok()` function in the [Demu weight = 1 / (1 - fee) ^ seconds; ``` +`fee()` available on DemurrageCollateral contract + ### Implementation [DemurrageCollateral.sol](../contracts/plugins/assets/DemurrageCollateral.sol) implements a generalized demurrage collateral plugin that should support almost all use-cases diff --git a/scripts/verification/collateral-plugins/verify_arb_100.ts b/scripts/verification/collateral-plugins/verify_arb_100.ts index 44b21a904..0c4a4433f 100644 --- a/scripts/verification/collateral-plugins/verify_arb_100.ts +++ b/scripts/verification/collateral-plugins/verify_arb_100.ts @@ -49,7 +49,7 @@ async function main() { await verifyContract( chainId, - assetCollDeployments.collateral.ARB, + assetCollDeployments.collateral.DMR100ARB, [ { erc20: networkConfig[chainId].tokens.ARB, diff --git a/scripts/verification/collateral-plugins/verify_cbbtc_100.ts b/scripts/verification/collateral-plugins/verify_cbbtc_100.ts index 112a85a28..c1809ce3f 100644 --- a/scripts/verification/collateral-plugins/verify_cbbtc_100.ts +++ b/scripts/verification/collateral-plugins/verify_cbbtc_100.ts @@ -46,12 +46,12 @@ async function main() { const collateral = await hre.ethers.getContractAt( 'ICollateral', - assetCollDeployments.collateral.cbBTC! + assetCollDeployments.collateral.DMR100cbBTC! ) await verifyContract( chainId, - assetCollDeployments.collateral.cbBTC, + assetCollDeployments.collateral.DMR100cbBTC, [ { erc20: networkConfig[chainId].tokens.cbBTC, diff --git a/scripts/verification/collateral-plugins/verify_eurc_100.ts b/scripts/verification/collateral-plugins/verify_eurc_100.ts index 759cce433..5886dd3e3 100644 --- a/scripts/verification/collateral-plugins/verify_eurc_100.ts +++ b/scripts/verification/collateral-plugins/verify_eurc_100.ts @@ -46,12 +46,12 @@ async function main() { const collateral = await hre.ethers.getContractAt( 'ICollateral', - assetCollDeployments.collateral.EURC! + assetCollDeployments.collateral.DMR100EURC! ) await verifyContract( chainId, - assetCollDeployments.collateral.EURC, + assetCollDeployments.collateral.DMR100EURC, [ { erc20: networkConfig[chainId].tokens.EURC, diff --git a/scripts/verification/collateral-plugins/verify_paxg_100.ts b/scripts/verification/collateral-plugins/verify_paxg_100.ts index f99deaba2..cbf0a250d 100644 --- a/scripts/verification/collateral-plugins/verify_paxg_100.ts +++ b/scripts/verification/collateral-plugins/verify_paxg_100.ts @@ -47,12 +47,12 @@ async function main() { const collateral = await hre.ethers.getContractAt( 'ICollateral', - assetCollDeployments.collateral.PAXG! + assetCollDeployments.collateral.DMR100PAXG! ) await verifyContract( chainId, - assetCollDeployments.collateral.PAXG, + assetCollDeployments.collateral.DMR100PAXG, [ { erc20: networkConfig[chainId].tokens.PAXG, From 29f2191c447e1292b7624d81cbf7b5e85d7fe437 Mon Sep 17 00:00:00 2001 From: Taylor Brent Date: Fri, 25 Oct 2024 16:35:07 -0400 Subject: [PATCH 36/40] fix test --- test/scenario/DemurrageCollateral.test.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/scenario/DemurrageCollateral.test.ts b/test/scenario/DemurrageCollateral.test.ts index 93e428373..f5517836f 100644 --- a/test/scenario/DemurrageCollateral.test.ts +++ b/test/scenario/DemurrageCollateral.test.ts @@ -64,7 +64,7 @@ describeP1(`Demurrage Collateral - P${IMPLEMENTATION}`, () => { coll: DemurrageCollateral, decayedAmt: BigNumber ): Promise => { - const elapsed = (await getLatestBlockTimestamp()) - (await coll.t0()) + const elapsed = (await getLatestBlockTimestamp()) - (await coll.T0()) const decayFn = makeDecayFn(await coll.fee()) return fp('1e18').div(decayFn(decayedAmt, elapsed)) } From 9a1922d0edea61371c7d5bd5bffc7a6467ca4993 Mon Sep 17 00:00:00 2001 From: Taylor Brent Date: Tue, 5 Nov 2024 06:56:54 +0900 Subject: [PATCH 37/40] post merge --- common/configuration.ts | 3 --- 1 file changed, 3 deletions(-) diff --git a/common/configuration.ts b/common/configuration.ts index c5c7dbf74..9330cef6c 100644 --- a/common/configuration.ts +++ b/common/configuration.ts @@ -532,13 +532,10 @@ export const networkConfig: { [key: string]: INetworkConfig } = { sUSDbC: '0x4c80e24119cfb836cdf0a6b53dc23f04f7e652ca', wstETH: '0xc1CBa3fCea344f92D9239c08C0568f6F2F0ee452', STG: '0xE3B53AF74a4BF62Ae5511055290838050bf764Df', -<<<<<<< HEAD cbBTC: '0xcbB7C0000aB88B473b1f5aFd9ef808440eed33Bf', EURC: '0x60a3E35Cc302bFA44Cb288Bc5a4F316Fdb1adb42', -======= eUSD: '0xCfA3Ef56d303AE4fAabA0592388F19d7C3399FB4', meUSD: '0xbb819D845b573B5D7C538F5b85057160cfb5f313', ->>>>>>> 4.0.0 }, chainlinkFeeds: { DAI: '0x591e79239a7d679378ec8c847e5038150364c78f', // 0.3%, 24hr From 2259659d0b2ad5007192d1a5e6f4bd8ed986849c Mon Sep 17 00:00:00 2001 From: Taylor Brent Date: Tue, 5 Nov 2024 07:07:41 +0900 Subject: [PATCH 38/40] fix comet suite --- .../individual-collateral/compoundv3/CometTestSuite.test.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/test/plugins/individual-collateral/compoundv3/CometTestSuite.test.ts b/test/plugins/individual-collateral/compoundv3/CometTestSuite.test.ts index c0062e93d..9618459e3 100644 --- a/test/plugins/individual-collateral/compoundv3/CometTestSuite.test.ts +++ b/test/plugins/individual-collateral/compoundv3/CometTestSuite.test.ts @@ -383,6 +383,7 @@ allTests.forEach((curr: CTokenV3Enumeration) => { itChecksTargetPerRefDefaultUp: it, itChecksRefPerTokDefault: it.skip, // implemented in this file itChecksPriceChanges: it, + itChecksPriceChangesRefPerTok: it, itChecksNonZeroDefaultThreshold: it, itHasRevenueHiding: it.skip, // implemented in this file itIsPricedByPeg: true, From 9dd8d6d4b09609210ce97fa125085ffd5ced1d4f Mon Sep 17 00:00:00 2001 From: Taylor Brent Date: Tue, 5 Nov 2024 07:11:46 +0900 Subject: [PATCH 39/40] remove from dep scripts --- scripts/deploy.ts | 6 ++---- scripts/verify_etherscan.ts | 6 ++---- 2 files changed, 4 insertions(+), 8 deletions(-) diff --git a/scripts/deploy.ts b/scripts/deploy.ts index 6984d76b9..7cb13796d 100644 --- a/scripts/deploy.ts +++ b/scripts/deploy.ts @@ -95,8 +95,7 @@ async function main() { 'phase2-assets/collaterals/deploy_USDe.ts', 'phase2-assets/assets/deploy_crv.ts', 'phase2-assets/assets/deploy_cvx.ts', - 'phase2-assets/collaterals/deploy_pyusd.ts', - 'phase2-assets/collaterals/deploy_paxg_100.ts' + 'phase2-assets/collaterals/deploy_pyusd.ts' ) } else if (chainId == '8453' || chainId == '84531') { // Base L2 chains @@ -125,8 +124,7 @@ async function main() { 'phase2-assets/collaterals/deploy_convex_crvusd_usdc_collateral.ts', 'phase2-assets/collaterals/deploy_convex_crvusd_usdt_collateral.ts', 'phase2-assets/collaterals/deploy_usdm.ts', - 'phase2-assets/assets/deploy_arb.ts', - 'phase2-assets/assets/deploy_arb_100.ts' + 'phase2-assets/assets/deploy_arb.ts' ) } diff --git a/scripts/verify_etherscan.ts b/scripts/verify_etherscan.ts index d7ff0b157..812374ca1 100644 --- a/scripts/verify_etherscan.ts +++ b/scripts/verify_etherscan.ts @@ -81,8 +81,7 @@ async function main() { 'collateral-plugins/verify_ethx.ts', 'collateral-plugins/verify_apxeth.ts', 'collateral-plugins/verify_USDe.ts', - 'collateral-plugins/verify_pyusd_100.ts', - 'collateral-plugins/verify_paxg_100.ts' + 'collateral-plugins/verify_pyusd.ts' ) } else if (chainId == '8453' || chainId == '84531') { // Base L2 chains @@ -102,8 +101,7 @@ async function main() { 'collateral-plugins/verify_cusdtv3.ts', 'collateral-plugins/verify_convex_crvusd_usdc.ts', 'collateral-plugins/verify_convex_crvusd_usdt.ts', - 'collateral-plugins/verify_usdm.ts', - 'collateral-plugins/verify_arb_100.ts' + 'collateral-plugins/verify_usdm.ts' ) } From 839cf60423e965f1e07dd4a2354564adb86d102d Mon Sep 17 00:00:00 2001 From: Taylor Brent Date: Tue, 5 Nov 2024 07:13:31 +0900 Subject: [PATCH 40/40] nits --- scripts/addresses/8453-tmp-assets-collateral.json | 2 +- .../addresses/base-3.4.0/8453-tmp-assets-collateral.json | 8 ++------ 2 files changed, 3 insertions(+), 7 deletions(-) diff --git a/scripts/addresses/8453-tmp-assets-collateral.json b/scripts/addresses/8453-tmp-assets-collateral.json index f0d7f5c03..06257a8a2 100644 --- a/scripts/addresses/8453-tmp-assets-collateral.json +++ b/scripts/addresses/8453-tmp-assets-collateral.json @@ -27,4 +27,4 @@ "wstETH": "0xc1CBa3fCea344f92D9239c08C0568f6F2F0ee452", "meUSD": "0xbb819D845b573B5D7C538F5b85057160cfb5f313" } -} +} \ No newline at end of file diff --git a/scripts/addresses/base-3.4.0/8453-tmp-assets-collateral.json b/scripts/addresses/base-3.4.0/8453-tmp-assets-collateral.json index b761e0e44..4602fbd2d 100644 --- a/scripts/addresses/base-3.4.0/8453-tmp-assets-collateral.json +++ b/scripts/addresses/base-3.4.0/8453-tmp-assets-collateral.json @@ -11,9 +11,7 @@ "cbETH": "0x851B461a9744f4c9E996C03072cAB6f44Fa04d0D", "saBasUSDC": "0xC19f5d60e2Aca1174f3D5Fe189f0A69afaB76f50", "cUSDCv3": "0xf7a9D27c3B60c78c6F6e2c2d6ED6E8B94b352461", - "wstETH": "0x8b4374005291B8FCD14C4E947604b2FB3C660A73", - "DMR100cbBTC": "0x27Bb9158A6727aBdcaF6A1B983FFCA94E41d5616", - "DMR100EURC": "0x3a55cEE81eaDaB7F64430ff0148F547baFD993fb" + "wstETH": "0x8b4374005291B8FCD14C4E947604b2FB3C660A73" }, "erc20s": { "COMP": "0x9e1028F5F1D5eDE59748FFceE5532509976840E0", @@ -25,8 +23,6 @@ "saBasUSDC": "0x6F6f81e5E66f503184f2202D83a79650c3285759", "STG": "0xE3B53AF74a4BF62Ae5511055290838050bf764Df", "cUSDCv3": "0x53f1Df4E5591Ae35Bf738742981669c3767241FA", - "wstETH": "0xc1CBa3fCea344f92D9239c08C0568f6F2F0ee452", - "DMR100cbBTC": "0xcbB7C0000aB88B473b1f5aFd9ef808440eed33Bf", - "DMR100EURC": "0x60a3E35Cc302bFA44Cb288Bc5a4F316Fdb1adb42" + "wstETH": "0xc1CBa3fCea344f92D9239c08C0568f6F2F0ee452" } }