From 6c2c8142075be4fec37e768c3f0f61c4ee0e0e38 Mon Sep 17 00:00:00 2001 From: Lecky Date: Mon, 11 Oct 2021 17:49:27 +1100 Subject: [PATCH 001/133] created DynamicFee library - created libraries folder - moving SafeDecimalMath into libraries --- contracts/BaseDebtCache.sol | 2 +- contracts/BaseRewardEscrowV2.sol | 2 +- contracts/Collateral.sol | 2 +- contracts/CollateralManager.sol | 2 +- contracts/CollateralManagerState.sol | 2 +- contracts/CollateralUtil.sol | 2 +- contracts/DebtCache.sol | 2 +- contracts/Depot.sol | 2 +- contracts/EtherWrapper.sol | 2 +- contracts/ExchangeRates.sol | 2 +- contracts/Exchanger.sol | 2 +- contracts/ExternStateToken.sol | 2 +- contracts/FeePool.sol | 2 +- contracts/FeePoolState.sol | 2 +- contracts/Issuer.sol | 2 +- contracts/Liquidations.sol | 2 +- contracts/Math.sol | 2 +- contracts/PurgeableSynth.sol | 2 +- contracts/RewardEscrow.sol | 2 +- contracts/RewardsDistribution.sol | 2 +- contracts/SupplySchedule.sol | 2 +- contracts/SynthRedeemer.sol | 2 +- contracts/SynthetixEscrow.sol | 2 +- contracts/SynthetixState.sol | 2 +- contracts/SystemSettings.sol | 2 +- contracts/TradingRewards.sol | 2 +- contracts/VirtualSynth.sol | 2 +- contracts/libraries/DynamicFee.sol | 17 +++++++++++++++++ contracts/{ => libraries}/SafeDecimalMath.sol | 0 contracts/test-helpers/MockEtherWrapper.sol | 2 +- .../test-helpers/PublicSafeDecimalMath.sol | 2 +- contracts/test-helpers/SwapWithVirtualSynth.sol | 2 +- contracts/test-helpers/TestableDynamicFee.sol | 6 ++++++ 33 files changed, 53 insertions(+), 30 deletions(-) create mode 100644 contracts/libraries/DynamicFee.sol rename contracts/{ => libraries}/SafeDecimalMath.sol (100%) create mode 100644 contracts/test-helpers/TestableDynamicFee.sol diff --git a/contracts/BaseDebtCache.sol b/contracts/BaseDebtCache.sol index bc459c469b..83f4793c79 100644 --- a/contracts/BaseDebtCache.sol +++ b/contracts/BaseDebtCache.sol @@ -7,7 +7,7 @@ import "./MixinSystemSettings.sol"; import "./interfaces/IDebtCache.sol"; // Libraries -import "./SafeDecimalMath.sol"; +import "./libraries/SafeDecimalMath.sol"; // Internal references import "./interfaces/IIssuer.sol"; diff --git a/contracts/BaseRewardEscrowV2.sol b/contracts/BaseRewardEscrowV2.sol index ac0faa8ac9..3abf390bf1 100644 --- a/contracts/BaseRewardEscrowV2.sol +++ b/contracts/BaseRewardEscrowV2.sol @@ -8,7 +8,7 @@ import "./LimitedSetup.sol"; import "./interfaces/IRewardEscrowV2.sol"; // Libraries -import "./SafeDecimalMath.sol"; +import "./libraries/SafeDecimalMath.sol"; // Internal references import "./interfaces/IERC20.sol"; diff --git a/contracts/Collateral.sol b/contracts/Collateral.sol index 7b7b900b31..a71986c621 100644 --- a/contracts/Collateral.sol +++ b/contracts/Collateral.sol @@ -10,7 +10,7 @@ import "./MixinSystemSettings.sol"; import "./interfaces/ICollateralLoan.sol"; // Libraries -import "./SafeDecimalMath.sol"; +import "./libraries/SafeDecimalMath.sol"; // Internal references import "./interfaces/ICollateralUtil.sol"; diff --git a/contracts/CollateralManager.sol b/contracts/CollateralManager.sol index c0d8b1762c..5711f0135c 100644 --- a/contracts/CollateralManager.sol +++ b/contracts/CollateralManager.sol @@ -9,7 +9,7 @@ import "./interfaces/ICollateralManager.sol"; // Libraries import "./AddressSetLib.sol"; import "./Bytes32SetLib.sol"; -import "./SafeDecimalMath.sol"; +import "./libraries/SafeDecimalMath.sol"; // Internal references import "./CollateralManagerState.sol"; diff --git a/contracts/CollateralManagerState.sol b/contracts/CollateralManagerState.sol index 769901a24e..062059478a 100644 --- a/contracts/CollateralManagerState.sol +++ b/contracts/CollateralManagerState.sol @@ -7,7 +7,7 @@ import "./Owned.sol"; import "./State.sol"; // Libraries -import "./SafeDecimalMath.sol"; +import "./libraries/SafeDecimalMath.sol"; contract CollateralManagerState is Owned, State { using SafeMath for uint; diff --git a/contracts/CollateralUtil.sol b/contracts/CollateralUtil.sol index 0e8e9d36e1..c130fd2ab6 100644 --- a/contracts/CollateralUtil.sol +++ b/contracts/CollateralUtil.sol @@ -9,7 +9,7 @@ import "./interfaces/ICollateralLoan.sol"; import "./interfaces/IExchangeRates.sol"; import "./MixinSystemSettings.sol"; -import "./SafeDecimalMath.sol"; +import "./libraries/SafeDecimalMath.sol"; contract CollateralUtil is ICollateralUtil, ICollateralLoan, MixinSystemSettings { /* ========== LIBRARIES ========== */ diff --git a/contracts/DebtCache.sol b/contracts/DebtCache.sol index 50888a5d9f..ff56980c58 100644 --- a/contracts/DebtCache.sol +++ b/contracts/DebtCache.sol @@ -1,7 +1,7 @@ pragma solidity ^0.5.16; // Libraries -import "./SafeDecimalMath.sol"; +import "./libraries/SafeDecimalMath.sol"; // Inheritance import "./BaseDebtCache.sol"; diff --git a/contracts/Depot.sol b/contracts/Depot.sol index c3bb6eb237..5d2831747b 100644 --- a/contracts/Depot.sol +++ b/contracts/Depot.sol @@ -8,7 +8,7 @@ import "./MixinResolver.sol"; import "./interfaces/IDepot.sol"; // Libraries -import "./SafeDecimalMath.sol"; +import "./libraries/SafeDecimalMath.sol"; // Internal references import "./interfaces/IERC20.sol"; diff --git a/contracts/EtherWrapper.sol b/contracts/EtherWrapper.sol index 5ea051fdc1..c3e9c12de2 100644 --- a/contracts/EtherWrapper.sol +++ b/contracts/EtherWrapper.sol @@ -18,7 +18,7 @@ import "./MixinSystemSettings.sol"; // Libraries import "openzeppelin-solidity-2.3.0/contracts/math/SafeMath.sol"; -import "./SafeDecimalMath.sol"; +import "./libraries/SafeDecimalMath.sol"; // https://docs.synthetix.io/contracts/source/contracts/etherwrapper contract EtherWrapper is Owned, Pausable, MixinResolver, MixinSystemSettings, IEtherWrapper { diff --git a/contracts/ExchangeRates.sol b/contracts/ExchangeRates.sol index 4a9603ab37..d37826bda7 100644 --- a/contracts/ExchangeRates.sol +++ b/contracts/ExchangeRates.sol @@ -7,7 +7,7 @@ import "./MixinSystemSettings.sol"; import "./interfaces/IExchangeRates.sol"; // Libraries -import "./SafeDecimalMath.sol"; +import "./libraries/SafeDecimalMath.sol"; // Internal references // AggregatorInterface from Chainlink represents a decentralized pricing network for a single currency key diff --git a/contracts/Exchanger.sol b/contracts/Exchanger.sol index b6ae2e371d..1bf58a816c 100644 --- a/contracts/Exchanger.sol +++ b/contracts/Exchanger.sol @@ -7,7 +7,7 @@ import "./MixinSystemSettings.sol"; import "./interfaces/IExchanger.sol"; // Libraries -import "./SafeDecimalMath.sol"; +import "./libraries/SafeDecimalMath.sol"; // Internal references import "./interfaces/ISystemStatus.sol"; diff --git a/contracts/ExternStateToken.sol b/contracts/ExternStateToken.sol index 666c5ae690..8e2225f821 100644 --- a/contracts/ExternStateToken.sol +++ b/contracts/ExternStateToken.sol @@ -5,7 +5,7 @@ import "./Owned.sol"; import "./Proxyable.sol"; // Libraries -import "./SafeDecimalMath.sol"; +import "./libraries/SafeDecimalMath.sol"; // Internal references import "./TokenState.sol"; diff --git a/contracts/FeePool.sol b/contracts/FeePool.sol index 312491c382..a4c3877c4d 100644 --- a/contracts/FeePool.sol +++ b/contracts/FeePool.sol @@ -9,7 +9,7 @@ import "./MixinSystemSettings.sol"; import "./interfaces/IFeePool.sol"; // Libraries -import "./SafeDecimalMath.sol"; +import "./libraries/SafeDecimalMath.sol"; // Internal references import "./interfaces/IERC20.sol"; diff --git a/contracts/FeePoolState.sol b/contracts/FeePoolState.sol index 93c1f03f99..454a067416 100644 --- a/contracts/FeePoolState.sol +++ b/contracts/FeePoolState.sol @@ -5,7 +5,7 @@ import "./Owned.sol"; import "./LimitedSetup.sol"; // Libraries -import "./SafeDecimalMath.sol"; +import "./libraries/SafeDecimalMath.sol"; // Internal references import "./interfaces/IFeePool.sol"; diff --git a/contracts/Issuer.sol b/contracts/Issuer.sol index 0d85956c36..15fd2593e1 100644 --- a/contracts/Issuer.sol +++ b/contracts/Issuer.sol @@ -7,7 +7,7 @@ import "./MixinSystemSettings.sol"; import "./interfaces/IIssuer.sol"; // Libraries -import "./SafeDecimalMath.sol"; +import "./libraries/SafeDecimalMath.sol"; // Internal references import "./interfaces/ISynth.sol"; diff --git a/contracts/Liquidations.sol b/contracts/Liquidations.sol index 04bd1d9c77..dfd02098d6 100644 --- a/contracts/Liquidations.sol +++ b/contracts/Liquidations.sol @@ -7,7 +7,7 @@ import "./MixinSystemSettings.sol"; import "./interfaces/ILiquidations.sol"; // Libraries -import "./SafeDecimalMath.sol"; +import "./libraries/SafeDecimalMath.sol"; // Internal references import "./EternalStorage.sol"; diff --git a/contracts/Math.sol b/contracts/Math.sol index 08379c62e4..3e5edd5a44 100644 --- a/contracts/Math.sol +++ b/contracts/Math.sol @@ -1,7 +1,7 @@ pragma solidity ^0.5.16; // Libraries -import "./SafeDecimalMath.sol"; +import "./libraries/SafeDecimalMath.sol"; // https://docs.synthetix.io/contracts/source/libraries/math library Math { diff --git a/contracts/PurgeableSynth.sol b/contracts/PurgeableSynth.sol index d4924c2224..329bcfb58e 100644 --- a/contracts/PurgeableSynth.sol +++ b/contracts/PurgeableSynth.sol @@ -4,7 +4,7 @@ pragma solidity ^0.5.16; import "./Synth.sol"; // Libraries -import "./SafeDecimalMath.sol"; +import "./libraries/SafeDecimalMath.sol"; // Internal References import "./interfaces/IExchangeRates.sol"; diff --git a/contracts/RewardEscrow.sol b/contracts/RewardEscrow.sol index 2d8d64affe..79b667f550 100644 --- a/contracts/RewardEscrow.sol +++ b/contracts/RewardEscrow.sol @@ -5,7 +5,7 @@ import "./Owned.sol"; import "./interfaces/IRewardEscrow.sol"; // Libraries -import "./SafeDecimalMath.sol"; +import "./libraries/SafeDecimalMath.sol"; // Internal references import "./interfaces/IERC20.sol"; diff --git a/contracts/RewardsDistribution.sol b/contracts/RewardsDistribution.sol index cf688eac5d..a2f8168c9a 100644 --- a/contracts/RewardsDistribution.sol +++ b/contracts/RewardsDistribution.sol @@ -5,7 +5,7 @@ import "./Owned.sol"; import "./interfaces/IRewardsDistribution.sol"; // Libraires -import "./SafeDecimalMath.sol"; +import "./libraries/SafeDecimalMath.sol"; // Internal references import "./interfaces/IERC20.sol"; diff --git a/contracts/SupplySchedule.sol b/contracts/SupplySchedule.sol index f50add0ea9..aac347b375 100644 --- a/contracts/SupplySchedule.sol +++ b/contracts/SupplySchedule.sol @@ -5,7 +5,7 @@ import "./Owned.sol"; import "./interfaces/ISupplySchedule.sol"; // Libraries -import "./SafeDecimalMath.sol"; +import "./libraries/SafeDecimalMath.sol"; import "./Math.sol"; // Internal references diff --git a/contracts/SynthRedeemer.sol b/contracts/SynthRedeemer.sol index 2fdc10fbd1..b3e435e3c4 100644 --- a/contracts/SynthRedeemer.sol +++ b/contracts/SynthRedeemer.sol @@ -5,7 +5,7 @@ import "./MixinResolver.sol"; import "./interfaces/ISynthRedeemer.sol"; // Libraries -import "./SafeDecimalMath.sol"; +import "./libraries/SafeDecimalMath.sol"; // Internal references import "./interfaces/IERC20.sol"; diff --git a/contracts/SynthetixEscrow.sol b/contracts/SynthetixEscrow.sol index e5e2ca6f20..06c771efdc 100644 --- a/contracts/SynthetixEscrow.sol +++ b/contracts/SynthetixEscrow.sol @@ -6,7 +6,7 @@ import "./LimitedSetup.sol"; import "./interfaces/IHasBalance.sol"; // Libraires -import "./SafeDecimalMath.sol"; +import "./libraries/SafeDecimalMath.sol"; // Internal references import "./interfaces/IERC20.sol"; diff --git a/contracts/SynthetixState.sol b/contracts/SynthetixState.sol index c57b6567b6..36757e3c75 100644 --- a/contracts/SynthetixState.sol +++ b/contracts/SynthetixState.sol @@ -6,7 +6,7 @@ import "./State.sol"; import "./interfaces/ISynthetixState.sol"; // Libraries -import "./SafeDecimalMath.sol"; +import "./libraries/SafeDecimalMath.sol"; // https://docs.synthetix.io/contracts/source/contracts/synthetixstate contract SynthetixState is Owned, State, ISynthetixState { diff --git a/contracts/SystemSettings.sol b/contracts/SystemSettings.sol index 7715e765b4..3c6f7d4fab 100644 --- a/contracts/SystemSettings.sol +++ b/contracts/SystemSettings.sol @@ -7,7 +7,7 @@ import "./MixinSystemSettings.sol"; import "./interfaces/ISystemSettings.sol"; // Libraries -import "./SafeDecimalMath.sol"; +import "./libraries/SafeDecimalMath.sol"; // https://docs.synthetix.io/contracts/source/contracts/systemsettings contract SystemSettings is Owned, MixinSystemSettings, ISystemSettings { diff --git a/contracts/TradingRewards.sol b/contracts/TradingRewards.sol index 2563d2abe2..0d8602f543 100644 --- a/contracts/TradingRewards.sol +++ b/contracts/TradingRewards.sol @@ -10,7 +10,7 @@ import "openzeppelin-solidity-2.3.0/contracts/token/ERC20/SafeERC20.sol"; import "openzeppelin-solidity-2.3.0/contracts/utils/ReentrancyGuard.sol"; // Libraries. -import "./SafeDecimalMath.sol"; +import "./libraries/SafeDecimalMath.sol"; // Internal references. import "./interfaces/ITradingRewards.sol"; diff --git a/contracts/VirtualSynth.sol b/contracts/VirtualSynth.sol index 166c419849..a1d7c2d784 100644 --- a/contracts/VirtualSynth.sol +++ b/contracts/VirtualSynth.sol @@ -4,7 +4,7 @@ pragma solidity ^0.5.16; import "openzeppelin-solidity-2.3.0/contracts/token/ERC20/ERC20.sol"; // Libraries -import "./SafeDecimalMath.sol"; +import "./libraries/SafeDecimalMath.sol"; // Internal references import "./interfaces/ISynth.sol"; diff --git a/contracts/libraries/DynamicFee.sol b/contracts/libraries/DynamicFee.sol new file mode 100644 index 0000000000..f63ad52c5e --- /dev/null +++ b/contracts/libraries/DynamicFee.sol @@ -0,0 +1,17 @@ +pragma solidity ^0.5.16; + +// Libraries +import "./SafeDecimalMath.sol"; + +library DynamicFee { + using SafeDecimalMath for uint; + uint public constant PRICE_DIFFERENTIAL_THREASHOLD_NUMERATOR = 4; + uint public constant PRICE_DIFFERENTIAL_THREASHOLD_DENUMERATOR = 100; + + /// @notice Calculate price differential + /// @param price Current round price + /// @param previousPrice Previous round price + function getPriceDifferential(uint price, uint previousPrice) public pure returns (uint) { + int(price.divideDecimal(previousPrice)) - 1; + } +} diff --git a/contracts/SafeDecimalMath.sol b/contracts/libraries/SafeDecimalMath.sol similarity index 100% rename from contracts/SafeDecimalMath.sol rename to contracts/libraries/SafeDecimalMath.sol diff --git a/contracts/test-helpers/MockEtherWrapper.sol b/contracts/test-helpers/MockEtherWrapper.sol index b0eb2f0c2b..858d83bc10 100644 --- a/contracts/test-helpers/MockEtherWrapper.sol +++ b/contracts/test-helpers/MockEtherWrapper.sol @@ -1,6 +1,6 @@ pragma solidity ^0.5.16; -import "../SafeDecimalMath.sol"; +import "../libraries/SafeDecimalMath.sol"; contract MockEtherWrapper { using SafeMath for uint; diff --git a/contracts/test-helpers/PublicSafeDecimalMath.sol b/contracts/test-helpers/PublicSafeDecimalMath.sol index cfee49aeb9..6a1ad81354 100644 --- a/contracts/test-helpers/PublicSafeDecimalMath.sol +++ b/contracts/test-helpers/PublicSafeDecimalMath.sol @@ -3,7 +3,7 @@ */ pragma solidity ^0.5.16; -import "../SafeDecimalMath.sol"; +import "../libraries/SafeDecimalMath.sol"; contract PublicSafeDecimalMath { using SafeDecimalMath for uint; diff --git a/contracts/test-helpers/SwapWithVirtualSynth.sol b/contracts/test-helpers/SwapWithVirtualSynth.sol index f3d8ae2b8a..676edfb266 100644 --- a/contracts/test-helpers/SwapWithVirtualSynth.sol +++ b/contracts/test-helpers/SwapWithVirtualSynth.sol @@ -4,7 +4,7 @@ pragma solidity ^0.5.16; import "openzeppelin-solidity-2.3.0/contracts/token/ERC20/ERC20.sol"; // Libraries -import "../SafeDecimalMath.sol"; +import "../libraries/SafeDecimalMath.sol"; // Internal references import "../interfaces/ISynthetix.sol"; diff --git a/contracts/test-helpers/TestableDynamicFee.sol b/contracts/test-helpers/TestableDynamicFee.sol new file mode 100644 index 0000000000..8804b2f01e --- /dev/null +++ b/contracts/test-helpers/TestableDynamicFee.sol @@ -0,0 +1,6 @@ +pragma solidity ^0.5.16; + +// Libraries +import "../libraries/DynamicFee.sol"; + +contract TestableDynamicFee {} From aaf12e545985e1e1d1aff714c1b5fff78da04e58 Mon Sep 17 00:00:00 2001 From: Lecky Date: Wed, 13 Oct 2021 23:46:05 +1100 Subject: [PATCH 002/133] moving Math into libraries - adding decimals into SafeDecimalMath --- contracts/SupplySchedule.sol | 2 +- contracts/{ => libraries}/Math.sol | 2 +- contracts/libraries/SafeDecimalMath.sol | 17 ++++++++++++----- 3 files changed, 14 insertions(+), 7 deletions(-) rename contracts/{ => libraries}/Math.sol (95%) diff --git a/contracts/SupplySchedule.sol b/contracts/SupplySchedule.sol index aac347b375..1e69c9e156 100644 --- a/contracts/SupplySchedule.sol +++ b/contracts/SupplySchedule.sol @@ -6,7 +6,7 @@ import "./interfaces/ISupplySchedule.sol"; // Libraries import "./libraries/SafeDecimalMath.sol"; -import "./Math.sol"; +import "./libraries/Math.sol"; // Internal references import "./Proxy.sol"; diff --git a/contracts/Math.sol b/contracts/libraries/Math.sol similarity index 95% rename from contracts/Math.sol rename to contracts/libraries/Math.sol index 3e5edd5a44..08379c62e4 100644 --- a/contracts/Math.sol +++ b/contracts/libraries/Math.sol @@ -1,7 +1,7 @@ pragma solidity ^0.5.16; // Libraries -import "./libraries/SafeDecimalMath.sol"; +import "./SafeDecimalMath.sol"; // https://docs.synthetix.io/contracts/source/libraries/math library Math { diff --git a/contracts/libraries/SafeDecimalMath.sol b/contracts/libraries/SafeDecimalMath.sol index 1a8fed7160..f232696ac7 100644 --- a/contracts/libraries/SafeDecimalMath.sol +++ b/contracts/libraries/SafeDecimalMath.sol @@ -8,30 +8,37 @@ library SafeDecimalMath { using SafeMath for uint; /* Number of decimal places in the representations. */ - uint8 public constant decimals = 18; + uint8 public constant DECIMALS = 18; uint8 public constant highPrecisionDecimals = 27; /* The number representing 1.0. */ - uint public constant UNIT = 10**uint(decimals); + uint public constant UNIT = 10**uint(DECIMALS); /* The number representing 1.0 for higher fidelity numbers. */ uint public constant PRECISE_UNIT = 10**uint(highPrecisionDecimals); - uint private constant UNIT_TO_HIGH_PRECISION_CONVERSION_FACTOR = 10**uint(highPrecisionDecimals - decimals); + uint private constant UNIT_TO_HIGH_PRECISION_CONVERSION_FACTOR = 10**uint(highPrecisionDecimals - DECIMALS); /** - * @return Provides an interface to UNIT. + * @notice Provides an interface to UNIT. */ function unit() external pure returns (uint) { return UNIT; } /** - * @return Provides an interface to PRECISE_UNIT. + * @notice Provides an interface to PRECISE_UNIT. */ function preciseUnit() external pure returns (uint) { return PRECISE_UNIT; } + /** + * @notice Provides an interface to decimals. + */ + function decimals() external pure returns (uint8) { + return DECIMALS; + } + /** * @return The result of multiplying x and y, interpreting the operands as fixed-point * decimals. From b707cbc3d43ba85390c583fb9599df7cd5f4e8fc Mon Sep 17 00:00:00 2001 From: Lecky Date: Thu, 14 Oct 2021 00:00:57 +1100 Subject: [PATCH 003/133] fixing import Math path; --- contracts/libraries/DynamicFee.sol | 35 +++++++++++++++++++++++++-- contracts/test-helpers/PublicMath.sol | 2 +- 2 files changed, 34 insertions(+), 3 deletions(-) diff --git a/contracts/libraries/DynamicFee.sol b/contracts/libraries/DynamicFee.sol index f63ad52c5e..5edf82eb8b 100644 --- a/contracts/libraries/DynamicFee.sol +++ b/contracts/libraries/DynamicFee.sol @@ -2,16 +2,47 @@ pragma solidity ^0.5.16; // Libraries import "./SafeDecimalMath.sol"; +import "./Math.sol"; library DynamicFee { using SafeDecimalMath for uint; - uint public constant PRICE_DIFFERENTIAL_THREASHOLD_NUMERATOR = 4; - uint public constant PRICE_DIFFERENTIAL_THREASHOLD_DENUMERATOR = 100; + using Math for uint; + + /// @notice Get threshold constant default 0.4% + function threshold() public pure returns (uint) { + return 4 * 10**uint(SafeDecimalMath.decimals() - 3); + } + + /// @notice Get weight decay constant default 0.9 + function weightDecay() public pure returns (uint) { + return 9 * 10**uint(SafeDecimalMath.decimals() - 1); + } /// @notice Calculate price differential /// @param price Current round price /// @param previousPrice Previous round price function getPriceDifferential(uint price, uint previousPrice) public pure returns (uint) { int(price.divideDecimal(previousPrice)) - 1; + + int abs = int(price.divideDecimal(previousPrice)) - int(SafeDecimalMath.unit()); + abs = abs > 0 ? abs : -abs; + return abs > int(threshold()) ? uint(abs) : uint(0); + } + + /// @notice Calculate Price Weight + /// @param round A round number that go back from + /// the current round from 0 to N + function getPriceWeight(uint round) public pure returns (uint) { + return weightDecay().powDecimal(round); + } + + /// @notice Calculate dynamic fee based on preceding 10 price differential + /// @param prices A list of prices from the current round to the previous rounds + function getDynamicFee(uint[] memory prices) public pure returns (uint) { + uint size = prices.length; + require(size >= 2, "Not enough prices"); + for (uint i = 0; i < size - 1; i++) { + getPriceDifferential(prices[i], prices[i + 1]); + } } } diff --git a/contracts/test-helpers/PublicMath.sol b/contracts/test-helpers/PublicMath.sol index 16ce63c952..cc89855e3c 100644 --- a/contracts/test-helpers/PublicMath.sol +++ b/contracts/test-helpers/PublicMath.sol @@ -3,7 +3,7 @@ */ pragma solidity ^0.5.16; -import "../Math.sol"; +import "../libraries/Math.sol"; contract PublicMath { using Math for uint; From 6e9a995fb5717b55177d82559796762de137fcb0 Mon Sep 17 00:00:00 2001 From: Lecky Date: Thu, 14 Oct 2021 00:06:42 +1100 Subject: [PATCH 004/133] adding implementation for DynamicFee --- contracts/libraries/DynamicFee.sol | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/contracts/libraries/DynamicFee.sol b/contracts/libraries/DynamicFee.sol index 5edf82eb8b..2478e4729b 100644 --- a/contracts/libraries/DynamicFee.sol +++ b/contracts/libraries/DynamicFee.sol @@ -7,6 +7,7 @@ import "./Math.sol"; library DynamicFee { using SafeDecimalMath for uint; using Math for uint; + using SafeMath for uint; /// @notice Get threshold constant default 0.4% function threshold() public pure returns (uint) { @@ -38,11 +39,13 @@ library DynamicFee { /// @notice Calculate dynamic fee based on preceding 10 price differential /// @param prices A list of prices from the current round to the previous rounds - function getDynamicFee(uint[] memory prices) public pure returns (uint) { + function getDynamicFee(uint[] memory prices) public pure returns (uint dynamicFee) { uint size = prices.length; require(size >= 2, "Not enough prices"); for (uint i = 0; i < size - 1; i++) { - getPriceDifferential(prices[i], prices[i + 1]); + uint priceDifferential = getPriceDifferential(prices[i], prices[i + 1]); + uint priceWeight = getPriceWeight(i); + dynamicFee = dynamicFee.add(priceDifferential.multiplyDecimal(priceWeight)); } } } From 82e71a5f2d58405a70ff2221d95b388ac898e06c Mon Sep 17 00:00:00 2001 From: Lecky Date: Thu, 14 Oct 2021 00:08:19 +1100 Subject: [PATCH 005/133] Adding Natspec --- contracts/libraries/DynamicFee.sol | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/contracts/libraries/DynamicFee.sol b/contracts/libraries/DynamicFee.sol index 2478e4729b..2532e61566 100644 --- a/contracts/libraries/DynamicFee.sol +++ b/contracts/libraries/DynamicFee.sol @@ -10,11 +10,13 @@ library DynamicFee { using SafeMath for uint; /// @notice Get threshold constant default 0.4% + /// @return uint threshold constant function threshold() public pure returns (uint) { return 4 * 10**uint(SafeDecimalMath.decimals() - 3); } /// @notice Get weight decay constant default 0.9 + /// @return uint weight decay constant function weightDecay() public pure returns (uint) { return 9 * 10**uint(SafeDecimalMath.decimals() - 1); } @@ -22,6 +24,7 @@ library DynamicFee { /// @notice Calculate price differential /// @param price Current round price /// @param previousPrice Previous round price + /// @return uint price differential function getPriceDifferential(uint price, uint previousPrice) public pure returns (uint) { int(price.divideDecimal(previousPrice)) - 1; @@ -33,12 +36,14 @@ library DynamicFee { /// @notice Calculate Price Weight /// @param round A round number that go back from /// the current round from 0 to N + /// @return uint price weight function getPriceWeight(uint round) public pure returns (uint) { return weightDecay().powDecimal(round); } /// @notice Calculate dynamic fee based on preceding 10 price differential /// @param prices A list of prices from the current round to the previous rounds + /// @return uint dynamic fee function getDynamicFee(uint[] memory prices) public pure returns (uint dynamicFee) { uint size = prices.length; require(size >= 2, "Not enough prices"); From 82808cf6009fbae57dac53e911845eacbbfaa60e Mon Sep 17 00:00:00 2001 From: Lecky Date: Thu, 14 Oct 2021 16:47:50 +1100 Subject: [PATCH 006/133] Adding test for DynamicFee --- contracts/test-helpers/TestableDynamicFee.sol | 6 ++++- .../contracts/libraries/TestableDynamicFee.js | 22 +++++++++++++++++++ 2 files changed, 27 insertions(+), 1 deletion(-) create mode 100644 test/contracts/libraries/TestableDynamicFee.js diff --git a/contracts/test-helpers/TestableDynamicFee.sol b/contracts/test-helpers/TestableDynamicFee.sol index 8804b2f01e..3d845c6f64 100644 --- a/contracts/test-helpers/TestableDynamicFee.sol +++ b/contracts/test-helpers/TestableDynamicFee.sol @@ -3,4 +3,8 @@ pragma solidity ^0.5.16; // Libraries import "../libraries/DynamicFee.sol"; -contract TestableDynamicFee {} +contract TestableDynamicFee { + function testGetDynamicFee(uint[] memory prices) public pure returns (uint) { + return DynamicFee.getDynamicFee(prices); + } +} diff --git a/test/contracts/libraries/TestableDynamicFee.js b/test/contracts/libraries/TestableDynamicFee.js new file mode 100644 index 0000000000..a19a298f78 --- /dev/null +++ b/test/contracts/libraries/TestableDynamicFee.js @@ -0,0 +1,22 @@ +const { contract, artifacts } = require('hardhat'); +const { assert } = require('../common'); +const { toUnit } = require('../../utils')(); +const SafeDecimalMath = artifacts.require('SafeDecimalMath'); +const DynamicFee = artifacts.require('DynamicFee'); +const TestableDynamicFee = artifacts.require('TestableDynamicFee'); + +contract('TestableDynamicFee', () => { + let testableDynamicFee; + + before(async () => { + DynamicFee.link(await SafeDecimalMath.new()); + TestableDynamicFee.link(await DynamicFee.new()); + testableDynamicFee = await TestableDynamicFee.new(); + }); + + it('Can get dynamic fee', async () => { + const prices = [toUnit('102'), toUnit('101'), toUnit('100')]; + const dynamicFee = await testableDynamicFee.testGetDynamicFee(prices); + assert.bnEqual(dynamicFee, '18900990099009900'); + }); +}); From 89116c39f60a89339915ab4978913d14740217cc Mon Sep 17 00:00:00 2001 From: Lecky Date: Thu, 14 Oct 2021 18:06:20 +1100 Subject: [PATCH 007/133] Adding more tests for DynamicFee --- contracts/test-helpers/TestableDynamicFee.sol | 18 +++++++++++- .../contracts/libraries/TestableDynamicFee.js | 29 +++++++++++++++++++ 2 files changed, 46 insertions(+), 1 deletion(-) diff --git a/contracts/test-helpers/TestableDynamicFee.sol b/contracts/test-helpers/TestableDynamicFee.sol index 3d845c6f64..cd18c1d16b 100644 --- a/contracts/test-helpers/TestableDynamicFee.sol +++ b/contracts/test-helpers/TestableDynamicFee.sol @@ -4,7 +4,23 @@ pragma solidity ^0.5.16; import "../libraries/DynamicFee.sol"; contract TestableDynamicFee { - function testGetDynamicFee(uint[] memory prices) public pure returns (uint) { + function testThreshold() public pure returns (uint) { + return DynamicFee.threshold(); + } + + function testWeightDecay() public pure returns (uint) { + return DynamicFee.weightDecay(); + } + + function testGetPriceDifferential(uint price, uint previousPrice) public pure returns (uint) { + return DynamicFee.getPriceDifferential(price, previousPrice); + } + + function testGetPriceWeight(uint round) public view returns (uint) { + return DynamicFee.getPriceWeight(round); + } + + function testGetDynamicFee(uint[] memory prices) public view returns (uint) { return DynamicFee.getDynamicFee(prices); } } diff --git a/test/contracts/libraries/TestableDynamicFee.js b/test/contracts/libraries/TestableDynamicFee.js index a19a298f78..2e93b5453d 100644 --- a/test/contracts/libraries/TestableDynamicFee.js +++ b/test/contracts/libraries/TestableDynamicFee.js @@ -14,6 +14,35 @@ contract('TestableDynamicFee', () => { testableDynamicFee = await TestableDynamicFee.new(); }); + it('Can get threshold', async () => { + const threshold = await testableDynamicFee.testThreshold(); + assert.bnEqual(threshold, toUnit('0.004')); + }); + + it('Can get weight decay', async () => { + const weightDecay = await testableDynamicFee.testWeightDecay(); + assert.bnEqual(weightDecay, toUnit('0.9')); + }); + + it('Can get price differential', async () => { + const priceDiff = await testableDynamicFee.testGetPriceDifferential( + toUnit('102'), + toUnit('101') + ); + assert.bnEqual(priceDiff, '9900990099009900'); + }); + + it('Can get price weight', async () => { + const priceWeight0 = await testableDynamicFee.testGetPriceWeight('0'); + assert.bnEqual(priceWeight0, toUnit('1')); + + const priceWeight1 = await testableDynamicFee.testGetPriceWeight('1'); + assert.bnEqual(priceWeight1, toUnit('0.9')); + + const priceWeight2 = await testableDynamicFee.testGetPriceWeight('2'); + assert.bnEqual(priceWeight2, toUnit('0.81')); + }); + it('Can get dynamic fee', async () => { const prices = [toUnit('102'), toUnit('101'), toUnit('100')]; const dynamicFee = await testableDynamicFee.testGetDynamicFee(prices); From 278b3d7c702ea32b82969b075ec7e32a4642b43f Mon Sep 17 00:00:00 2001 From: Lecky Date: Thu, 14 Oct 2021 21:10:09 +1100 Subject: [PATCH 008/133] Try to fix CI Cov by moving test file --- contracts/test-helpers/TestableDynamicFee.sol | 4 ++-- test/contracts/{libraries => }/TestableDynamicFee.js | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) rename test/contracts/{libraries => }/TestableDynamicFee.js (95%) diff --git a/contracts/test-helpers/TestableDynamicFee.sol b/contracts/test-helpers/TestableDynamicFee.sol index cd18c1d16b..57aaf31d1f 100644 --- a/contracts/test-helpers/TestableDynamicFee.sol +++ b/contracts/test-helpers/TestableDynamicFee.sol @@ -16,11 +16,11 @@ contract TestableDynamicFee { return DynamicFee.getPriceDifferential(price, previousPrice); } - function testGetPriceWeight(uint round) public view returns (uint) { + function testGetPriceWeight(uint round) public pure returns (uint) { return DynamicFee.getPriceWeight(round); } - function testGetDynamicFee(uint[] memory prices) public view returns (uint) { + function testGetDynamicFee(uint[] memory prices) public pure returns (uint) { return DynamicFee.getDynamicFee(prices); } } diff --git a/test/contracts/libraries/TestableDynamicFee.js b/test/contracts/TestableDynamicFee.js similarity index 95% rename from test/contracts/libraries/TestableDynamicFee.js rename to test/contracts/TestableDynamicFee.js index 2e93b5453d..4f3d986404 100644 --- a/test/contracts/libraries/TestableDynamicFee.js +++ b/test/contracts/TestableDynamicFee.js @@ -1,6 +1,6 @@ const { contract, artifacts } = require('hardhat'); -const { assert } = require('../common'); -const { toUnit } = require('../../utils')(); +const { assert } = require('./common'); +const { toUnit } = require('../utils')(); const SafeDecimalMath = artifacts.require('SafeDecimalMath'); const DynamicFee = artifacts.require('DynamicFee'); const TestableDynamicFee = artifacts.require('TestableDynamicFee'); From bb2660145eef33632dafa49250bc7c3f5b0c15de Mon Sep 17 00:00:00 2001 From: Lecky Date: Thu, 14 Oct 2021 21:55:33 +1100 Subject: [PATCH 009/133] revert libraries folder and move to new PR --- contracts/BaseDebtCache.sol | 2 +- contracts/BaseRewardEscrowV2.sol | 2 +- contracts/Collateral.sol | 2 +- contracts/CollateralManager.sol | 2 +- contracts/CollateralManagerState.sol | 2 +- contracts/CollateralUtil.sol | 2 +- contracts/DebtCache.sol | 2 +- contracts/Depot.sol | 2 +- contracts/{libraries => }/DynamicFee.sol | 0 contracts/EtherWrapper.sol | 2 +- contracts/ExchangeRates.sol | 2 +- contracts/Exchanger.sol | 2 +- contracts/ExternStateToken.sol | 2 +- contracts/FeePool.sol | 2 +- contracts/FeePoolState.sol | 2 +- contracts/Issuer.sol | 2 +- contracts/Liquidations.sol | 2 +- contracts/{libraries => }/Math.sol | 0 contracts/PurgeableSynth.sol | 2 +- contracts/RewardEscrow.sol | 2 +- contracts/RewardsDistribution.sol | 2 +- contracts/{libraries => }/SafeDecimalMath.sol | 0 contracts/SupplySchedule.sol | 4 ++-- contracts/SynthRedeemer.sol | 2 +- contracts/SynthetixEscrow.sol | 2 +- contracts/SynthetixState.sol | 2 +- contracts/SystemSettings.sol | 2 +- contracts/TradingRewards.sol | 2 +- contracts/VirtualSynth.sol | 2 +- 29 files changed, 27 insertions(+), 27 deletions(-) rename contracts/{libraries => }/DynamicFee.sol (100%) rename contracts/{libraries => }/Math.sol (100%) rename contracts/{libraries => }/SafeDecimalMath.sol (100%) diff --git a/contracts/BaseDebtCache.sol b/contracts/BaseDebtCache.sol index 83f4793c79..bc459c469b 100644 --- a/contracts/BaseDebtCache.sol +++ b/contracts/BaseDebtCache.sol @@ -7,7 +7,7 @@ import "./MixinSystemSettings.sol"; import "./interfaces/IDebtCache.sol"; // Libraries -import "./libraries/SafeDecimalMath.sol"; +import "./SafeDecimalMath.sol"; // Internal references import "./interfaces/IIssuer.sol"; diff --git a/contracts/BaseRewardEscrowV2.sol b/contracts/BaseRewardEscrowV2.sol index 3abf390bf1..ac0faa8ac9 100644 --- a/contracts/BaseRewardEscrowV2.sol +++ b/contracts/BaseRewardEscrowV2.sol @@ -8,7 +8,7 @@ import "./LimitedSetup.sol"; import "./interfaces/IRewardEscrowV2.sol"; // Libraries -import "./libraries/SafeDecimalMath.sol"; +import "./SafeDecimalMath.sol"; // Internal references import "./interfaces/IERC20.sol"; diff --git a/contracts/Collateral.sol b/contracts/Collateral.sol index a71986c621..7b7b900b31 100644 --- a/contracts/Collateral.sol +++ b/contracts/Collateral.sol @@ -10,7 +10,7 @@ import "./MixinSystemSettings.sol"; import "./interfaces/ICollateralLoan.sol"; // Libraries -import "./libraries/SafeDecimalMath.sol"; +import "./SafeDecimalMath.sol"; // Internal references import "./interfaces/ICollateralUtil.sol"; diff --git a/contracts/CollateralManager.sol b/contracts/CollateralManager.sol index 08e69536f9..cb3d969f82 100644 --- a/contracts/CollateralManager.sol +++ b/contracts/CollateralManager.sol @@ -9,7 +9,7 @@ import "./interfaces/ICollateralManager.sol"; // Libraries import "./AddressSetLib.sol"; import "./Bytes32SetLib.sol"; -import "./libraries/SafeDecimalMath.sol"; +import "./SafeDecimalMath.sol"; // Internal references import "./CollateralManagerState.sol"; diff --git a/contracts/CollateralManagerState.sol b/contracts/CollateralManagerState.sol index 062059478a..769901a24e 100644 --- a/contracts/CollateralManagerState.sol +++ b/contracts/CollateralManagerState.sol @@ -7,7 +7,7 @@ import "./Owned.sol"; import "./State.sol"; // Libraries -import "./libraries/SafeDecimalMath.sol"; +import "./SafeDecimalMath.sol"; contract CollateralManagerState is Owned, State { using SafeMath for uint; diff --git a/contracts/CollateralUtil.sol b/contracts/CollateralUtil.sol index c130fd2ab6..0e8e9d36e1 100644 --- a/contracts/CollateralUtil.sol +++ b/contracts/CollateralUtil.sol @@ -9,7 +9,7 @@ import "./interfaces/ICollateralLoan.sol"; import "./interfaces/IExchangeRates.sol"; import "./MixinSystemSettings.sol"; -import "./libraries/SafeDecimalMath.sol"; +import "./SafeDecimalMath.sol"; contract CollateralUtil is ICollateralUtil, ICollateralLoan, MixinSystemSettings { /* ========== LIBRARIES ========== */ diff --git a/contracts/DebtCache.sol b/contracts/DebtCache.sol index ff56980c58..50888a5d9f 100644 --- a/contracts/DebtCache.sol +++ b/contracts/DebtCache.sol @@ -1,7 +1,7 @@ pragma solidity ^0.5.16; // Libraries -import "./libraries/SafeDecimalMath.sol"; +import "./SafeDecimalMath.sol"; // Inheritance import "./BaseDebtCache.sol"; diff --git a/contracts/Depot.sol b/contracts/Depot.sol index 5d2831747b..c3bb6eb237 100644 --- a/contracts/Depot.sol +++ b/contracts/Depot.sol @@ -8,7 +8,7 @@ import "./MixinResolver.sol"; import "./interfaces/IDepot.sol"; // Libraries -import "./libraries/SafeDecimalMath.sol"; +import "./SafeDecimalMath.sol"; // Internal references import "./interfaces/IERC20.sol"; diff --git a/contracts/libraries/DynamicFee.sol b/contracts/DynamicFee.sol similarity index 100% rename from contracts/libraries/DynamicFee.sol rename to contracts/DynamicFee.sol diff --git a/contracts/EtherWrapper.sol b/contracts/EtherWrapper.sol index c3e9c12de2..5ea051fdc1 100644 --- a/contracts/EtherWrapper.sol +++ b/contracts/EtherWrapper.sol @@ -18,7 +18,7 @@ import "./MixinSystemSettings.sol"; // Libraries import "openzeppelin-solidity-2.3.0/contracts/math/SafeMath.sol"; -import "./libraries/SafeDecimalMath.sol"; +import "./SafeDecimalMath.sol"; // https://docs.synthetix.io/contracts/source/contracts/etherwrapper contract EtherWrapper is Owned, Pausable, MixinResolver, MixinSystemSettings, IEtherWrapper { diff --git a/contracts/ExchangeRates.sol b/contracts/ExchangeRates.sol index d37826bda7..4a9603ab37 100644 --- a/contracts/ExchangeRates.sol +++ b/contracts/ExchangeRates.sol @@ -7,7 +7,7 @@ import "./MixinSystemSettings.sol"; import "./interfaces/IExchangeRates.sol"; // Libraries -import "./libraries/SafeDecimalMath.sol"; +import "./SafeDecimalMath.sol"; // Internal references // AggregatorInterface from Chainlink represents a decentralized pricing network for a single currency key diff --git a/contracts/Exchanger.sol b/contracts/Exchanger.sol index 1bf58a816c..b6ae2e371d 100644 --- a/contracts/Exchanger.sol +++ b/contracts/Exchanger.sol @@ -7,7 +7,7 @@ import "./MixinSystemSettings.sol"; import "./interfaces/IExchanger.sol"; // Libraries -import "./libraries/SafeDecimalMath.sol"; +import "./SafeDecimalMath.sol"; // Internal references import "./interfaces/ISystemStatus.sol"; diff --git a/contracts/ExternStateToken.sol b/contracts/ExternStateToken.sol index 8e2225f821..666c5ae690 100644 --- a/contracts/ExternStateToken.sol +++ b/contracts/ExternStateToken.sol @@ -5,7 +5,7 @@ import "./Owned.sol"; import "./Proxyable.sol"; // Libraries -import "./libraries/SafeDecimalMath.sol"; +import "./SafeDecimalMath.sol"; // Internal references import "./TokenState.sol"; diff --git a/contracts/FeePool.sol b/contracts/FeePool.sol index a4c3877c4d..312491c382 100644 --- a/contracts/FeePool.sol +++ b/contracts/FeePool.sol @@ -9,7 +9,7 @@ import "./MixinSystemSettings.sol"; import "./interfaces/IFeePool.sol"; // Libraries -import "./libraries/SafeDecimalMath.sol"; +import "./SafeDecimalMath.sol"; // Internal references import "./interfaces/IERC20.sol"; diff --git a/contracts/FeePoolState.sol b/contracts/FeePoolState.sol index 454a067416..93c1f03f99 100644 --- a/contracts/FeePoolState.sol +++ b/contracts/FeePoolState.sol @@ -5,7 +5,7 @@ import "./Owned.sol"; import "./LimitedSetup.sol"; // Libraries -import "./libraries/SafeDecimalMath.sol"; +import "./SafeDecimalMath.sol"; // Internal references import "./interfaces/IFeePool.sol"; diff --git a/contracts/Issuer.sol b/contracts/Issuer.sol index 15fd2593e1..0d85956c36 100644 --- a/contracts/Issuer.sol +++ b/contracts/Issuer.sol @@ -7,7 +7,7 @@ import "./MixinSystemSettings.sol"; import "./interfaces/IIssuer.sol"; // Libraries -import "./libraries/SafeDecimalMath.sol"; +import "./SafeDecimalMath.sol"; // Internal references import "./interfaces/ISynth.sol"; diff --git a/contracts/Liquidations.sol b/contracts/Liquidations.sol index dfd02098d6..04bd1d9c77 100644 --- a/contracts/Liquidations.sol +++ b/contracts/Liquidations.sol @@ -7,7 +7,7 @@ import "./MixinSystemSettings.sol"; import "./interfaces/ILiquidations.sol"; // Libraries -import "./libraries/SafeDecimalMath.sol"; +import "./SafeDecimalMath.sol"; // Internal references import "./EternalStorage.sol"; diff --git a/contracts/libraries/Math.sol b/contracts/Math.sol similarity index 100% rename from contracts/libraries/Math.sol rename to contracts/Math.sol diff --git a/contracts/PurgeableSynth.sol b/contracts/PurgeableSynth.sol index 329bcfb58e..d4924c2224 100644 --- a/contracts/PurgeableSynth.sol +++ b/contracts/PurgeableSynth.sol @@ -4,7 +4,7 @@ pragma solidity ^0.5.16; import "./Synth.sol"; // Libraries -import "./libraries/SafeDecimalMath.sol"; +import "./SafeDecimalMath.sol"; // Internal References import "./interfaces/IExchangeRates.sol"; diff --git a/contracts/RewardEscrow.sol b/contracts/RewardEscrow.sol index 79b667f550..2d8d64affe 100644 --- a/contracts/RewardEscrow.sol +++ b/contracts/RewardEscrow.sol @@ -5,7 +5,7 @@ import "./Owned.sol"; import "./interfaces/IRewardEscrow.sol"; // Libraries -import "./libraries/SafeDecimalMath.sol"; +import "./SafeDecimalMath.sol"; // Internal references import "./interfaces/IERC20.sol"; diff --git a/contracts/RewardsDistribution.sol b/contracts/RewardsDistribution.sol index a2f8168c9a..cf688eac5d 100644 --- a/contracts/RewardsDistribution.sol +++ b/contracts/RewardsDistribution.sol @@ -5,7 +5,7 @@ import "./Owned.sol"; import "./interfaces/IRewardsDistribution.sol"; // Libraires -import "./libraries/SafeDecimalMath.sol"; +import "./SafeDecimalMath.sol"; // Internal references import "./interfaces/IERC20.sol"; diff --git a/contracts/libraries/SafeDecimalMath.sol b/contracts/SafeDecimalMath.sol similarity index 100% rename from contracts/libraries/SafeDecimalMath.sol rename to contracts/SafeDecimalMath.sol diff --git a/contracts/SupplySchedule.sol b/contracts/SupplySchedule.sol index 1e69c9e156..f50add0ea9 100644 --- a/contracts/SupplySchedule.sol +++ b/contracts/SupplySchedule.sol @@ -5,8 +5,8 @@ import "./Owned.sol"; import "./interfaces/ISupplySchedule.sol"; // Libraries -import "./libraries/SafeDecimalMath.sol"; -import "./libraries/Math.sol"; +import "./SafeDecimalMath.sol"; +import "./Math.sol"; // Internal references import "./Proxy.sol"; diff --git a/contracts/SynthRedeemer.sol b/contracts/SynthRedeemer.sol index b3e435e3c4..2fdc10fbd1 100644 --- a/contracts/SynthRedeemer.sol +++ b/contracts/SynthRedeemer.sol @@ -5,7 +5,7 @@ import "./MixinResolver.sol"; import "./interfaces/ISynthRedeemer.sol"; // Libraries -import "./libraries/SafeDecimalMath.sol"; +import "./SafeDecimalMath.sol"; // Internal references import "./interfaces/IERC20.sol"; diff --git a/contracts/SynthetixEscrow.sol b/contracts/SynthetixEscrow.sol index 06c771efdc..e5e2ca6f20 100644 --- a/contracts/SynthetixEscrow.sol +++ b/contracts/SynthetixEscrow.sol @@ -6,7 +6,7 @@ import "./LimitedSetup.sol"; import "./interfaces/IHasBalance.sol"; // Libraires -import "./libraries/SafeDecimalMath.sol"; +import "./SafeDecimalMath.sol"; // Internal references import "./interfaces/IERC20.sol"; diff --git a/contracts/SynthetixState.sol b/contracts/SynthetixState.sol index 36757e3c75..c57b6567b6 100644 --- a/contracts/SynthetixState.sol +++ b/contracts/SynthetixState.sol @@ -6,7 +6,7 @@ import "./State.sol"; import "./interfaces/ISynthetixState.sol"; // Libraries -import "./libraries/SafeDecimalMath.sol"; +import "./SafeDecimalMath.sol"; // https://docs.synthetix.io/contracts/source/contracts/synthetixstate contract SynthetixState is Owned, State, ISynthetixState { diff --git a/contracts/SystemSettings.sol b/contracts/SystemSettings.sol index 3c6f7d4fab..7715e765b4 100644 --- a/contracts/SystemSettings.sol +++ b/contracts/SystemSettings.sol @@ -7,7 +7,7 @@ import "./MixinSystemSettings.sol"; import "./interfaces/ISystemSettings.sol"; // Libraries -import "./libraries/SafeDecimalMath.sol"; +import "./SafeDecimalMath.sol"; // https://docs.synthetix.io/contracts/source/contracts/systemsettings contract SystemSettings is Owned, MixinSystemSettings, ISystemSettings { diff --git a/contracts/TradingRewards.sol b/contracts/TradingRewards.sol index 0d8602f543..2563d2abe2 100644 --- a/contracts/TradingRewards.sol +++ b/contracts/TradingRewards.sol @@ -10,7 +10,7 @@ import "openzeppelin-solidity-2.3.0/contracts/token/ERC20/SafeERC20.sol"; import "openzeppelin-solidity-2.3.0/contracts/utils/ReentrancyGuard.sol"; // Libraries. -import "./libraries/SafeDecimalMath.sol"; +import "./SafeDecimalMath.sol"; // Internal references. import "./interfaces/ITradingRewards.sol"; diff --git a/contracts/VirtualSynth.sol b/contracts/VirtualSynth.sol index a1d7c2d784..166c419849 100644 --- a/contracts/VirtualSynth.sol +++ b/contracts/VirtualSynth.sol @@ -4,7 +4,7 @@ pragma solidity ^0.5.16; import "openzeppelin-solidity-2.3.0/contracts/token/ERC20/ERC20.sol"; // Libraries -import "./libraries/SafeDecimalMath.sol"; +import "./SafeDecimalMath.sol"; // Internal references import "./interfaces/ISynth.sol"; From f4f42184353980d00baf0d664d294a03e41d11f4 Mon Sep 17 00:00:00 2001 From: Lecky Date: Fri, 15 Oct 2021 00:53:39 +1100 Subject: [PATCH 010/133] revert libraries path --- contracts/test-helpers/MockEtherWrapper.sol | 2 +- contracts/test-helpers/PublicMath.sol | 2 +- contracts/test-helpers/PublicSafeDecimalMath.sol | 2 +- contracts/test-helpers/SwapWithVirtualSynth.sol | 2 +- contracts/test-helpers/TestableDynamicFee.sol | 2 +- 5 files changed, 5 insertions(+), 5 deletions(-) diff --git a/contracts/test-helpers/MockEtherWrapper.sol b/contracts/test-helpers/MockEtherWrapper.sol index 858d83bc10..b0eb2f0c2b 100644 --- a/contracts/test-helpers/MockEtherWrapper.sol +++ b/contracts/test-helpers/MockEtherWrapper.sol @@ -1,6 +1,6 @@ pragma solidity ^0.5.16; -import "../libraries/SafeDecimalMath.sol"; +import "../SafeDecimalMath.sol"; contract MockEtherWrapper { using SafeMath for uint; diff --git a/contracts/test-helpers/PublicMath.sol b/contracts/test-helpers/PublicMath.sol index cc89855e3c..16ce63c952 100644 --- a/contracts/test-helpers/PublicMath.sol +++ b/contracts/test-helpers/PublicMath.sol @@ -3,7 +3,7 @@ */ pragma solidity ^0.5.16; -import "../libraries/Math.sol"; +import "../Math.sol"; contract PublicMath { using Math for uint; diff --git a/contracts/test-helpers/PublicSafeDecimalMath.sol b/contracts/test-helpers/PublicSafeDecimalMath.sol index 6a1ad81354..cfee49aeb9 100644 --- a/contracts/test-helpers/PublicSafeDecimalMath.sol +++ b/contracts/test-helpers/PublicSafeDecimalMath.sol @@ -3,7 +3,7 @@ */ pragma solidity ^0.5.16; -import "../libraries/SafeDecimalMath.sol"; +import "../SafeDecimalMath.sol"; contract PublicSafeDecimalMath { using SafeDecimalMath for uint; diff --git a/contracts/test-helpers/SwapWithVirtualSynth.sol b/contracts/test-helpers/SwapWithVirtualSynth.sol index 676edfb266..f3d8ae2b8a 100644 --- a/contracts/test-helpers/SwapWithVirtualSynth.sol +++ b/contracts/test-helpers/SwapWithVirtualSynth.sol @@ -4,7 +4,7 @@ pragma solidity ^0.5.16; import "openzeppelin-solidity-2.3.0/contracts/token/ERC20/ERC20.sol"; // Libraries -import "../libraries/SafeDecimalMath.sol"; +import "../SafeDecimalMath.sol"; // Internal references import "../interfaces/ISynthetix.sol"; diff --git a/contracts/test-helpers/TestableDynamicFee.sol b/contracts/test-helpers/TestableDynamicFee.sol index 57aaf31d1f..7b62189ef1 100644 --- a/contracts/test-helpers/TestableDynamicFee.sol +++ b/contracts/test-helpers/TestableDynamicFee.sol @@ -1,7 +1,7 @@ pragma solidity ^0.5.16; // Libraries -import "../libraries/DynamicFee.sol"; +import "../DynamicFee.sol"; contract TestableDynamicFee { function testThreshold() public pure returns (uint) { From 980bc20d7230a7192f25532fc433063d0f797cc6 Mon Sep 17 00:00:00 2001 From: Lecky Date: Mon, 18 Oct 2021 16:15:11 +1100 Subject: [PATCH 011/133] fixed test with data from Afif --- contracts/DynamicFee.sol | 3 ++- test/contracts/TestableDynamicFee.js | 17 ++++++++++++++--- 2 files changed, 16 insertions(+), 4 deletions(-) diff --git a/contracts/DynamicFee.sol b/contracts/DynamicFee.sol index 2532e61566..a2c415027e 100644 --- a/contracts/DynamicFee.sol +++ b/contracts/DynamicFee.sol @@ -30,7 +30,8 @@ library DynamicFee { int abs = int(price.divideDecimal(previousPrice)) - int(SafeDecimalMath.unit()); abs = abs > 0 ? abs : -abs; - return abs > int(threshold()) ? uint(abs) : uint(0); + int priceDifferential = abs - int(threshold()); + return priceDifferential > 0 ? uint(priceDifferential) : uint(0); } /// @notice Calculate Price Weight diff --git a/test/contracts/TestableDynamicFee.js b/test/contracts/TestableDynamicFee.js index 4f3d986404..18afcad85b 100644 --- a/test/contracts/TestableDynamicFee.js +++ b/test/contracts/TestableDynamicFee.js @@ -29,7 +29,7 @@ contract('TestableDynamicFee', () => { toUnit('102'), toUnit('101') ); - assert.bnEqual(priceDiff, '9900990099009900'); + assert.bnEqual(priceDiff, '5900990099009900'); }); it('Can get price weight', async () => { @@ -44,8 +44,19 @@ contract('TestableDynamicFee', () => { }); it('Can get dynamic fee', async () => { - const prices = [toUnit('102'), toUnit('101'), toUnit('100')]; + const prices = [ + toUnit('49234.65005734'), + toUnit('49535.05178912'), + toUnit('49714.05205647'), + toUnit('49691.8024553899'), + toUnit('49714.05205647'), + toUnit('49722.83886705'), + toUnit('49838.87627216'), + toUnit('49842.74988613'), + toUnit('49933.34034209'), + toUnit('49871.92313713'), + ]; const dynamicFee = await testableDynamicFee.testGetDynamicFee(prices); - assert.bnEqual(dynamicFee, '18900990099009900'); + assert.bnEqual(dynamicFee, '2064427530203592'); }); }); From 723c6e6fd8c5d3899cdc04f283bcd5e4f172dae9 Mon Sep 17 00:00:00 2001 From: Lecky Date: Thu, 21 Oct 2021 16:55:06 +1100 Subject: [PATCH 012/133] feat(Exchanger): Adding dynamic fee into exchange fee - Adding dynamic fee constants; --- contracts/DynamicFee.sol | 38 +++++++++---------- contracts/Exchanger.sol | 32 ++++++++++++++-- contracts/MixinSystemSettings.sol | 23 +++++++++++ contracts/SystemSettings.sol | 35 +++++++++++++++++ contracts/test-helpers/TestableDynamicFee.sol | 21 ++++------ index.js | 3 ++ .../deploy/configure-system-settings.js | 28 ++++++++++++++ test/contracts/TestableDynamicFee.js | 14 ++----- 8 files changed, 147 insertions(+), 47 deletions(-) diff --git a/contracts/DynamicFee.sol b/contracts/DynamicFee.sol index a2c415027e..a568eff7a7 100644 --- a/contracts/DynamicFee.sol +++ b/contracts/DynamicFee.sol @@ -9,48 +9,48 @@ library DynamicFee { using Math for uint; using SafeMath for uint; - /// @notice Get threshold constant default 0.4% - /// @return uint threshold constant - function threshold() public pure returns (uint) { - return 4 * 10**uint(SafeDecimalMath.decimals() - 3); - } - - /// @notice Get weight decay constant default 0.9 - /// @return uint weight decay constant - function weightDecay() public pure returns (uint) { - return 9 * 10**uint(SafeDecimalMath.decimals() - 1); - } - /// @notice Calculate price differential /// @param price Current round price /// @param previousPrice Previous round price + /// @param threshold Threshold constant /// @return uint price differential - function getPriceDifferential(uint price, uint previousPrice) public pure returns (uint) { + function getPriceDifferential( + uint price, + uint previousPrice, + uint threshold + ) public pure returns (uint) { int(price.divideDecimal(previousPrice)) - 1; int abs = int(price.divideDecimal(previousPrice)) - int(SafeDecimalMath.unit()); abs = abs > 0 ? abs : -abs; - int priceDifferential = abs - int(threshold()); + int priceDifferential = abs - int(threshold); return priceDifferential > 0 ? uint(priceDifferential) : uint(0); } /// @notice Calculate Price Weight /// @param round A round number that go back from /// the current round from 0 to N + /// @param weightDecay Weight decay constant /// @return uint price weight - function getPriceWeight(uint round) public pure returns (uint) { - return weightDecay().powDecimal(round); + function getPriceWeight(uint round, uint weightDecay) public pure returns (uint) { + return weightDecay.powDecimal(round); } /// @notice Calculate dynamic fee based on preceding 10 price differential /// @param prices A list of prices from the current round to the previous rounds + /// @param threshold A threshold to determine the price differential + /// @param weightDecay A weight decay constant /// @return uint dynamic fee - function getDynamicFee(uint[] memory prices) public pure returns (uint dynamicFee) { + function getDynamicFee( + uint[] memory prices, + uint threshold, + uint weightDecay + ) public pure returns (uint dynamicFee) { uint size = prices.length; require(size >= 2, "Not enough prices"); for (uint i = 0; i < size - 1; i++) { - uint priceDifferential = getPriceDifferential(prices[i], prices[i + 1]); - uint priceWeight = getPriceWeight(i); + uint priceDifferential = getPriceDifferential(prices[i], prices[i + 1], threshold); + uint priceWeight = getPriceWeight(i, weightDecay); dynamicFee = dynamicFee.add(priceDifferential.multiplyDecimal(priceWeight)); } } diff --git a/contracts/Exchanger.sol b/contracts/Exchanger.sol index b6ae2e371d..e01c4be72f 100644 --- a/contracts/Exchanger.sol +++ b/contracts/Exchanger.sol @@ -8,6 +8,7 @@ import "./interfaces/IExchanger.sol"; // Libraries import "./SafeDecimalMath.sol"; +import "./DynamicFee.sol"; // Internal references import "./interfaces/ISystemStatus.sol"; @@ -715,6 +716,11 @@ contract Exchanger is Owned, MixinSystemSettings, IExchanger { return timestamp.add(_waitingPeriodSecs).sub(now); } + /* ========== Exchange Related Fees ========== */ + /// @notice public function to get the fee for a given exchange + /// @param sourceCurrencyKey The source currency key + /// @param destinationCurrencyKey The destination currency key + /// @return The exchange fee rate function feeRateForExchange(bytes32 sourceCurrencyKey, bytes32 destinationCurrencyKey) external view @@ -723,6 +729,10 @@ contract Exchanger is Owned, MixinSystemSettings, IExchanger { exchangeFeeRate = _feeRateForExchange(sourceCurrencyKey, destinationCurrencyKey); } + /// @notice Calculate the exchange fee for a given source and destination currency key + /// @param sourceCurrencyKey The source currency key + /// @param destinationCurrencyKey The destination currency key + /// @return The exchange fee rate function _feeRateForExchange(bytes32 sourceCurrencyKey, bytes32 destinationCurrencyKey) internal view @@ -731,9 +741,8 @@ contract Exchanger is Owned, MixinSystemSettings, IExchanger { // Get the exchange fee rate as per destination currencyKey exchangeFeeRate = getExchangeFeeRate(destinationCurrencyKey); - if (sourceCurrencyKey == sUSD || destinationCurrencyKey == sUSD) { - return exchangeFeeRate; - } + // Get the exchange dynamic fee rate as per destination currencyKey + uint exchangeDynamicFeeRate = _getDynamicFeeForExchange(destinationCurrencyKey); // Is this a swing trade? long to short or short to long skipping sUSD. if ( @@ -742,9 +751,24 @@ contract Exchanger is Owned, MixinSystemSettings, IExchanger { ) { // Double the exchange fee exchangeFeeRate = exchangeFeeRate.mul(2); + + // Double the exchange dynamic fee + exchangeDynamicFeeRate = exchangeDynamicFeeRate.mul(2); } - return exchangeFeeRate; + return exchangeFeeRate.add(exchangeDynamicFeeRate); + } + + /// @notice Get dynamic fee for a given currency key + /// @param currencyKey The given currency key + /// @return The dyanmic fee + function _getDynamicFeeForExchange(bytes32 currencyKey) internal view returns (uint dynamicFee) { + uint threshold = getExchangeDynamicFeeThreshold(); + uint weightDecay = getExchangeDynamicFeeWeightDecay(); + uint rounds = getExchangeDynamicFeeRounds(); + uint[] memory prices; + (prices, ) = exchangeRates().ratesAndUpdatedTimeForCurrencyLastNRounds(currencyKey, rounds); + dynamicFee = DynamicFee.getDynamicFee(prices, threshold, weightDecay); } function getAmountsForExchange( diff --git a/contracts/MixinSystemSettings.sol b/contracts/MixinSystemSettings.sol index f352c6e570..8e6dc6ef55 100644 --- a/contracts/MixinSystemSettings.sol +++ b/contracts/MixinSystemSettings.sol @@ -18,7 +18,11 @@ contract MixinSystemSettings is MixinResolver { bytes32 internal constant SETTING_LIQUIDATION_RATIO = "liquidationRatio"; bytes32 internal constant SETTING_LIQUIDATION_PENALTY = "liquidationPenalty"; bytes32 internal constant SETTING_RATE_STALE_PERIOD = "rateStalePeriod"; + /* ========== Exchange Fees Related ========== */ bytes32 internal constant SETTING_EXCHANGE_FEE_RATE = "exchangeFeeRate"; + bytes32 internal constant SETTING_DYNAMIC_FEE_THRESHOLD = "dynamicFeeThreshold"; + bytes32 internal constant SETTING_DYNAMIC_FEE_WEIGHT_DECAY = "dynamicFeeWeightDecay"; + bytes32 internal constant SETTING_DYNAMIC_FEE_ROUNDS = "dynamicFeeRounds"; bytes32 internal constant SETTING_MINIMUM_STAKE_TIME = "minimumStakeTime"; bytes32 internal constant SETTING_AGGREGATOR_WARNING_FLAGS = "aggregatorWarningFlags"; bytes32 internal constant SETTING_TRADING_REWARDS_ENABLED = "tradingRewardsEnabled"; @@ -111,6 +115,7 @@ contract MixinSystemSettings is MixinResolver { return flexibleStorage().getUIntValue(SETTING_CONTRACT_NAME, SETTING_RATE_STALE_PERIOD); } + /* ========== Exchange Related Fees ========== */ function getExchangeFeeRate(bytes32 currencyKey) internal view returns (uint) { return flexibleStorage().getUIntValue( @@ -119,6 +124,24 @@ contract MixinSystemSettings is MixinResolver { ); } + /// @notice Get exchange dynamic fee threshold constant default 40bps + /// @return uint threshold constant + function getExchangeDynamicFeeThreshold() internal view returns (uint) { + return flexibleStorage().getUIntValue(SETTING_CONTRACT_NAME, SETTING_DYNAMIC_FEE_THRESHOLD); + } + + /// @notice Get exchange dynamic fee weight decay constant default 0.9 + /// @return uint weight decay constant + function getExchangeDynamicFeeWeightDecay() internal view returns (uint) { + return flexibleStorage().getUIntValue(SETTING_CONTRACT_NAME, SETTING_DYNAMIC_FEE_WEIGHT_DECAY); + } + + /// @notice Get exchange dynamic fee rounds default to 10 rounds + /// @return uint last N round + function getExchangeDynamicFeeRounds() internal view returns (uint) { + return flexibleStorage().getUIntValue(SETTING_CONTRACT_NAME, SETTING_DYNAMIC_FEE_ROUNDS); + } + function getMinimumStakeTime() internal view returns (uint) { return flexibleStorage().getUIntValue(SETTING_CONTRACT_NAME, SETTING_MINIMUM_STAKE_TIME); } diff --git a/contracts/SystemSettings.sol b/contracts/SystemSettings.sol index 7715e765b4..1148916210 100644 --- a/contracts/SystemSettings.sol +++ b/contracts/SystemSettings.sol @@ -271,6 +271,7 @@ contract SystemSettings is Owned, MixinSystemSettings, ISystemSettings { emit RateStalePeriodUpdated(period); } + /* ========== Exchange Fees Related ========== */ function setExchangeFeeRateForSynths(bytes32[] calldata synthKeys, uint256[] calldata exchangeFeeRates) external onlyOwner @@ -287,6 +288,36 @@ contract SystemSettings is Owned, MixinSystemSettings, ISystemSettings { } } + /// @notice Set exchange dynamic fee threshold constant default 40bps + /// @return uint threshold constant + function setExchangeDynamicFeeThreshold(uint threshold) external onlyOwner { + require(threshold != 0, "Threshold cannot be 0"); + + flexibleStorage().setUIntValue(SETTING_CONTRACT_NAME, SETTING_DYNAMIC_FEE_THRESHOLD, threshold); + + emit ExchangeDynamicFeeThresholdUpdated(threshold); + } + + /// @notice Set exchange dynamic fee weight decay constant default 0.9 + /// @return uint weight decay constant + function setExchangeDynamicFeeWeightDecay(uint weightDecay) external onlyOwner { + require(weightDecay != 0, "Weight decay cannot be 0"); + + flexibleStorage().setUIntValue(SETTING_CONTRACT_NAME, SETTING_DYNAMIC_FEE_WEIGHT_DECAY, weightDecay); + + emit ExchangeDynamicFeeWeightDecayUpdated(weightDecay); + } + + /// @notice Set exchange dynamic fee last N rounds constant default to 10 + /// @return uint dynamic fee last N rounds + function setExchangeDynamicFeeRounds(uint rounds) external onlyOwner { + require(rounds != 0, "rounds cannot be 0"); + + flexibleStorage().setUIntValue(SETTING_CONTRACT_NAME, SETTING_DYNAMIC_FEE_ROUNDS, rounds); + + emit ExchangeDynamicFeeRoundsUpdated(rounds); + } + function setMinimumStakeTime(uint _seconds) external onlyOwner { require(_seconds <= MAX_MINIMUM_STAKE_TIME, "stake time exceed maximum 1 week"); flexibleStorage().setUIntValue(SETTING_CONTRACT_NAME, SETTING_MINIMUM_STAKE_TIME, _seconds); @@ -371,7 +402,11 @@ contract SystemSettings is Owned, MixinSystemSettings, ISystemSettings { event LiquidationRatioUpdated(uint newRatio); event LiquidationPenaltyUpdated(uint newPenalty); event RateStalePeriodUpdated(uint rateStalePeriod); + /* ========== Exchange Fees Related ========== */ event ExchangeFeeUpdated(bytes32 synthKey, uint newExchangeFeeRate); + event ExchangeDynamicFeeThresholdUpdated(uint dynamicFeeThreshold); + event ExchangeDynamicFeeWeightDecayUpdated(uint dynamicFeeWeightDecay); + event ExchangeDynamicFeeRoundsUpdated(uint dynamicFeeRounds); event MinimumStakeTimeUpdated(uint minimumStakeTime); event DebtSnapshotStaleTimeUpdated(uint debtSnapshotStaleTime); event AggregatorWarningFlagsUpdated(address flags); diff --git a/contracts/test-helpers/TestableDynamicFee.sol b/contracts/test-helpers/TestableDynamicFee.sol index 7b62189ef1..29fd390d5d 100644 --- a/contracts/test-helpers/TestableDynamicFee.sol +++ b/contracts/test-helpers/TestableDynamicFee.sol @@ -4,23 +4,18 @@ pragma solidity ^0.5.16; import "../DynamicFee.sol"; contract TestableDynamicFee { - function testThreshold() public pure returns (uint) { - return DynamicFee.threshold(); - } - - function testWeightDecay() public pure returns (uint) { - return DynamicFee.weightDecay(); - } + uint public threshold = 4 * 10**uint(SafeDecimalMath.decimals() - 3); + uint public weightDecay = 9 * 10**uint(SafeDecimalMath.decimals() - 1); - function testGetPriceDifferential(uint price, uint previousPrice) public pure returns (uint) { - return DynamicFee.getPriceDifferential(price, previousPrice); + function testGetPriceDifferential(uint price, uint previousPrice) public view returns (uint) { + return DynamicFee.getPriceDifferential(price, previousPrice, threshold); } - function testGetPriceWeight(uint round) public pure returns (uint) { - return DynamicFee.getPriceWeight(round); + function testGetPriceWeight(uint round) public view returns (uint) { + return DynamicFee.getPriceWeight(round, weightDecay); } - function testGetDynamicFee(uint[] memory prices) public pure returns (uint) { - return DynamicFee.getDynamicFee(prices); + function testGetDynamicFee(uint[] memory prices) public view returns (uint) { + return DynamicFee.getDynamicFee(prices, threshold, weightDecay); } } diff --git a/index.js b/index.js index e225dde049..7947cdb2ae 100644 --- a/index.js +++ b/index.js @@ -136,6 +136,9 @@ const defaults = { crypto: w3utils.toWei('0.01'), index: w3utils.toWei('0.01'), }, + DYNAMIC_FEE_THRESHOLD: w3utils.toWei('0.004'), // 40 bps + DYNAMIC_FEE_WEIGHT_DECAY: w3utils.toWei('0.9'), // dynamic fee weight decay for each round + DYNAMIC_FEE_ROUNDS: '10', // dynamic fee rounds MINIMUM_STAKE_TIME: (3600 * 24).toString(), // 1 days DEBT_SNAPSHOT_STALE_TIME: (43800).toString(), // 12 hour heartbeat + 10 minutes mining time AGGREGATOR_WARNING_FLAGS: { diff --git a/publish/src/commands/deploy/configure-system-settings.js b/publish/src/commands/deploy/configure-system-settings.js index fbd004f5ec..548d88e56c 100644 --- a/publish/src/commands/deploy/configure-system-settings.js +++ b/publish/src/commands/deploy/configure-system-settings.js @@ -298,5 +298,33 @@ module.exports = async ({ writeArg: await getDeployParameter('ETHER_WRAPPER_BURN_FEE_RATE'), comment: 'Set the fee rate for burning sETH for ETH in the EtherWrapper (SIP-112)', }); + /* ========== Exchange Fees Related ========== */ + await runStep({ + contract: 'SystemSettings', + target: SystemSettings, + read: 'getExchangeDynamicFeeThreshold', + expected: input => input !== '0', // only change if zero + write: 'setExchangeDynamicFeeThreshold', + writeArg: await getDeployParameter('DYNAMIC_FEE_THRESHOLD'), + comment: 'Set exchange dynamic fee threshold (SIP-184)', + }); + await runStep({ + contract: 'SystemSettings', + target: SystemSettings, + read: 'getExchangeDynamicFeeWeightDecay', + expected: input => input !== '0', // only change if zero + write: 'setExchangeDynamicFeeWeightDecay', + writeArg: await getDeployParameter('DYNAMIC_FEE_WEIGHT_DECAY'), + comment: 'Set exchange dynamic fee weight decay (SIP-184)', + }); + await runStep({ + contract: 'SystemSettings', + target: SystemSettings, + read: 'getExchangeDynamicFeeRounds', + expected: input => input !== '0', // only change if zero + write: 'setExchangeDynamicFeeRounds', + writeArg: await getDeployParameter('DYNAMIC_FEE_WEIGHT_DECAY'), + comment: 'Set exchange dynamic fee weight decay (SIP-184)', + }); } }; diff --git a/test/contracts/TestableDynamicFee.js b/test/contracts/TestableDynamicFee.js index 18afcad85b..3a86fcfca5 100644 --- a/test/contracts/TestableDynamicFee.js +++ b/test/contracts/TestableDynamicFee.js @@ -9,21 +9,13 @@ contract('TestableDynamicFee', () => { let testableDynamicFee; before(async () => { - DynamicFee.link(await SafeDecimalMath.new()); + const safeDecimalMath = await SafeDecimalMath.new(); + DynamicFee.link(safeDecimalMath); + TestableDynamicFee.link(safeDecimalMath); TestableDynamicFee.link(await DynamicFee.new()); testableDynamicFee = await TestableDynamicFee.new(); }); - it('Can get threshold', async () => { - const threshold = await testableDynamicFee.testThreshold(); - assert.bnEqual(threshold, toUnit('0.004')); - }); - - it('Can get weight decay', async () => { - const weightDecay = await testableDynamicFee.testWeightDecay(); - assert.bnEqual(weightDecay, toUnit('0.9')); - }); - it('Can get price differential', async () => { const priceDiff = await testableDynamicFee.testGetPriceDifferential( toUnit('102'), From 8b1d49109d0d95705966384e5301558e7a18ab51 Mon Sep 17 00:00:00 2001 From: Lecky Date: Sat, 23 Oct 2021 00:05:37 +1100 Subject: [PATCH 013/133] fix(test): updateRates for rounds - Adding require check for price; - Adding setup for test; --- contracts/DynamicFee.sol | 2 ++ test/contracts/TradingRewards.spec.js | 13 +++++++++---- test/contracts/setup.js | 18 +++++++++++++++++- 3 files changed, 28 insertions(+), 5 deletions(-) diff --git a/contracts/DynamicFee.sol b/contracts/DynamicFee.sol index a568eff7a7..084cd1fa35 100644 --- a/contracts/DynamicFee.sol +++ b/contracts/DynamicFee.sol @@ -19,6 +19,8 @@ library DynamicFee { uint previousPrice, uint threshold ) public pure returns (uint) { + require(price > 0, "Price cannot be 0"); + require(previousPrice > 0, "Previous price cannot be 0"); int(price.divideDecimal(previousPrice)) - 1; int abs = int(price.divideDecimal(previousPrice)) - int(SafeDecimalMath.unit()); diff --git a/test/contracts/TradingRewards.spec.js b/test/contracts/TradingRewards.spec.js index cbb4b3aa68..98f46040ca 100644 --- a/test/contracts/TradingRewards.spec.js +++ b/test/contracts/TradingRewards.spec.js @@ -4,7 +4,10 @@ const { assert, addSnapshotBeforeRestoreAfter } = require('./common'); const { setupAllContracts } = require('./setup'); const { currentTime, toUnit, multiplyDecimal } = require('../utils')(); const { setExchangeFeeRateForSynths, getDecodedLogs, decodedEventEqual } = require('./helpers'); -const { toBytes32 } = require('../..'); +const { + toBytes32, + defaults: { DYNAMIC_FEE_ROUNDS }, +} = require('../..'); /* * This tests the TradingRewards contract's integration @@ -103,9 +106,11 @@ contract('TradingRewards', accounts => { const oracle = account1; const timestamp = await currentTime(); - await exchangeRates.updateRates([sETH, sBTC], Object.values(rates), timestamp, { - from: oracle, - }); + for (let i = 0; i < DYNAMIC_FEE_ROUNDS; i++) { + await exchangeRates.updateRates([sETH, sBTC], Object.values(rates), timestamp, { + from: oracle, + }); + } await setExchangeFeeRateForSynths({ owner, diff --git a/test/contracts/setup.js b/test/contracts/setup.js index b29437f797..a8aa806ab4 100644 --- a/test/contracts/setup.js +++ b/test/contracts/setup.js @@ -18,6 +18,9 @@ const { LIQUIDATION_RATIO, LIQUIDATION_PENALTY, RATE_STALE_PERIOD, + DYNAMIC_FEE_THRESHOLD, + DYNAMIC_FEE_WEIGHT_DECAY, + DYNAMIC_FEE_ROUNDS, MINIMUM_STAKE_TIME, DEBT_SNAPSHOT_STALE_TIME, CROSS_DOMAIN_DEPOSIT_GAS_LIMIT, @@ -121,7 +124,13 @@ const setupContract = async ({ // if it needs library linking if (Object.keys((await artifacts.readArtifact(source || contract)).linkReferences).length > 0) { - await artifact.link(await artifacts.require('SafeDecimalMath').new()); + const safeDecimalMath = await artifacts.require('SafeDecimalMath').new(); + await artifact.link(safeDecimalMath); + if (contract === 'Exchanger') { + const DynamicFee = artifacts.require('DynamicFee'); + DynamicFee.link(safeDecimalMath); + await artifact.link(await DynamicFee.new()); + } } const tryGetAddressOf = name => (cache[name] ? cache[name].address : ZERO_ADDRESS); @@ -1013,6 +1022,13 @@ const setupAllContracts = async ({ returnObj['SystemSettings'].setLiquidationRatio(LIQUIDATION_RATIO, { from: owner }), returnObj['SystemSettings'].setLiquidationPenalty(LIQUIDATION_PENALTY, { from: owner }), returnObj['SystemSettings'].setRateStalePeriod(RATE_STALE_PERIOD, { from: owner }), + returnObj['SystemSettings'].setExchangeDynamicFeeThreshold(DYNAMIC_FEE_THRESHOLD, { + from: owner, + }), + returnObj['SystemSettings'].setExchangeDynamicFeeWeightDecay(DYNAMIC_FEE_WEIGHT_DECAY, { + from: owner, + }), + returnObj['SystemSettings'].setExchangeDynamicFeeRounds(DYNAMIC_FEE_ROUNDS, { from: owner }), returnObj['SystemSettings'].setMinimumStakeTime(MINIMUM_STAKE_TIME, { from: owner }), returnObj['SystemSettings'].setDebtSnapshotStaleTime(DEBT_SNAPSHOT_STALE_TIME, { from: owner, From ff48fb9725ac7d483b83fa2068a0528b1667d08e Mon Sep 17 00:00:00 2001 From: Lecky Date: Mon, 25 Oct 2021 15:48:42 +1100 Subject: [PATCH 014/133] fix(test): Adding dynamic fee to 0 for sUSD - Adding more rounds of price feeds for test; - Adding dynamic fee in systemSettings in test --- contracts/Exchanger.sol | 4 ++++ test/contracts/SynthUtil.js | 9 ++++++--- test/contracts/SystemSettings.js | 3 +++ 3 files changed, 13 insertions(+), 3 deletions(-) diff --git a/contracts/Exchanger.sol b/contracts/Exchanger.sol index e01c4be72f..cc6b6afb0f 100644 --- a/contracts/Exchanger.sol +++ b/contracts/Exchanger.sol @@ -763,6 +763,10 @@ contract Exchanger is Owned, MixinSystemSettings, IExchanger { /// @param currencyKey The given currency key /// @return The dyanmic fee function _getDynamicFeeForExchange(bytes32 currencyKey) internal view returns (uint dynamicFee) { + // No dynamic fee for sUSD + if (currencyKey == sUSD) { + return 0; + } uint threshold = getExchangeDynamicFeeThreshold(); uint weightDecay = getExchangeDynamicFeeWeightDecay(); uint rounds = getExchangeDynamicFeeRounds(); diff --git a/test/contracts/SynthUtil.js b/test/contracts/SynthUtil.js index 9231c90968..fc99b1d3a9 100644 --- a/test/contracts/SynthUtil.js +++ b/test/contracts/SynthUtil.js @@ -4,6 +4,7 @@ const { contract } = require('hardhat'); const { assert, addSnapshotBeforeRestoreAfterEach } = require('./common'); const { toBytes32, + defaults: { DYNAMIC_FEE_ROUNDS }, constants: { ZERO_BYTES32 }, } = require('../..'); const { toUnit, currentTime } = require('../utils')(); @@ -51,9 +52,11 @@ contract('SynthUtil', accounts => { beforeEach(async () => { timestamp = await currentTime(); - await exchangeRates.updateRates([sBTC, iBTC], ['5000', '5000'].map(toUnit), timestamp, { - from: oracle, - }); + for (let i = 0; i < DYNAMIC_FEE_ROUNDS; i++) { + await exchangeRates.updateRates([sBTC, iBTC], ['5000', '5000'].map(toUnit), timestamp, { + from: oracle, + }); + } await debtCache.takeDebtSnapshot(); // set a 0% default exchange fee rate for test purpose diff --git a/test/contracts/SystemSettings.js b/test/contracts/SystemSettings.js index f2faead937..f00d3ba282 100644 --- a/test/contracts/SystemSettings.js +++ b/test/contracts/SystemSettings.js @@ -73,6 +73,9 @@ contract('SystemSettings', async accounts => { 'setCrossDomainMessageGasLimit', 'setEtherWrapperMaxETH', 'setEtherWrapperMintFeeRate', + 'setExchangeDynamicFeeThreshold', + 'setExchangeDynamicFeeWeightDecay', + 'setExchangeDynamicFeeRounds', 'setEtherWrapperBurnFeeRate', 'setMinCratio', 'setCollateralManager', From 849583ac8284eb8433a1204a9480a68263f966a6 Mon Sep 17 00:00:00 2001 From: Lecky Date: Mon, 25 Oct 2021 18:08:57 +1100 Subject: [PATCH 015/133] Fixing more tests and adding in more descriptions --- contracts/DynamicFee.sol | 10 ++++++---- contracts/Exchanger.sol | 2 +- test/contracts/helpers.js | 23 ++++++++++++++--------- 3 files changed, 21 insertions(+), 14 deletions(-) diff --git a/contracts/DynamicFee.sol b/contracts/DynamicFee.sol index 084cd1fa35..e89d119f75 100644 --- a/contracts/DynamicFee.sol +++ b/contracts/DynamicFee.sol @@ -9,11 +9,14 @@ library DynamicFee { using Math for uint; using SafeMath for uint; - /// @notice Calculate price differential + /// @notice Calculate price differential - + /// The difference between the current price and the previous price /// @param price Current round price /// @param previousPrice Previous round price - /// @param threshold Threshold constant - /// @return uint price differential + /// @param threshold Threshold constant - + /// A system constant for the price differential default to 40 bps + /// @return uint price differential with 18 decimals + /// only return if non-zero value, otherwise return 0 function getPriceDifferential( uint price, uint previousPrice, @@ -21,7 +24,6 @@ library DynamicFee { ) public pure returns (uint) { require(price > 0, "Price cannot be 0"); require(previousPrice > 0, "Previous price cannot be 0"); - int(price.divideDecimal(previousPrice)) - 1; int abs = int(price.divideDecimal(previousPrice)) - int(SafeDecimalMath.unit()); abs = abs > 0 ? abs : -abs; diff --git a/contracts/Exchanger.sol b/contracts/Exchanger.sol index cc6b6afb0f..a89a6d3e1d 100644 --- a/contracts/Exchanger.sol +++ b/contracts/Exchanger.sol @@ -759,7 +759,7 @@ contract Exchanger is Owned, MixinSystemSettings, IExchanger { return exchangeFeeRate.add(exchangeDynamicFeeRate); } - /// @notice Get dynamic fee for a given currency key + /// @notice Get dynamic fee for a given currency key (SIP-184) /// @param currencyKey The given currency key /// @return The dyanmic fee function _getDynamicFeeForExchange(bytes32 currencyKey) internal view returns (uint dynamicFee) { diff --git a/test/contracts/helpers.js b/test/contracts/helpers.js index cc8b6d205b..541232089d 100644 --- a/test/contracts/helpers.js +++ b/test/contracts/helpers.js @@ -6,7 +6,10 @@ const { smockit } = require('@eth-optimism/smock'); const { assert } = require('./common'); const { currentTime, toUnit } = require('../utils')(); -const { toBytes32 } = require('../..'); +const { + toBytes32, + defaults: { DYNAMIC_FEE_ROUNDS }, +} = require('../..'); module.exports = { /** @@ -93,14 +96,16 @@ module.exports = { 'ETH', ].map(toBytes32); - await exchangeRates.updateRates( - [SNX, sAUD, sEUR, sBTC, iBTC, sETH, ETH], - ['0.1', '0.5', '1.25', '5000', '4000', '172', '172'].map(toUnit), - timestamp, - { - from: oracle, - } - ); + for (let i = 0; i < DYNAMIC_FEE_ROUNDS; i++) { + await exchangeRates.updateRates( + [SNX, sAUD, sEUR, sBTC, iBTC, sETH, ETH], + ['0.1', '0.5', '1.25', '5000', '4000', '172', '172'].map(toUnit), + timestamp, + { + from: oracle, + } + ); + } await debtCache.takeDebtSnapshot(); }, From 5cd88ec04385eda3888ef95d67c365f220a20eef Mon Sep 17 00:00:00 2001 From: Lecky Date: Tue, 26 Oct 2021 18:04:32 +1100 Subject: [PATCH 016/133] fix test by adding rounds for price feeds --- test/contracts/Issuer.js | 24 +++++++++++++---------- test/contracts/RewardsIntegrationTests.js | 23 +++++++++++++--------- 2 files changed, 28 insertions(+), 19 deletions(-) diff --git a/test/contracts/Issuer.js b/test/contracts/Issuer.js index 2c9c17b821..77e077efd2 100644 --- a/test/contracts/Issuer.js +++ b/test/contracts/Issuer.js @@ -30,7 +30,7 @@ const { const { toBytes32, constants: { ZERO_ADDRESS }, - defaults: { ISSUANCE_RATIO, MINIMUM_STAKE_TIME }, + defaults: { ISSUANCE_RATIO, MINIMUM_STAKE_TIME, DYNAMIC_FEE_ROUNDS }, } = require('../..'); contract('Issuer (via Synthetix)', async accounts => { @@ -118,12 +118,14 @@ contract('Issuer (via Synthetix)', async accounts => { beforeEach(async () => { timestamp = await currentTime(); - await exchangeRates.updateRates( - [sAUD, sEUR, SNX, sETH], - ['0.5', '1.25', '0.1', '200'].map(toUnit), - timestamp, - { from: oracle } - ); + for (let i = 0; i < DYNAMIC_FEE_ROUNDS; i++) { + await exchangeRates.updateRates( + [sAUD, sEUR, SNX, sETH], + ['0.5', '1.25', '0.1', '200'].map(toUnit), + timestamp, + { from: oracle } + ); + } // set a 0.3% default exchange fee rate const exchangeFeeRate = toUnit('0.003'); @@ -719,9 +721,11 @@ contract('Issuer (via Synthetix)', async accounts => { }); describe('when the synth has a rate', () => { beforeEach(async () => { - await exchangeRates.updateRates([currencyKey], [toUnit('2')], timestamp, { - from: oracle, - }); + for (let i = 0; i < DYNAMIC_FEE_ROUNDS; i++) { + await exchangeRates.updateRates([currencyKey], [toUnit('2')], timestamp, { + from: oracle, + }); + } }); describe('when another user exchanges into the synth', () => { diff --git a/test/contracts/RewardsIntegrationTests.js b/test/contracts/RewardsIntegrationTests.js index 64415c28dd..7f85ed110c 100644 --- a/test/contracts/RewardsIntegrationTests.js +++ b/test/contracts/RewardsIntegrationTests.js @@ -4,7 +4,10 @@ const { contract, web3 } = require('hardhat'); const { assert, addSnapshotBeforeRestoreAfterEach } = require('./common'); -const { toBytes32 } = require('../..'); +const { + toBytes32, + defaults: { DYNAMIC_FEE_ROUNDS }, +} = require('../..'); const { currentTime, fastForward, toUnit, toPreciseUnit, multiplyDecimal } = require('../utils')(); @@ -64,14 +67,16 @@ contract('Rewards Integration Tests', accounts => { const updateRatesWithDefaults = async () => { const timestamp = await currentTime(); - await exchangeRates.updateRates( - [sAUD, sEUR, SNX, sBTC, iBTC, sETH, ETH], - ['0.5', '1.25', '0.1', '5000', '4000', '172', '172'].map(toUnit), - timestamp, - { - from: oracle, - } - ); + for (let i = 0; i < DYNAMIC_FEE_ROUNDS; i++) { + await exchangeRates.updateRates( + [sAUD, sEUR, SNX, sBTC, iBTC, sETH, ETH], + ['0.5', '1.25', '0.1', '5000', '4000', '172', '172'].map(toUnit), + timestamp, + { + from: oracle, + } + ); + } await debtCache.takeDebtSnapshot(); }; From 89b78c61eae8a578ef9385e26560d3bb540f05cb Mon Sep 17 00:00:00 2001 From: Lecky Date: Wed, 27 Oct 2021 14:35:01 +1100 Subject: [PATCH 017/133] fixed FeePool test - removed fastForward as startTime and Now is not equal --- test/contracts/FeePool.js | 14 ++++++-------- 1 file changed, 6 insertions(+), 8 deletions(-) diff --git a/test/contracts/FeePool.js b/test/contracts/FeePool.js index 0ff86d0f86..2c2c7412cf 100644 --- a/test/contracts/FeePool.js +++ b/test/contracts/FeePool.js @@ -30,7 +30,7 @@ const { setupAllContracts } = require('./setup'); const { toBytes32, - defaults: { ISSUANCE_RATIO, FEE_PERIOD_DURATION, TARGET_THRESHOLD }, + defaults: { ISSUANCE_RATIO, FEE_PERIOD_DURATION, TARGET_THRESHOLD, DYNAMIC_FEE_ROUNDS }, } = require('../..'); contract('FeePool', async accounts => { @@ -40,9 +40,11 @@ contract('FeePool', async accounts => { const updateRatesWithDefaults = async () => { const timestamp = await currentTime(); - await exchangeRates.updateRates([sAUD, SNX], ['0.5', '0.1'].map(toUnit), timestamp, { - from: oracle, - }); + for (let i = 0; i < DYNAMIC_FEE_ROUNDS; i++) { + await exchangeRates.updateRates([sAUD, SNX], ['0.5', '0.1'].map(toUnit), timestamp, { + from: oracle, + }); + } await debtCache.takeDebtSnapshot(); }; @@ -751,13 +753,9 @@ contract('FeePool', async accounts => { }); it('should disallow closing the current fee period too early', async () => { - const feePeriodDuration = await feePool.feePeriodDuration(); - // Close the current one so we know exactly what we're dealing with await closeFeePeriod(); - // Try to close the new fee period 5 seconds early - await fastForward(feePeriodDuration.sub(web3.utils.toBN('5'))); await assert.revert( feePool.closeCurrentFeePeriod({ from: account1 }), 'Too early to close fee period' From c56aa8b6d959821d66b91b2c474c2fee73cbf1b7 Mon Sep 17 00:00:00 2001 From: Lecky Date: Wed, 27 Oct 2021 16:37:51 +1100 Subject: [PATCH 018/133] fixing ExchangerWithVirtualSynth link in setup.js --- test/contracts/setup.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/test/contracts/setup.js b/test/contracts/setup.js index a8aa806ab4..dc2285a301 100644 --- a/test/contracts/setup.js +++ b/test/contracts/setup.js @@ -126,7 +126,8 @@ const setupContract = async ({ if (Object.keys((await artifacts.readArtifact(source || contract)).linkReferences).length > 0) { const safeDecimalMath = await artifacts.require('SafeDecimalMath').new(); await artifact.link(safeDecimalMath); - if (contract === 'Exchanger') { + // eslint-disable-next-line no-constant-condition + if (contract === 'Exchanger' || 'ExchangerWithVirtualSynth') { const DynamicFee = artifacts.require('DynamicFee'); DynamicFee.link(safeDecimalMath); await artifact.link(await DynamicFee.new()); From 5302a44cd058da97fcafe6c7c3ebcdd50b4605eb Mon Sep 17 00:00:00 2001 From: Lecky Date: Wed, 27 Oct 2021 16:55:13 +1100 Subject: [PATCH 019/133] fixing setup library linking --- test/contracts/setup.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/test/contracts/setup.js b/test/contracts/setup.js index dc2285a301..6542d57d5f 100644 --- a/test/contracts/setup.js +++ b/test/contracts/setup.js @@ -127,7 +127,8 @@ const setupContract = async ({ const safeDecimalMath = await artifacts.require('SafeDecimalMath').new(); await artifact.link(safeDecimalMath); // eslint-disable-next-line no-constant-condition - if (contract === 'Exchanger' || 'ExchangerWithVirtualSynth') { + if (contract === ('Exchanger' || 'ExchangerWithVirtualSynth')) { + console.log('contract: ', contract); const DynamicFee = artifacts.require('DynamicFee'); DynamicFee.link(safeDecimalMath); await artifact.link(await DynamicFee.new()); From 8aa8c5e5e0ff25504f9487a2fc0dbe5f9d47c1f9 Mon Sep 17 00:00:00 2001 From: Lecky Date: Wed, 27 Oct 2021 21:42:24 +1100 Subject: [PATCH 020/133] fixing setup in test --- test/contracts/setup.js | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/test/contracts/setup.js b/test/contracts/setup.js index 6542d57d5f..2a32694196 100644 --- a/test/contracts/setup.js +++ b/test/contracts/setup.js @@ -127,8 +127,7 @@ const setupContract = async ({ const safeDecimalMath = await artifacts.require('SafeDecimalMath').new(); await artifact.link(safeDecimalMath); // eslint-disable-next-line no-constant-condition - if (contract === ('Exchanger' || 'ExchangerWithVirtualSynth')) { - console.log('contract: ', contract); + if (/Exchanger|ExchangerWithVirtualSynth/.test(contract)) { const DynamicFee = artifacts.require('DynamicFee'); DynamicFee.link(safeDecimalMath); await artifact.link(await DynamicFee.new()); From 34b92afb387c2b6199dea770ff4d931fe5744f02 Mon Sep 17 00:00:00 2001 From: Lecky Date: Wed, 3 Nov 2021 21:04:40 +1100 Subject: [PATCH 021/133] fixing exhcnager test - Rename priceWeight to RoundDecay - adding cap 100% for max exchangeFeeRate - Added return for invalid rate - linking libraries in setup --- contracts/DynamicFee.sol | 18 +++--- contracts/Exchanger.sol | 12 ++++ contracts/test-helpers/TestableDynamicFee.sol | 2 +- test/contracts/Exchanger.spec.js | 64 +++++++++++-------- test/contracts/setup.js | 5 +- test/utils/index.js | 4 +- 6 files changed, 63 insertions(+), 42 deletions(-) diff --git a/contracts/DynamicFee.sol b/contracts/DynamicFee.sol index e89d119f75..be6672dd5a 100644 --- a/contracts/DynamicFee.sol +++ b/contracts/DynamicFee.sol @@ -31,12 +31,12 @@ library DynamicFee { return priceDifferential > 0 ? uint(priceDifferential) : uint(0); } - /// @notice Calculate Price Weight - /// @param round A round number that go back from - /// the current round from 0 to N + /// @notice Calculate decay based on round + /// @param round A round number that go back + /// from the current round from 0 to N /// @param weightDecay Weight decay constant - /// @return uint price weight - function getPriceWeight(uint round, uint weightDecay) public pure returns (uint) { + /// @return uint decay with 18 decimals + function getRoundDecay(uint round, uint weightDecay) public pure returns (uint) { return weightDecay.powDecimal(round); } @@ -52,10 +52,10 @@ library DynamicFee { ) public pure returns (uint dynamicFee) { uint size = prices.length; require(size >= 2, "Not enough prices"); - for (uint i = 0; i < size - 1; i++) { - uint priceDifferential = getPriceDifferential(prices[i], prices[i + 1], threshold); - uint priceWeight = getPriceWeight(i, weightDecay); - dynamicFee = dynamicFee.add(priceDifferential.multiplyDecimal(priceWeight)); + for (uint i = prices.length - 1; i > 0; i--) { + uint priceDifferential = getPriceDifferential(prices[i - 1], prices[i], threshold); + uint roundDecay = getRoundDecay(i, weightDecay); + dynamicFee = (dynamicFee.multiplyDecimal(roundDecay)).add(priceDifferential); } } } diff --git a/contracts/Exchanger.sol b/contracts/Exchanger.sol index a89a6d3e1d..fb590cc601 100644 --- a/contracts/Exchanger.sol +++ b/contracts/Exchanger.sol @@ -744,6 +744,10 @@ contract Exchanger is Owned, MixinSystemSettings, IExchanger { // Get the exchange dynamic fee rate as per destination currencyKey uint exchangeDynamicFeeRate = _getDynamicFeeForExchange(destinationCurrencyKey); + if (sourceCurrencyKey == sUSD || destinationCurrencyKey == sUSD) { + return exchangeFeeRate.add(exchangeDynamicFeeRate); + } + // Is this a swing trade? long to short or short to long skipping sUSD. if ( (sourceCurrencyKey[0] == 0x73 && destinationCurrencyKey[0] == 0x69) || @@ -816,6 +820,12 @@ contract Exchanger is Owned, MixinSystemSettings, IExchanger { sourceAmount, destinationCurrencyKey ); + + // Return when invalid rate + if (destinationAmount == 0 && destinationRate == 0) { + return (destinationAmount, 0, 0, sourceRate, destinationRate); + } + exchangeFeeRate = _feeRateForExchange(sourceCurrencyKey, destinationCurrencyKey); amountReceived = _getAmountReceivedForExchange(destinationAmount, exchangeFeeRate); fee = destinationAmount.sub(amountReceived); @@ -826,6 +836,8 @@ contract Exchanger is Owned, MixinSystemSettings, IExchanger { pure returns (uint amountReceived) { + // Cap max exchangeFeeRate to 100% + exchangeFeeRate = exchangeFeeRate > SafeDecimalMath.unit() ? SafeDecimalMath.unit() : exchangeFeeRate; amountReceived = destinationAmount.multiplyDecimal(SafeDecimalMath.unit().sub(exchangeFeeRate)); } diff --git a/contracts/test-helpers/TestableDynamicFee.sol b/contracts/test-helpers/TestableDynamicFee.sol index 29fd390d5d..36dd57a630 100644 --- a/contracts/test-helpers/TestableDynamicFee.sol +++ b/contracts/test-helpers/TestableDynamicFee.sol @@ -12,7 +12,7 @@ contract TestableDynamicFee { } function testGetPriceWeight(uint round) public view returns (uint) { - return DynamicFee.getPriceWeight(round, weightDecay); + return DynamicFee.getRoundDecay(round, weightDecay); } function testGetDynamicFee(uint[] memory prices) public view returns (uint) { diff --git a/test/contracts/Exchanger.spec.js b/test/contracts/Exchanger.spec.js index 6556970252..77599ffc09 100644 --- a/test/contracts/Exchanger.spec.js +++ b/test/contracts/Exchanger.spec.js @@ -22,7 +22,7 @@ const { const { toBytes32, - defaults: { WAITING_PERIOD_SECS, PRICE_DEVIATION_THRESHOLD_FACTOR }, + defaults: { WAITING_PERIOD_SECS, PRICE_DEVIATION_THRESHOLD_FACTOR, DYNAMIC_FEE_ROUNDS }, } = require('../..'); const BN = require('bn.js'); @@ -2333,7 +2333,7 @@ contract('Exchanger (spec tests)', async accounts => { const balanceFromExchange = prevBalance ? balance.sub(prevBalance) : balance; - assert.bnEqual(balanceFromExchange, effectiveValueMinusFees); + assert.bnClose(balanceFromExchange, effectiveValueMinusFees); // check logs const synthExchangeEvent = txn.logs.find( @@ -2344,7 +2344,7 @@ contract('Exchanger (spec tests)', async accounts => { fromCurrencyKey: from, fromAmount: amountExchanged, toCurrencyKey: to, - toAmount: effectiveValueMinusFees, + toAmount: balanceFromExchange, toAddress: account1, }); }; @@ -2667,7 +2667,9 @@ contract('Exchanger (spec tests)', async accounts => { aggregator = await MockAggregator.new({ from: owner }); await exchangeRates.addAggregator(sETH, aggregator.address, { from: owner }); // set a 0 rate to prevent invalid rate from causing a revert on exchange - await aggregator.setLatestAnswer('0', await currentTime()); + for (let i = 0; i < DYNAMIC_FEE_ROUNDS; i++) { + await aggregator.setLatestAnswer('0', await currentTime()); + } }); describe('when exchanging into that synth', () => { @@ -3563,7 +3565,8 @@ contract('Exchanger (spec tests)', async accounts => { rebateAmount, numEntries, } = await exchanger.settlementOwing(account1, sETH); - assert.bnClose(reclaimAmount, toUnit('0.25'), (1e16).toString()); + // Reclaim dropped from 0.25 to 0.2 as fees increased by introducing dynamic fee + assert.bnClose(reclaimAmount, toUnit('0.2'), (1e16).toString()); assert.bnClose(rebateAmount, toUnit('0.25'), (1e16).toString()); assert.equal(numEntries, '2'); }); @@ -3582,7 +3585,8 @@ contract('Exchanger (spec tests)', async accounts => { rebateAmount, numEntries, } = await exchanger.settlementOwing(account1, sETH); - assert.bnClose(reclaimAmount, toUnit('0.25'), (1e16).toString()); + // Reclaim dropped from 0.25 to 0.2 as fees increased by introducing dynamic fee + assert.bnClose(reclaimAmount, toUnit('0.2'), (1e16).toString()); assert.bnClose(rebateAmount, toUnit('0.25'), (1e16).toString()); assert.equal(numEntries, '3'); }); @@ -3607,10 +3611,12 @@ contract('Exchanger (spec tests)', async accounts => { describe('and the aggregator has a rate (so the exchange succeeds)', () => { beforeEach(async () => { - await aggregator.setLatestAnswer( - convertToAggregatorPrice(100), - await currentTime() - ); + for (let i = 0; i < DYNAMIC_FEE_ROUNDS; i++) { + await aggregator.setLatestAnswer( + convertToAggregatorPrice(100), + await currentTime() + ); + } }); describe('when a user exchanges out of the aggregated rate into sUSD', () => { beforeEach(async () => { @@ -3879,15 +3885,17 @@ contract('Exchanger (spec tests)', async accounts => { addSnapshotBeforeRestoreAfterEach(); beforeEach(async () => { - timestamp = await currentTime(); - await exchangeRates.updateRates( - [sAUD, sEUR, SNX, sETH, sBTC, iBTC], - ['0.5', '2', '1', '100', '5000', '5000'].map(toUnit), - timestamp, - { - from: oracle, - } - ); + for (let i = 0; i < DYNAMIC_FEE_ROUNDS; i++) { + timestamp = await currentTime(); + await exchangeRates.updateRates( + [sAUD, sEUR, SNX, sETH, sBTC, iBTC], + ['0.5', '2', '1', '100', '5000', '5000'].map(toUnit), + timestamp, + { + from: oracle, + } + ); + } // set a 0.5% exchange fee rate (1/200) exchangeFeeRate = toUnit('0.005'); @@ -3981,14 +3989,16 @@ contract('Exchanger (spec tests)', async accounts => { beforeEach(async () => { timestamp = await currentTime(); - await exchangeRates.updateRates( - [sAUD, sEUR, SNX, sETH, sBTC, iBTC], - ['0.5', '2', '1', '100', '5000', '5000'].map(toUnit), - timestamp, - { - from: oracle, - } - ); + for (let i = 0; i < DYNAMIC_FEE_ROUNDS; i++) { + await exchangeRates.updateRates( + [sAUD, sEUR, SNX, sETH, sBTC, iBTC], + ['0.5', '2', '1', '100', '5000', '5000'].map(toUnit), + timestamp, + { + from: oracle, + } + ); + } // set a 0.5% exchange fee rate (1/200) exchangeFeeRate = toUnit('0.005'); diff --git a/test/contracts/setup.js b/test/contracts/setup.js index 7c579f887d..bf14ce63b3 100644 --- a/test/contracts/setup.js +++ b/test/contracts/setup.js @@ -122,12 +122,11 @@ const setupContract = async ({ ); }; - // if it needs library linking + // Linking library if needed if (Object.keys((await artifacts.readArtifact(source || contract)).linkReferences).length > 0) { const safeDecimalMath = await artifacts.require('SafeDecimalMath').new(); await artifact.link(safeDecimalMath); - // eslint-disable-next-line no-constant-condition - if (/Exchanger|ExchangerWithVirtualSynth/.test(contract)) { + if (/^Exchanger$|^ExchangerWithVirtualSynth$/.test(artifact._json.contractName)) { const DynamicFee = artifacts.require('DynamicFee'); DynamicFee.link(safeDecimalMath); await artifact.link(await DynamicFee.new()); diff --git a/test/utils/index.js b/test/utils/index.js index 54f251eaa1..abba588897 100644 --- a/test/utils/index.js +++ b/test/utils/index.js @@ -371,11 +371,11 @@ module.exports = ({ web3 } = {}) => { assert.ok( actual.gte(expected.sub(variance)), - `Number is too small to be close (Delta between actual and expected is ${actualDelta.toString()}, but variance was only ${variance.toString()}` + `${expected.toString()} is too small to be close (actual is ${actualDelta.toString()}, but variance was only ${variance.toString()}` ); assert.ok( actual.lte(expected.add(variance)), - `Number is too large to be close (Delta between actual and expected is ${actualDelta.toString()}, but variance was only ${variance.toString()})` + `${expected.toString()} is too large to be close (actual is ${actualDelta.toString()}, but variance was only ${variance.toString()})` ); }; From 76451fe6b02765ad2b0f34d6786ad84bc77caeb6 Mon Sep 17 00:00:00 2001 From: Lecky Date: Wed, 3 Nov 2021 21:24:54 +1100 Subject: [PATCH 022/133] use iBTC as destinationCurrency as it got rate. --- test/contracts/Exchanger.spec.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/contracts/Exchanger.spec.js b/test/contracts/Exchanger.spec.js index 77599ffc09..43b434bbee 100644 --- a/test/contracts/Exchanger.spec.js +++ b/test/contracts/Exchanger.spec.js @@ -456,7 +456,7 @@ contract('Exchanger (spec tests)', async accounts => { assert.bnEqual(actualFeeRate, exchangeFeeRate, 'Rate must be the exchange fee rate'); }); it('for two inverse synths, returns the regular exchange fee', async () => { - const actualFeeRate = await exchanger.feeRateForExchange(iBTC, iETH); + const actualFeeRate = await exchanger.feeRateForExchange(iETH, iBTC); assert.bnEqual(actualFeeRate, exchangeFeeRate, 'Rate must be the exchange fee rate'); }); it('for an inverse synth and sUSD, returns the regular exchange fee', async () => { From f30f8e3baff49444ce92778b49952a4170ade6df Mon Sep 17 00:00:00 2001 From: Lecky Date: Mon, 8 Nov 2021 15:11:07 +1100 Subject: [PATCH 023/133] move cap max exchange fee into exchange --- contracts/Exchanger.sol | 11 +++++++---- test/utils/index.js | 4 ++-- 2 files changed, 9 insertions(+), 6 deletions(-) diff --git a/contracts/Exchanger.sol b/contracts/Exchanger.sol index fb590cc601..76e0cc4ed4 100644 --- a/contracts/Exchanger.sol +++ b/contracts/Exchanger.sol @@ -745,7 +745,10 @@ contract Exchanger is Owned, MixinSystemSettings, IExchanger { uint exchangeDynamicFeeRate = _getDynamicFeeForExchange(destinationCurrencyKey); if (sourceCurrencyKey == sUSD || destinationCurrencyKey == sUSD) { - return exchangeFeeRate.add(exchangeDynamicFeeRate); + exchangeFeeRate = exchangeFeeRate.add(exchangeDynamicFeeRate); + // Cap max exchangeFeeRate to 100% + exchangeFeeRate = exchangeFeeRate > SafeDecimalMath.unit() ? SafeDecimalMath.unit() : exchangeFeeRate; + return exchangeFeeRate; } // Is this a swing trade? long to short or short to long skipping sUSD. @@ -760,7 +763,9 @@ contract Exchanger is Owned, MixinSystemSettings, IExchanger { exchangeDynamicFeeRate = exchangeDynamicFeeRate.mul(2); } - return exchangeFeeRate.add(exchangeDynamicFeeRate); + exchangeFeeRate = exchangeFeeRate.add(exchangeDynamicFeeRate); + // Cap max exchangeFeeRate to 100% + exchangeFeeRate = exchangeFeeRate > SafeDecimalMath.unit() ? SafeDecimalMath.unit() : exchangeFeeRate; } /// @notice Get dynamic fee for a given currency key (SIP-184) @@ -836,8 +841,6 @@ contract Exchanger is Owned, MixinSystemSettings, IExchanger { pure returns (uint amountReceived) { - // Cap max exchangeFeeRate to 100% - exchangeFeeRate = exchangeFeeRate > SafeDecimalMath.unit() ? SafeDecimalMath.unit() : exchangeFeeRate; amountReceived = destinationAmount.multiplyDecimal(SafeDecimalMath.unit().sub(exchangeFeeRate)); } diff --git a/test/utils/index.js b/test/utils/index.js index abba588897..c83711eedf 100644 --- a/test/utils/index.js +++ b/test/utils/index.js @@ -371,11 +371,11 @@ module.exports = ({ web3 } = {}) => { assert.ok( actual.gte(expected.sub(variance)), - `${expected.toString()} is too small to be close (actual is ${actualDelta.toString()}, but variance was only ${variance.toString()}` + `Number is too small to be close (actual is ${actualDelta.toString()}, but variance was only ${variance.toString()}` ); assert.ok( actual.lte(expected.add(variance)), - `${expected.toString()} is too large to be close (actual is ${actualDelta.toString()}, but variance was only ${variance.toString()})` + `Number is too large to be close (actual is ${actualDelta.toString()}, but variance was only ${variance.toString()})` ); }; From 5a30543411e065a4dc223657f8c89a90a1b912f5 Mon Sep 17 00:00:00 2001 From: Lecky Date: Mon, 8 Nov 2021 17:56:53 +1100 Subject: [PATCH 024/133] fixing exchanger.spec.js - disable dynamic fee when price feeds less than 2 rounds - disable dynamic fee on exchanger spec settle --- contracts/DynamicFee.sol | 5 ++++- contracts/SystemSettings.sol | 7 ++++--- test/contracts/Exchanger.spec.js | 5 +++++ 3 files changed, 13 insertions(+), 4 deletions(-) diff --git a/contracts/DynamicFee.sol b/contracts/DynamicFee.sol index be6672dd5a..d50fac8f38 100644 --- a/contracts/DynamicFee.sol +++ b/contracts/DynamicFee.sol @@ -51,7 +51,10 @@ library DynamicFee { uint weightDecay ) public pure returns (uint dynamicFee) { uint size = prices.length; - require(size >= 2, "Not enough prices"); + // 0/disable dynamic fee when price feeds less than 2 rounds + if (size < 2) { + return dynamicFee; + } for (uint i = prices.length - 1; i > 0; i--) { uint priceDifferential = getPriceDifferential(prices[i - 1], prices[i], threshold); uint roundDecay = getRoundDecay(i, weightDecay); diff --git a/contracts/SystemSettings.sol b/contracts/SystemSettings.sol index 813bb89e7a..cbd3597e05 100644 --- a/contracts/SystemSettings.sol +++ b/contracts/SystemSettings.sol @@ -327,7 +327,8 @@ contract SystemSettings is Owned, MixinSystemSettings, ISystemSettings { /// @notice Set exchange dynamic fee last N rounds constant default to 10 /// @return uint dynamic fee last N rounds function setExchangeDynamicFeeRounds(uint rounds) external onlyOwner { - require(rounds != 0, "rounds cannot be 0"); + // Allowing to be 0 as a flag to disable Dynamic Fee + // require(rounds != 0, "rounds cannot be 0"); flexibleStorage().setUIntValue(SETTING_CONTRACT_NAME, SETTING_DYNAMIC_FEE_ROUNDS, rounds); @@ -380,7 +381,7 @@ contract SystemSettings is Owned, MixinSystemSettings, ISystemSettings { function setWrapperMintFeeRate(address _wrapper, int _rate) external onlyOwner { require(_rate <= MAX_WRAPPER_MINT_FEE_RATE, "rate > MAX_WRAPPER_MINT_FEE_RATE"); require(_rate >= -MAX_WRAPPER_MINT_FEE_RATE, "rate < -MAX_WRAPPER_MINT_FEE_RATE"); - + // if mint rate is negative, burn fee rate should be positive and at least equal in magnitude // otherwise risk of flash loan attack if (_rate < 0) { @@ -398,7 +399,7 @@ contract SystemSettings is Owned, MixinSystemSettings, ISystemSettings { function setWrapperBurnFeeRate(address _wrapper, int _rate) external onlyOwner { require(_rate <= MAX_WRAPPER_BURN_FEE_RATE, "rate > MAX_WRAPPER_BURN_FEE_RATE"); require(_rate >= -MAX_WRAPPER_BURN_FEE_RATE, "rate < -MAX_WRAPPER_BURN_FEE_RATE"); - + // if burn rate is negative, burn fee rate should be negative and at least equal in magnitude // otherwise risk of flash loan attack if (_rate < 0) { diff --git a/test/contracts/Exchanger.spec.js b/test/contracts/Exchanger.spec.js index 43b434bbee..fdec5b13fb 100644 --- a/test/contracts/Exchanger.spec.js +++ b/test/contracts/Exchanger.spec.js @@ -210,6 +210,7 @@ contract('Exchanger (spec tests)', async accounts => { new web3.utils.BN(1), new web3.utils.BN(2), ], + bnCloseVariance, }); }); @@ -690,6 +691,8 @@ contract('Exchanger (spec tests)', async accounts => { describe(`when ${section} is suspended`, () => { beforeEach(async () => { await setStatus({ owner, systemStatus, section, suspend: true, synth }); + // Disable Dynamic Fee by setting rounds to 0 + await systemSettings.setExchangeDynamicFeeRounds('0', { from: owner }); }); it('then calling settle() reverts', async () => { await assert.revert( @@ -742,6 +745,8 @@ contract('Exchanger (spec tests)', async accounts => { from: oracle, } ); + // Disable Dynamic Fee by setting rounds to 0 + await systemSettings.setExchangeDynamicFeeRounds('0', { from: owner }); }); describe('and the exchange fee rate is 1% for easier human consumption', () => { beforeEach(async () => { From c636a2838327b84039b2f07bc1d98be90b5a72b8 Mon Sep 17 00:00:00 2001 From: Lecky Date: Mon, 8 Nov 2021 21:20:12 +1100 Subject: [PATCH 025/133] fixed ExchangerWithVirtualSynth linking in test --- test/contracts/ExchangerWithVirtualSynth.behaviors.js | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/test/contracts/ExchangerWithVirtualSynth.behaviors.js b/test/contracts/ExchangerWithVirtualSynth.behaviors.js index 5debf51fd7..142d2e3c76 100644 --- a/test/contracts/ExchangerWithVirtualSynth.behaviors.js +++ b/test/contracts/ExchangerWithVirtualSynth.behaviors.js @@ -6,10 +6,14 @@ const { toBytes32 } = require('../..'); const { prepareSmocks } = require('./helpers'); let ExchangerWithVirtualSynth; +let SafeDecimalMath; +let DynamicFee; module.exports = function({ accounts }) { before(async () => { ExchangerWithVirtualSynth = artifacts.require('ExchangerWithVirtualSynth'); + SafeDecimalMath = artifacts.require('SafeDecimalMath'); + DynamicFee = artifacts.require('DynamicFee'); }); beforeEach(async () => { @@ -37,7 +41,10 @@ module.exports = function({ accounts }) { }); before(async () => { - ExchangerWithVirtualSynth.link(await artifacts.require('SafeDecimalMath').new()); + const safeDecimalMath = await SafeDecimalMath.new(); + DynamicFee.link(safeDecimalMath); + ExchangerWithVirtualSynth.link(safeDecimalMath); + ExchangerWithVirtualSynth.link(await DynamicFee.new()); }); return { From 15669025db54d497f6cf5b18f5ab34f091b40879 Mon Sep 17 00:00:00 2001 From: Lecky Date: Tue, 9 Nov 2021 13:38:25 +1100 Subject: [PATCH 026/133] fixing baseSynthetix by reducing the price change --- test/contracts/BaseSynthetix.js | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/test/contracts/BaseSynthetix.js b/test/contracts/BaseSynthetix.js index 2613fed687..4af304b72e 100644 --- a/test/contracts/BaseSynthetix.js +++ b/test/contracts/BaseSynthetix.js @@ -846,7 +846,7 @@ contract('BaseSynthetix', async accounts => { it("should lock newly received synthetix if the user's collaterisation is too high", async () => { // Set sEUR for purposes of this test const timestamp1 = await currentTime(); - await exchangeRates.updateRates([sEUR], [toUnit('0.75')], timestamp1, { from: oracle }); + await exchangeRates.updateRates([sEUR], [toUnit('1')], timestamp1, { from: oracle }); await debtCache.takeDebtSnapshot(); const issuedSynthetixs = web3.utils.toBN('200000'); @@ -891,7 +891,7 @@ contract('BaseSynthetix', async accounts => { // Set sAUD for purposes of this test const timestamp1 = await currentTime(); - const aud2usdrate = toUnit('2'); + const aud2usdrate = toUnit('1'); await exchangeRates.updateRates([sAUD], [aud2usdrate], timestamp1, { from: oracle }); await debtCache.takeDebtSnapshot(); @@ -916,12 +916,12 @@ contract('BaseSynthetix', async accounts => { // Increase the value of sAUD relative to synthetix const timestamp2 = await currentTime(); - const newAUDExchangeRate = toUnit('1'); + const newAUDExchangeRate = toUnit('0.75'); await exchangeRates.updateRates([sAUD], [newAUDExchangeRate], timestamp2, { from: oracle }); await debtCache.takeDebtSnapshot(); const transferable2 = await baseSynthetix.transferableSynthetix(account1); - assert.equal(transferable2.gt(toUnit('1000')), true); + assert.equal(transferable2.gt(toUnit('100')), true); }); describe('when the user has issued some sUSD and exchanged for other synths', () => { From 574c0ffd4aaec1aa6995e13f7bdf0a9969196797 Mon Sep 17 00:00:00 2001 From: Lecky Date: Tue, 9 Nov 2021 13:48:22 +1100 Subject: [PATCH 027/133] setting DYNAMIC_FEE_ROUNDS to 0 for L1 to disable dynamicFee --- publish/deployed/mainnet/params.json | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/publish/deployed/mainnet/params.json b/publish/deployed/mainnet/params.json index fe51488c70..2507f5be86 100644 --- a/publish/deployed/mainnet/params.json +++ b/publish/deployed/mainnet/params.json @@ -1 +1,6 @@ -[] +[ + { + "name": "DYNAMIC_FEE_ROUNDS", + "value": "0" + } +] From 88afd1c00a55757a39bc2e8125f21c56327af4e7 Mon Sep 17 00:00:00 2001 From: Lecky Date: Tue, 9 Nov 2021 15:03:05 +1100 Subject: [PATCH 028/133] fix debtCache test - adding 10 rounds price feed - disable dynamicFee as well when calculating debts --- test/contracts/DebtCache.js | 23 ++++++++++++++++------- 1 file changed, 16 insertions(+), 7 deletions(-) diff --git a/test/contracts/DebtCache.js b/test/contracts/DebtCache.js index 75de4c34fc..1aee3f3ac8 100644 --- a/test/contracts/DebtCache.js +++ b/test/contracts/DebtCache.js @@ -19,7 +19,7 @@ const { const { toBytes32, - defaults: { DEBT_SNAPSHOT_STALE_TIME }, + defaults: { DEBT_SNAPSHOT_STALE_TIME, DYNAMIC_FEE_ROUNDS }, constants: { ZERO_ADDRESS }, } = require('../..'); @@ -286,12 +286,14 @@ contract('DebtCache', async accounts => { beforeEach(async () => { timestamp = await currentTime(); - await exchangeRates.updateRates( - [sAUD, sEUR, SNX, sETH, ETH, iETH], - ['0.5', '1.25', '10', '200', '200', '200'].map(toUnit), - timestamp, - { from: oracle } - ); + for (let i = 0; i < DYNAMIC_FEE_ROUNDS; i++) { + await exchangeRates.updateRates( + [sAUD, sEUR, SNX, sETH, ETH, iETH], + ['0.5', '1.25', '10', '200', '200', '200'].map(toUnit), + timestamp, + { from: oracle } + ); + } // set a 0.3% default exchange fee rate const exchangeFeeRate = toUnit('0.003'); @@ -988,9 +990,12 @@ contract('DebtCache', async accounts => { }); it('exchanging between synths updates debt properly when prices have changed', async () => { + // Zero exchange fees so that we can neglect them. await systemSettings.setExchangeFeeRateForSynths([sAUD, sUSD], [toUnit(0), toUnit(0)], { from: owner, }); + // Disable Dynamic fee so that we can neglect it. + await systemSettings.setExchangeDynamicFeeRounds('0', { from: owner }); await sEURContract.issue(account1, toUnit(20)); await debtCache.takeDebtSnapshot(); @@ -1014,9 +1019,13 @@ contract('DebtCache', async accounts => { }); it('settlement updates debt totals', async () => { + // Zero exchange fees so that we can neglect them. await systemSettings.setExchangeFeeRateForSynths([sAUD, sEUR], [toUnit(0), toUnit(0)], { from: owner, }); + // Disable Dynamic fee so that we can neglect it. + await systemSettings.setExchangeDynamicFeeRounds('0', { from: owner }); + await sAUDContract.issue(account1, toUnit(100)); await debtCache.takeDebtSnapshot(); From 6cc96fc17ff58be0dd525e9cb9418d5df1eb8ce1 Mon Sep 17 00:00:00 2001 From: Lecky Date: Thu, 11 Nov 2021 16:53:11 +1100 Subject: [PATCH 029/133] Adding exchangeDynamicFeeRate into ExchangeState --- contracts/ExchangeState.sol | 4 ++ contracts/Exchanger.sol | 61 +++++++++++++++++-------- contracts/interfaces/IExchangeState.sol | 3 ++ test/contracts/ExchangeState.js | 15 +++++- 4 files changed, 63 insertions(+), 20 deletions(-) diff --git a/contracts/ExchangeState.sol b/contracts/ExchangeState.sol index 2b3d0796c2..182d1f1f96 100644 --- a/contracts/ExchangeState.sol +++ b/contracts/ExchangeState.sol @@ -28,6 +28,7 @@ contract ExchangeState is Owned, State, IExchangeState { bytes32 dest, uint amountReceived, uint exchangeFeeRate, + uint exchangeDynamicFeeRate, uint timestamp, uint roundIdForSrc, uint roundIdForDest @@ -41,6 +42,7 @@ contract ExchangeState is Owned, State, IExchangeState { dest: dest, amountReceived: amountReceived, exchangeFeeRate: exchangeFeeRate, + exchangeDynamicFeeRate: exchangeDynamicFeeRate, timestamp: timestamp, roundIdForSrc: roundIdForSrc, roundIdForDest: roundIdForDest @@ -71,6 +73,7 @@ contract ExchangeState is Owned, State, IExchangeState { bytes32 dest, uint amountReceived, uint exchangeFeeRate, + uint exchangeDynamicFeeRate, uint timestamp, uint roundIdForSrc, uint roundIdForDest @@ -83,6 +86,7 @@ contract ExchangeState is Owned, State, IExchangeState { entry.dest, entry.amountReceived, entry.exchangeFeeRate, + entry.exchangeDynamicFeeRate, entry.timestamp, entry.roundIdForSrc, entry.roundIdForDest diff --git a/contracts/Exchanger.sol b/contracts/Exchanger.sol index 76e0cc4ed4..063ca3a9d9 100644 --- a/contracts/Exchanger.sol +++ b/contracts/Exchanger.sol @@ -258,6 +258,7 @@ contract Exchanger is Owned, MixinSystemSettings, IExchanger { bytes32 dest, uint amountReceived, uint exchangeFeeRate, + uint exchangeDynamicFeeRate, uint timestamp, uint roundIdForSrc, uint roundIdForDest @@ -270,6 +271,7 @@ contract Exchanger is Owned, MixinSystemSettings, IExchanger { dest: dest, amountReceived: amountReceived, exchangeFeeRate: exchangeFeeRate, + exchangeDynamicFeeRate: exchangeDynamicFeeRate, timestamp: timestamp, roundIdForSrc: roundIdForSrc, roundIdForDest: roundIdForDest @@ -426,21 +428,34 @@ contract Exchanger is Owned, MixinSystemSettings, IExchanger { return (0, 0, IVirtualSynth(0)); } - uint exchangeFeeRate; - uint sourceRate; - uint destinationRate; + // Using struct ExchangeEntry here to fix stack too deep error + IExchangeState.ExchangeEntry memory entry = + IExchangeState.ExchangeEntry({ + src: 0, + amount: 0, + dest: 0, + amountReceived: 0, + exchangeFeeRate: 0, + exchangeDynamicFeeRate: 0, + timestamp: 0, + roundIdForSrc: 0, // sourceRate + roundIdForDest: 0 // destinationRate + }); // Note: `fee` is denominated in the destinationCurrencyKey. - (amountReceived, fee, exchangeFeeRate, sourceRate, destinationRate) = _getAmountsForExchangeMinusFees( - sourceAmountAfterSettlement, - sourceCurrencyKey, - destinationCurrencyKey - ); + ( + amountReceived, + fee, + entry.exchangeFeeRate, + entry.exchangeDynamicFeeRate, + entry.roundIdForSrc, + entry.roundIdForDest + ) = _getAmountsForExchangeMinusFees(sourceAmountAfterSettlement, sourceCurrencyKey, destinationCurrencyKey); // SIP-65: Decentralized Circuit Breaker if ( - _suspendIfRateInvalid(sourceCurrencyKey, sourceRate) || - _suspendIfRateInvalid(destinationCurrencyKey, destinationRate) + _suspendIfRateInvalid(sourceCurrencyKey, entry.roundIdForSrc) || + _suspendIfRateInvalid(destinationCurrencyKey, entry.roundIdForDest) ) { return (0, 0, IVirtualSynth(0)); } @@ -481,7 +496,10 @@ contract Exchanger is Owned, MixinSystemSettings, IExchanger { // Nothing changes as far as issuance data goes because the total value in the system hasn't changed. // But we will update the debt snapshot in case exchange rates have fluctuated since the last exchange // in these currencies - _updateSNXIssuedDebtOnExchange([sourceCurrencyKey, destinationCurrencyKey], [sourceRate, destinationRate]); + _updateSNXIssuedDebtOnExchange( + [sourceCurrencyKey, destinationCurrencyKey], + [entry.roundIdForSrc, entry.roundIdForDest] + ); // Let the DApps know there was a Synth exchange ISynthetixInternal(address(synthetix())).emitSynthExchange( @@ -502,7 +520,8 @@ contract Exchanger is Owned, MixinSystemSettings, IExchanger { sourceAmountAfterSettlement, destinationCurrencyKey, amountReceived, - exchangeFeeRate + entry.exchangeFeeRate, + entry.exchangeDynamicFeeRate ); } } @@ -726,17 +745,18 @@ contract Exchanger is Owned, MixinSystemSettings, IExchanger { view returns (uint exchangeFeeRate) { - exchangeFeeRate = _feeRateForExchange(sourceCurrencyKey, destinationCurrencyKey); + (exchangeFeeRate, ) = _feeRateForExchange(sourceCurrencyKey, destinationCurrencyKey); } /// @notice Calculate the exchange fee for a given source and destination currency key /// @param sourceCurrencyKey The source currency key /// @param destinationCurrencyKey The destination currency key /// @return The exchange fee rate + /// @return The exchange dynamic fee rate function _feeRateForExchange(bytes32 sourceCurrencyKey, bytes32 destinationCurrencyKey) internal view - returns (uint exchangeFeeRate) + returns (uint exchangeFeeRate, uint exchangeDynamicFeeRate) { // Get the exchange fee rate as per destination currencyKey exchangeFeeRate = getExchangeFeeRate(destinationCurrencyKey); @@ -748,7 +768,7 @@ contract Exchanger is Owned, MixinSystemSettings, IExchanger { exchangeFeeRate = exchangeFeeRate.add(exchangeDynamicFeeRate); // Cap max exchangeFeeRate to 100% exchangeFeeRate = exchangeFeeRate > SafeDecimalMath.unit() ? SafeDecimalMath.unit() : exchangeFeeRate; - return exchangeFeeRate; + return (exchangeFeeRate, exchangeDynamicFeeRate); } // Is this a swing trade? long to short or short to long skipping sUSD. @@ -797,7 +817,7 @@ contract Exchanger is Owned, MixinSystemSettings, IExchanger { uint exchangeFeeRate ) { - (amountReceived, fee, exchangeFeeRate, , ) = _getAmountsForExchangeMinusFees( + (amountReceived, fee, exchangeFeeRate, , , ) = _getAmountsForExchangeMinusFees( sourceAmount, sourceCurrencyKey, destinationCurrencyKey @@ -815,6 +835,7 @@ contract Exchanger is Owned, MixinSystemSettings, IExchanger { uint amountReceived, uint fee, uint exchangeFeeRate, + uint exchangeDynamicFeeRate, uint sourceRate, uint destinationRate ) @@ -828,10 +849,10 @@ contract Exchanger is Owned, MixinSystemSettings, IExchanger { // Return when invalid rate if (destinationAmount == 0 && destinationRate == 0) { - return (destinationAmount, 0, 0, sourceRate, destinationRate); + return (destinationAmount, 0, 0, 0, sourceRate, destinationRate); } - exchangeFeeRate = _feeRateForExchange(sourceCurrencyKey, destinationCurrencyKey); + (exchangeFeeRate, exchangeDynamicFeeRate) = _feeRateForExchange(sourceCurrencyKey, destinationCurrencyKey); amountReceived = _getAmountReceivedForExchange(destinationAmount, exchangeFeeRate); fee = destinationAmount.sub(amountReceived); } @@ -850,7 +871,8 @@ contract Exchanger is Owned, MixinSystemSettings, IExchanger { uint amount, bytes32 dest, uint amountReceived, - uint exchangeFeeRate + uint exchangeFeeRate, + uint exchangeDynamicFeeRate ) internal { IExchangeRates exRates = exchangeRates(); uint roundIdForSrc = exRates.getCurrentRoundId(src); @@ -862,6 +884,7 @@ contract Exchanger is Owned, MixinSystemSettings, IExchanger { dest, amountReceived, exchangeFeeRate, + exchangeDynamicFeeRate, now, roundIdForSrc, roundIdForDest diff --git a/contracts/interfaces/IExchangeState.sol b/contracts/interfaces/IExchangeState.sol index 58c4c71dee..253970a611 100644 --- a/contracts/interfaces/IExchangeState.sol +++ b/contracts/interfaces/IExchangeState.sol @@ -9,6 +9,7 @@ interface IExchangeState { bytes32 dest; uint amountReceived; uint exchangeFeeRate; + uint exchangeDynamicFeeRate; uint timestamp; uint roundIdForSrc; uint roundIdForDest; @@ -29,6 +30,7 @@ interface IExchangeState { bytes32 dest, uint amountReceived, uint exchangeFeeRate, + uint exchangeDynamicFeeRate, uint timestamp, uint roundIdForSrc, uint roundIdForDest @@ -44,6 +46,7 @@ interface IExchangeState { bytes32 dest, uint amountReceived, uint exchangeFeeRate, + uint exchangeDynamicFeeRate, uint timestamp, uint roundIdForSrc, uint roundIdForDest diff --git a/test/contracts/ExchangeState.js b/test/contracts/ExchangeState.js index a818eb1bf2..83dbd9ad1d 100644 --- a/test/contracts/ExchangeState.js +++ b/test/contracts/ExchangeState.js @@ -38,6 +38,7 @@ contract('ExchangeState', accounts => { dest = sBTC, amountReceived = toUnit('99'), exchangeFeeRate = toUnit('0.01'), + exchangeDynamicFeeRate = toUnit('0.01'), timestamp = '0', roundIdForSrc = '0', roundIdForDest = '0', @@ -49,6 +50,7 @@ contract('ExchangeState', accounts => { dest, amountReceived, exchangeFeeRate, + exchangeDynamicFeeRate, timestamp, roundIdForSrc, roundIdForDest, @@ -94,7 +96,18 @@ contract('ExchangeState', accounts => { it('only the associated contract can invoke appendExchangeEntry()', async () => { await onlyGivenAddressCanInvoke({ fnc: exchangeState.appendExchangeEntry, - args: [account1, sUSD, toUnit('1'), sBTC, toUnit('1'), toUnit('0.01'), '0', '0', '0'], + args: [ + account1, + sUSD, + toUnit('1'), + sBTC, + toUnit('1'), + toUnit('0.01'), + toUnit('0.01'), + '0', + '0', + '0', + ], address: simulatedAssociatedContract, accounts, }); From d762939dc6dddcb71b9fc49f6f147cced5bb9ee9 Mon Sep 17 00:00:00 2001 From: Lecky Date: Thu, 11 Nov 2021 17:03:23 +1100 Subject: [PATCH 030/133] remove duplicated uint definition. --- contracts/Exchanger.sol | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/contracts/Exchanger.sol b/contracts/Exchanger.sol index 063ca3a9d9..100294b2fd 100644 --- a/contracts/Exchanger.sol +++ b/contracts/Exchanger.sol @@ -762,7 +762,7 @@ contract Exchanger is Owned, MixinSystemSettings, IExchanger { exchangeFeeRate = getExchangeFeeRate(destinationCurrencyKey); // Get the exchange dynamic fee rate as per destination currencyKey - uint exchangeDynamicFeeRate = _getDynamicFeeForExchange(destinationCurrencyKey); + exchangeDynamicFeeRate = _getDynamicFeeForExchange(destinationCurrencyKey); if (sourceCurrencyKey == sUSD || destinationCurrencyKey == sUSD) { exchangeFeeRate = exchangeFeeRate.add(exchangeDynamicFeeRate); From 3f2b1aed5830b85f5af67b254dd4886a5b82f4f4 Mon Sep 17 00:00:00 2001 From: Lecky Date: Thu, 11 Nov 2021 17:23:37 +1100 Subject: [PATCH 031/133] Revert "Adding exchangeDynamicFeeRate into ExchangeState" This reverts commit 6cc96fc17ff58be0dd525e9cb9418d5df1eb8ce1. --- contracts/ExchangeState.sol | 4 -- contracts/Exchanger.sol | 61 ++++++++----------------- contracts/interfaces/IExchangeState.sol | 3 -- test/contracts/ExchangeState.js | 15 +----- 4 files changed, 20 insertions(+), 63 deletions(-) diff --git a/contracts/ExchangeState.sol b/contracts/ExchangeState.sol index 182d1f1f96..2b3d0796c2 100644 --- a/contracts/ExchangeState.sol +++ b/contracts/ExchangeState.sol @@ -28,7 +28,6 @@ contract ExchangeState is Owned, State, IExchangeState { bytes32 dest, uint amountReceived, uint exchangeFeeRate, - uint exchangeDynamicFeeRate, uint timestamp, uint roundIdForSrc, uint roundIdForDest @@ -42,7 +41,6 @@ contract ExchangeState is Owned, State, IExchangeState { dest: dest, amountReceived: amountReceived, exchangeFeeRate: exchangeFeeRate, - exchangeDynamicFeeRate: exchangeDynamicFeeRate, timestamp: timestamp, roundIdForSrc: roundIdForSrc, roundIdForDest: roundIdForDest @@ -73,7 +71,6 @@ contract ExchangeState is Owned, State, IExchangeState { bytes32 dest, uint amountReceived, uint exchangeFeeRate, - uint exchangeDynamicFeeRate, uint timestamp, uint roundIdForSrc, uint roundIdForDest @@ -86,7 +83,6 @@ contract ExchangeState is Owned, State, IExchangeState { entry.dest, entry.amountReceived, entry.exchangeFeeRate, - entry.exchangeDynamicFeeRate, entry.timestamp, entry.roundIdForSrc, entry.roundIdForDest diff --git a/contracts/Exchanger.sol b/contracts/Exchanger.sol index 100294b2fd..a64d9a088f 100644 --- a/contracts/Exchanger.sol +++ b/contracts/Exchanger.sol @@ -258,7 +258,6 @@ contract Exchanger is Owned, MixinSystemSettings, IExchanger { bytes32 dest, uint amountReceived, uint exchangeFeeRate, - uint exchangeDynamicFeeRate, uint timestamp, uint roundIdForSrc, uint roundIdForDest @@ -271,7 +270,6 @@ contract Exchanger is Owned, MixinSystemSettings, IExchanger { dest: dest, amountReceived: amountReceived, exchangeFeeRate: exchangeFeeRate, - exchangeDynamicFeeRate: exchangeDynamicFeeRate, timestamp: timestamp, roundIdForSrc: roundIdForSrc, roundIdForDest: roundIdForDest @@ -428,34 +426,21 @@ contract Exchanger is Owned, MixinSystemSettings, IExchanger { return (0, 0, IVirtualSynth(0)); } - // Using struct ExchangeEntry here to fix stack too deep error - IExchangeState.ExchangeEntry memory entry = - IExchangeState.ExchangeEntry({ - src: 0, - amount: 0, - dest: 0, - amountReceived: 0, - exchangeFeeRate: 0, - exchangeDynamicFeeRate: 0, - timestamp: 0, - roundIdForSrc: 0, // sourceRate - roundIdForDest: 0 // destinationRate - }); + uint exchangeFeeRate; + uint sourceRate; + uint destinationRate; // Note: `fee` is denominated in the destinationCurrencyKey. - ( - amountReceived, - fee, - entry.exchangeFeeRate, - entry.exchangeDynamicFeeRate, - entry.roundIdForSrc, - entry.roundIdForDest - ) = _getAmountsForExchangeMinusFees(sourceAmountAfterSettlement, sourceCurrencyKey, destinationCurrencyKey); + (amountReceived, fee, exchangeFeeRate, sourceRate, destinationRate) = _getAmountsForExchangeMinusFees( + sourceAmountAfterSettlement, + sourceCurrencyKey, + destinationCurrencyKey + ); // SIP-65: Decentralized Circuit Breaker if ( - _suspendIfRateInvalid(sourceCurrencyKey, entry.roundIdForSrc) || - _suspendIfRateInvalid(destinationCurrencyKey, entry.roundIdForDest) + _suspendIfRateInvalid(sourceCurrencyKey, sourceRate) || + _suspendIfRateInvalid(destinationCurrencyKey, destinationRate) ) { return (0, 0, IVirtualSynth(0)); } @@ -496,10 +481,7 @@ contract Exchanger is Owned, MixinSystemSettings, IExchanger { // Nothing changes as far as issuance data goes because the total value in the system hasn't changed. // But we will update the debt snapshot in case exchange rates have fluctuated since the last exchange // in these currencies - _updateSNXIssuedDebtOnExchange( - [sourceCurrencyKey, destinationCurrencyKey], - [entry.roundIdForSrc, entry.roundIdForDest] - ); + _updateSNXIssuedDebtOnExchange([sourceCurrencyKey, destinationCurrencyKey], [sourceRate, destinationRate]); // Let the DApps know there was a Synth exchange ISynthetixInternal(address(synthetix())).emitSynthExchange( @@ -520,8 +502,7 @@ contract Exchanger is Owned, MixinSystemSettings, IExchanger { sourceAmountAfterSettlement, destinationCurrencyKey, amountReceived, - entry.exchangeFeeRate, - entry.exchangeDynamicFeeRate + exchangeFeeRate ); } } @@ -745,18 +726,17 @@ contract Exchanger is Owned, MixinSystemSettings, IExchanger { view returns (uint exchangeFeeRate) { - (exchangeFeeRate, ) = _feeRateForExchange(sourceCurrencyKey, destinationCurrencyKey); + exchangeFeeRate = _feeRateForExchange(sourceCurrencyKey, destinationCurrencyKey); } /// @notice Calculate the exchange fee for a given source and destination currency key /// @param sourceCurrencyKey The source currency key /// @param destinationCurrencyKey The destination currency key /// @return The exchange fee rate - /// @return The exchange dynamic fee rate function _feeRateForExchange(bytes32 sourceCurrencyKey, bytes32 destinationCurrencyKey) internal view - returns (uint exchangeFeeRate, uint exchangeDynamicFeeRate) + returns (uint exchangeFeeRate) { // Get the exchange fee rate as per destination currencyKey exchangeFeeRate = getExchangeFeeRate(destinationCurrencyKey); @@ -768,7 +748,7 @@ contract Exchanger is Owned, MixinSystemSettings, IExchanger { exchangeFeeRate = exchangeFeeRate.add(exchangeDynamicFeeRate); // Cap max exchangeFeeRate to 100% exchangeFeeRate = exchangeFeeRate > SafeDecimalMath.unit() ? SafeDecimalMath.unit() : exchangeFeeRate; - return (exchangeFeeRate, exchangeDynamicFeeRate); + return exchangeFeeRate; } // Is this a swing trade? long to short or short to long skipping sUSD. @@ -817,7 +797,7 @@ contract Exchanger is Owned, MixinSystemSettings, IExchanger { uint exchangeFeeRate ) { - (amountReceived, fee, exchangeFeeRate, , , ) = _getAmountsForExchangeMinusFees( + (amountReceived, fee, exchangeFeeRate, , ) = _getAmountsForExchangeMinusFees( sourceAmount, sourceCurrencyKey, destinationCurrencyKey @@ -835,7 +815,6 @@ contract Exchanger is Owned, MixinSystemSettings, IExchanger { uint amountReceived, uint fee, uint exchangeFeeRate, - uint exchangeDynamicFeeRate, uint sourceRate, uint destinationRate ) @@ -849,10 +828,10 @@ contract Exchanger is Owned, MixinSystemSettings, IExchanger { // Return when invalid rate if (destinationAmount == 0 && destinationRate == 0) { - return (destinationAmount, 0, 0, 0, sourceRate, destinationRate); + return (destinationAmount, 0, 0, sourceRate, destinationRate); } - (exchangeFeeRate, exchangeDynamicFeeRate) = _feeRateForExchange(sourceCurrencyKey, destinationCurrencyKey); + exchangeFeeRate = _feeRateForExchange(sourceCurrencyKey, destinationCurrencyKey); amountReceived = _getAmountReceivedForExchange(destinationAmount, exchangeFeeRate); fee = destinationAmount.sub(amountReceived); } @@ -871,8 +850,7 @@ contract Exchanger is Owned, MixinSystemSettings, IExchanger { uint amount, bytes32 dest, uint amountReceived, - uint exchangeFeeRate, - uint exchangeDynamicFeeRate + uint exchangeFeeRate ) internal { IExchangeRates exRates = exchangeRates(); uint roundIdForSrc = exRates.getCurrentRoundId(src); @@ -884,7 +862,6 @@ contract Exchanger is Owned, MixinSystemSettings, IExchanger { dest, amountReceived, exchangeFeeRate, - exchangeDynamicFeeRate, now, roundIdForSrc, roundIdForDest diff --git a/contracts/interfaces/IExchangeState.sol b/contracts/interfaces/IExchangeState.sol index 253970a611..58c4c71dee 100644 --- a/contracts/interfaces/IExchangeState.sol +++ b/contracts/interfaces/IExchangeState.sol @@ -9,7 +9,6 @@ interface IExchangeState { bytes32 dest; uint amountReceived; uint exchangeFeeRate; - uint exchangeDynamicFeeRate; uint timestamp; uint roundIdForSrc; uint roundIdForDest; @@ -30,7 +29,6 @@ interface IExchangeState { bytes32 dest, uint amountReceived, uint exchangeFeeRate, - uint exchangeDynamicFeeRate, uint timestamp, uint roundIdForSrc, uint roundIdForDest @@ -46,7 +44,6 @@ interface IExchangeState { bytes32 dest, uint amountReceived, uint exchangeFeeRate, - uint exchangeDynamicFeeRate, uint timestamp, uint roundIdForSrc, uint roundIdForDest diff --git a/test/contracts/ExchangeState.js b/test/contracts/ExchangeState.js index 83dbd9ad1d..a818eb1bf2 100644 --- a/test/contracts/ExchangeState.js +++ b/test/contracts/ExchangeState.js @@ -38,7 +38,6 @@ contract('ExchangeState', accounts => { dest = sBTC, amountReceived = toUnit('99'), exchangeFeeRate = toUnit('0.01'), - exchangeDynamicFeeRate = toUnit('0.01'), timestamp = '0', roundIdForSrc = '0', roundIdForDest = '0', @@ -50,7 +49,6 @@ contract('ExchangeState', accounts => { dest, amountReceived, exchangeFeeRate, - exchangeDynamicFeeRate, timestamp, roundIdForSrc, roundIdForDest, @@ -96,18 +94,7 @@ contract('ExchangeState', accounts => { it('only the associated contract can invoke appendExchangeEntry()', async () => { await onlyGivenAddressCanInvoke({ fnc: exchangeState.appendExchangeEntry, - args: [ - account1, - sUSD, - toUnit('1'), - sBTC, - toUnit('1'), - toUnit('0.01'), - toUnit('0.01'), - '0', - '0', - '0', - ], + args: [account1, sUSD, toUnit('1'), sBTC, toUnit('1'), toUnit('0.01'), '0', '0', '0'], address: simulatedAssociatedContract, accounts, }); From 63b494434a0b0de138448380c224eb1d10bf9438 Mon Sep 17 00:00:00 2001 From: Lecky Date: Thu, 11 Nov 2021 17:43:47 +1100 Subject: [PATCH 032/133] add missing declaration --- contracts/Exchanger.sol | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/contracts/Exchanger.sol b/contracts/Exchanger.sol index a64d9a088f..76e0cc4ed4 100644 --- a/contracts/Exchanger.sol +++ b/contracts/Exchanger.sol @@ -742,7 +742,7 @@ contract Exchanger is Owned, MixinSystemSettings, IExchanger { exchangeFeeRate = getExchangeFeeRate(destinationCurrencyKey); // Get the exchange dynamic fee rate as per destination currencyKey - exchangeDynamicFeeRate = _getDynamicFeeForExchange(destinationCurrencyKey); + uint exchangeDynamicFeeRate = _getDynamicFeeForExchange(destinationCurrencyKey); if (sourceCurrencyKey == sUSD || destinationCurrencyKey == sUSD) { exchangeFeeRate = exchangeFeeRate.add(exchangeDynamicFeeRate); From d0fe84ff219d3883938bdccf3004ea8d77df7558 Mon Sep 17 00:00:00 2001 From: Lecky Date: Sun, 14 Nov 2021 22:24:53 +1100 Subject: [PATCH 033/133] Adding lastExchangeDynamicFeeRate and lastExchangeRoundId; --- contracts/Exchanger.sol | 74 ++++++++++++++++++++++++++++++----------- 1 file changed, 55 insertions(+), 19 deletions(-) diff --git a/contracts/Exchanger.sol b/contracts/Exchanger.sol index 76e0cc4ed4..2102acc265 100644 --- a/contracts/Exchanger.sol +++ b/contracts/Exchanger.sol @@ -83,8 +83,21 @@ contract Exchanger is Owned, MixinSystemSettings, IExchanger { // SIP-65: Decentralized circuit breaker uint public constant CIRCUIT_BREAKER_SUSPENSION_REASON = 65; + /// @notice Return the last exchange rate + /// @param currencyKey is the currency key of the synth to be exchanged + /// @return the last exchange rate of the synth to sUSD mapping(bytes32 => uint) public lastExchangeRate; + /// @notice Return the last exchange dynamic fee rate + /// @param currencyKey is the currency key of the synth to be exchanged + /// @return the last exchange dynamic fee rate of the synth to sUSD + mapping(bytes32 => uint) public lastExchangeDynamicFeeRate; + + /// @notice Return the last exchange round ID + /// @param currencyKey is the currency key of the synth to be exchanged + /// @return the last exchange round ID + mapping(bytes32 => uint) public lastExchangeRoundId; + /* ========== ADDRESS RESOLVER CONFIGURATION ========== */ bytes32 private constant CONTRACT_SYSTEMSTATUS = "SystemStatus"; @@ -426,21 +439,39 @@ contract Exchanger is Owned, MixinSystemSettings, IExchanger { return (0, 0, IVirtualSynth(0)); } - uint exchangeFeeRate; - uint sourceRate; - uint destinationRate; + // Using struct ExchangeEntry here to fix stack too deep error + // Only using exchangeFeeRate,roundIdForSrc for sourceRate, roundIdForDest for destinationRate + IExchangeState.ExchangeEntry memory entry = + IExchangeState.ExchangeEntry({ + src: 0, + amount: 0, + dest: 0, + amountReceived: 0, + exchangeFeeRate: 0, + timestamp: 0, + roundIdForSrc: 0, // sourceRate + roundIdForDest: 0 // destinationRate + }); + uint exchangeDynamicFeeRate; // Note: `fee` is denominated in the destinationCurrencyKey. - (amountReceived, fee, exchangeFeeRate, sourceRate, destinationRate) = _getAmountsForExchangeMinusFees( - sourceAmountAfterSettlement, - sourceCurrencyKey, - destinationCurrencyKey - ); + ( + amountReceived, + fee, + entry.exchangeFeeRate, + exchangeDynamicFeeRate, + entry.roundIdForSrc, + entry.roundIdForDest + ) = _getAmountsForExchangeMinusFees(sourceAmountAfterSettlement, sourceCurrencyKey, destinationCurrencyKey); + + // SIP-184: Store last Exchange data in the cache to optimise the dynamic fee rate calculation + lastExchangeRoundId[destinationCurrencyKey] = exchangeRates().getCurrentRoundId(destinationCurrencyKey); + lastExchangeDynamicFeeRate[destinationCurrencyKey] = exchangeDynamicFeeRate; // SIP-65: Decentralized Circuit Breaker if ( - _suspendIfRateInvalid(sourceCurrencyKey, sourceRate) || - _suspendIfRateInvalid(destinationCurrencyKey, destinationRate) + _suspendIfRateInvalid(sourceCurrencyKey, entry.roundIdForSrc) || + _suspendIfRateInvalid(destinationCurrencyKey, entry.roundIdForDest) ) { return (0, 0, IVirtualSynth(0)); } @@ -481,7 +512,10 @@ contract Exchanger is Owned, MixinSystemSettings, IExchanger { // Nothing changes as far as issuance data goes because the total value in the system hasn't changed. // But we will update the debt snapshot in case exchange rates have fluctuated since the last exchange // in these currencies - _updateSNXIssuedDebtOnExchange([sourceCurrencyKey, destinationCurrencyKey], [sourceRate, destinationRate]); + _updateSNXIssuedDebtOnExchange( + [sourceCurrencyKey, destinationCurrencyKey], + [entry.roundIdForSrc, entry.roundIdForDest] + ); // Let the DApps know there was a Synth exchange ISynthetixInternal(address(synthetix())).emitSynthExchange( @@ -502,7 +536,7 @@ contract Exchanger is Owned, MixinSystemSettings, IExchanger { sourceAmountAfterSettlement, destinationCurrencyKey, amountReceived, - exchangeFeeRate + entry.exchangeFeeRate ); } } @@ -726,29 +760,30 @@ contract Exchanger is Owned, MixinSystemSettings, IExchanger { view returns (uint exchangeFeeRate) { - exchangeFeeRate = _feeRateForExchange(sourceCurrencyKey, destinationCurrencyKey); + (exchangeFeeRate, ) = _feeRateForExchange(sourceCurrencyKey, destinationCurrencyKey); } /// @notice Calculate the exchange fee for a given source and destination currency key /// @param sourceCurrencyKey The source currency key /// @param destinationCurrencyKey The destination currency key /// @return The exchange fee rate + /// @return The exchange dynamic fee rate function _feeRateForExchange(bytes32 sourceCurrencyKey, bytes32 destinationCurrencyKey) internal view - returns (uint exchangeFeeRate) + returns (uint exchangeFeeRate, uint exchangeDynamicFeeRate) { // Get the exchange fee rate as per destination currencyKey exchangeFeeRate = getExchangeFeeRate(destinationCurrencyKey); // Get the exchange dynamic fee rate as per destination currencyKey - uint exchangeDynamicFeeRate = _getDynamicFeeForExchange(destinationCurrencyKey); + exchangeDynamicFeeRate = _getDynamicFeeForExchange(destinationCurrencyKey); if (sourceCurrencyKey == sUSD || destinationCurrencyKey == sUSD) { exchangeFeeRate = exchangeFeeRate.add(exchangeDynamicFeeRate); // Cap max exchangeFeeRate to 100% exchangeFeeRate = exchangeFeeRate > SafeDecimalMath.unit() ? SafeDecimalMath.unit() : exchangeFeeRate; - return exchangeFeeRate; + return (exchangeFeeRate, exchangeDynamicFeeRate); } // Is this a swing trade? long to short or short to long skipping sUSD. @@ -797,7 +832,7 @@ contract Exchanger is Owned, MixinSystemSettings, IExchanger { uint exchangeFeeRate ) { - (amountReceived, fee, exchangeFeeRate, , ) = _getAmountsForExchangeMinusFees( + (amountReceived, fee, exchangeFeeRate, , , ) = _getAmountsForExchangeMinusFees( sourceAmount, sourceCurrencyKey, destinationCurrencyKey @@ -815,6 +850,7 @@ contract Exchanger is Owned, MixinSystemSettings, IExchanger { uint amountReceived, uint fee, uint exchangeFeeRate, + uint exchangeDynamicFeeRate, uint sourceRate, uint destinationRate ) @@ -828,10 +864,10 @@ contract Exchanger is Owned, MixinSystemSettings, IExchanger { // Return when invalid rate if (destinationAmount == 0 && destinationRate == 0) { - return (destinationAmount, 0, 0, sourceRate, destinationRate); + return (destinationAmount, 0, 0, 0, sourceRate, destinationRate); } - exchangeFeeRate = _feeRateForExchange(sourceCurrencyKey, destinationCurrencyKey); + (exchangeFeeRate, exchangeDynamicFeeRate) = _feeRateForExchange(sourceCurrencyKey, destinationCurrencyKey); amountReceived = _getAmountReceivedForExchange(destinationAmount, exchangeFeeRate); fee = destinationAmount.sub(amountReceived); } From 0a577c326f867ca16f09576381167b8bb945e782 Mon Sep 17 00:00:00 2001 From: Lecky Date: Mon, 15 Nov 2021 22:52:15 +1100 Subject: [PATCH 034/133] Add lastExchangeDynamicFeeRate - Added effectiveValueAndRatesAtRound - added read cache for _getRateAndTimestampAtRound --- contracts/DynamicFee.sol | 7 +- contracts/ExchangeRates.sol | 47 ++++++++++- contracts/Exchanger.sol | 83 ++++++++++++------- contracts/interfaces/IExchangeRates.sol | 15 ++++ contracts/test-helpers/TestableDynamicFee.sol | 4 +- test/contracts/TestableDynamicFee.js | 2 +- 6 files changed, 119 insertions(+), 39 deletions(-) diff --git a/contracts/DynamicFee.sol b/contracts/DynamicFee.sol index d50fac8f38..2863b75d07 100644 --- a/contracts/DynamicFee.sol +++ b/contracts/DynamicFee.sol @@ -44,17 +44,20 @@ library DynamicFee { /// @param prices A list of prices from the current round to the previous rounds /// @param threshold A threshold to determine the price differential /// @param weightDecay A weight decay constant + /// @param lastExchangeDynamicFeeRate The last exchange dynamic fee rate /// @return uint dynamic fee function getDynamicFee( uint[] memory prices, uint threshold, - uint weightDecay + uint weightDecay, + uint lastExchangeDynamicFeeRate ) public pure returns (uint dynamicFee) { uint size = prices.length; - // 0/disable dynamic fee when price feeds less than 2 rounds + // disable dynamic fee when price feeds less than 2 rounds if (size < 2) { return dynamicFee; } + dynamicFee = lastExchangeDynamicFeeRate; for (uint i = prices.length - 1; i > 0; i--) { uint priceDifferential = getPriceDifferential(prices[i - 1], prices[i], threshold); uint roundDecay = getRoundDecay(i, weightDecay); diff --git a/contracts/ExchangeRates.sol b/contracts/ExchangeRates.sol index 4a9603ab37..1dde9762ba 100644 --- a/contracts/ExchangeRates.sol +++ b/contracts/ExchangeRates.sol @@ -297,6 +297,44 @@ contract ExchangeRates is Owned, MixinSystemSettings, IExchangeRates { value = sourceAmount.multiplyDecimalRound(srcRate).divideDecimalRound(destRate); } + function effectiveValueAndRatesAtRound( + bytes32 sourceCurrencyKey, + uint sourceAmount, + bytes32 destinationCurrencyKey, + uint roundIdForSrc, + uint roundIdForDest + ) + external + view + returns ( + uint value, + uint sourceRate, + uint destinationRate + ) + { + if (roundIdForSrc > 0) { + (sourceRate, ) = _getRateAndTimestampAtRound(sourceCurrencyKey, roundIdForSrc); + } else { + sourceRate = _getRate(sourceCurrencyKey); + } + // If there's no change in the currency, then just return the amount they gave us + if (sourceCurrencyKey == destinationCurrencyKey) { + destinationRate = sourceRate; + value = sourceAmount; + } else { + if (roundIdForDest > 0) { + (destinationRate, ) = _getRateAndTimestampAtRound(destinationCurrencyKey, roundIdForDest); + } else { + destinationRate = _getRate(destinationCurrencyKey); + } + // prevent divide-by 0 error (this happens if the dest is not a valid rate) + if (destinationRate > 0) { + // Calculate the effective value by going from source -> USD -> destination + value = sourceAmount.multiplyDecimalRound(sourceRate).divideDecimalRound(destinationRate); + } + } + } + function rateAndTimestampAtRound(bytes32 currencyKey, uint roundId) external view returns (uint rate, uint time) { return _getRateAndTimestampAtRound(currencyKey, roundId); } @@ -625,6 +663,12 @@ contract ExchangeRates is Owned, MixinSystemSettings, IExchangeRates { function _getRateAndTimestampAtRound(bytes32 currencyKey, uint roundId) internal view returns (uint rate, uint time) { AggregatorV2V3Interface aggregator = aggregators[currencyKey]; + RateAndUpdatedTime memory update = _rates[currencyKey][roundId]; + + // Try to get rate from cache if possible + if (update.rate != 0) { + return (_rateOrInverted(currencyKey, update.rate, roundId), update.time); + } if (aggregator != AggregatorV2V3Interface(0)) { // this view from the aggregator is the most gas efficient but it can throw when there's no data, @@ -638,9 +682,6 @@ contract ExchangeRates is Owned, MixinSystemSettings, IExchangeRates { abi.decode(returnData, (uint80, int256, uint256, uint256, uint80)); return (_rateOrInverted(currencyKey, _formatAggregatorAnswer(currencyKey, answer), roundId), updatedAt); } - } else { - RateAndUpdatedTime memory update = _rates[currencyKey][roundId]; - return (_rateOrInverted(currencyKey, update.rate, roundId), update.time); } } diff --git a/contracts/Exchanger.sol b/contracts/Exchanger.sol index 2102acc265..f35f53a5ed 100644 --- a/contracts/Exchanger.sol +++ b/contracts/Exchanger.sol @@ -440,33 +440,33 @@ contract Exchanger is Owned, MixinSystemSettings, IExchanger { } // Using struct ExchangeEntry here to fix stack too deep error - // Only using exchangeFeeRate,roundIdForSrc for sourceRate, roundIdForDest for destinationRate IExchangeState.ExchangeEntry memory entry = IExchangeState.ExchangeEntry({ - src: 0, - amount: 0, - dest: 0, - amountReceived: 0, - exchangeFeeRate: 0, - timestamp: 0, - roundIdForSrc: 0, // sourceRate - roundIdForDest: 0 // destinationRate + src: 0, // N/A + amount: 0, // sourceRate + dest: 0, // N/A + amountReceived: 0, // destinationRate + exchangeFeeRate: 0, // exchangeFeeRate + timestamp: 0, // exchangeDynamicFeeRate + roundIdForSrc: 0, // N/A + roundIdForDest: 0 // currentDestinationRoundId }); - uint exchangeDynamicFeeRate; // Note: `fee` is denominated in the destinationCurrencyKey. ( amountReceived, fee, - entry.exchangeFeeRate, - exchangeDynamicFeeRate, - entry.roundIdForSrc, - entry.roundIdForDest + entry.exchangeFeeRate, // exchangeFeeRate + entry.timestamp, // exchangeDynamicFeeRate + entry.roundIdForDest, // currentDestinationRoundId + entry.amount, // sourceRate + entry.amountReceived // destinationRate ) = _getAmountsForExchangeMinusFees(sourceAmountAfterSettlement, sourceCurrencyKey, destinationCurrencyKey); - // SIP-184: Store last Exchange data in the cache to optimise the dynamic fee rate calculation - lastExchangeRoundId[destinationCurrencyKey] = exchangeRates().getCurrentRoundId(destinationCurrencyKey); - lastExchangeDynamicFeeRate[destinationCurrencyKey] = exchangeDynamicFeeRate; + // SIP-184: Store last Exchange round ID and dynamic fee in the cache to optimise the dynamic fee rate calculation + // Put it here to cache after _getAmountsForExchangeMinusFees calculations + lastExchangeRoundId[destinationCurrencyKey] = entry.roundIdForDest; + lastExchangeDynamicFeeRate[destinationCurrencyKey] = entry.timestamp; // SIP-65: Decentralized Circuit Breaker if ( @@ -514,7 +514,7 @@ contract Exchanger is Owned, MixinSystemSettings, IExchanger { // in these currencies _updateSNXIssuedDebtOnExchange( [sourceCurrencyKey, destinationCurrencyKey], - [entry.roundIdForSrc, entry.roundIdForDest] + [entry.amount, entry.amountReceived] // sourceRate, destinationRate ); // Let the DApps know there was a Synth exchange @@ -760,7 +760,7 @@ contract Exchanger is Owned, MixinSystemSettings, IExchanger { view returns (uint exchangeFeeRate) { - (exchangeFeeRate, ) = _feeRateForExchange(sourceCurrencyKey, destinationCurrencyKey); + (exchangeFeeRate, , ) = _feeRateForExchange(sourceCurrencyKey, destinationCurrencyKey); } /// @notice Calculate the exchange fee for a given source and destination currency key @@ -768,22 +768,27 @@ contract Exchanger is Owned, MixinSystemSettings, IExchanger { /// @param destinationCurrencyKey The destination currency key /// @return The exchange fee rate /// @return The exchange dynamic fee rate + /// @return Current exchange round ID function _feeRateForExchange(bytes32 sourceCurrencyKey, bytes32 destinationCurrencyKey) internal view - returns (uint exchangeFeeRate, uint exchangeDynamicFeeRate) + returns ( + uint exchangeFeeRate, + uint exchangeDynamicFeeRate, + uint currentDestinationRoundId + ) { // Get the exchange fee rate as per destination currencyKey exchangeFeeRate = getExchangeFeeRate(destinationCurrencyKey); // Get the exchange dynamic fee rate as per destination currencyKey - exchangeDynamicFeeRate = _getDynamicFeeForExchange(destinationCurrencyKey); + (exchangeDynamicFeeRate, currentDestinationRoundId) = _getDynamicFeeForExchange(destinationCurrencyKey); if (sourceCurrencyKey == sUSD || destinationCurrencyKey == sUSD) { exchangeFeeRate = exchangeFeeRate.add(exchangeDynamicFeeRate); // Cap max exchangeFeeRate to 100% exchangeFeeRate = exchangeFeeRate > SafeDecimalMath.unit() ? SafeDecimalMath.unit() : exchangeFeeRate; - return (exchangeFeeRate, exchangeDynamicFeeRate); + return (exchangeFeeRate, exchangeDynamicFeeRate, currentDestinationRoundId); } // Is this a swing trade? long to short or short to long skipping sUSD. @@ -806,17 +811,26 @@ contract Exchanger is Owned, MixinSystemSettings, IExchanger { /// @notice Get dynamic fee for a given currency key (SIP-184) /// @param currencyKey The given currency key /// @return The dyanmic fee - function _getDynamicFeeForExchange(bytes32 currencyKey) internal view returns (uint dynamicFee) { + /// @return The current round ID + function _getDynamicFeeForExchange(bytes32 currencyKey) internal view returns (uint dynamicFee, uint currentRoundId) { // No dynamic fee for sUSD if (currencyKey == sUSD) { - return 0; + return (0, 0); } uint threshold = getExchangeDynamicFeeThreshold(); uint weightDecay = getExchangeDynamicFeeWeightDecay(); uint rounds = getExchangeDynamicFeeRounds(); uint[] memory prices; - (prices, ) = exchangeRates().ratesAndUpdatedTimeForCurrencyLastNRounds(currencyKey, rounds); - dynamicFee = DynamicFee.getDynamicFee(prices, threshold, weightDecay); + currentRoundId = exchangeRates().getCurrentRoundId(currencyKey); + uint remainingRounds = currentRoundId.sub(lastExchangeRoundId[currencyKey]); + if (remainingRounds >= rounds) { + (prices, ) = exchangeRates().ratesAndUpdatedTimeForCurrencyLastNRounds(currencyKey, rounds); + dynamicFee = DynamicFee.getDynamicFee(prices, threshold, weightDecay, 0); + } else { + // optimise calculation by using the last exchange dynamic fee rate + (prices, ) = exchangeRates().ratesAndUpdatedTimeForCurrencyLastNRounds(currencyKey, remainingRounds); + dynamicFee = DynamicFee.getDynamicFee(prices, threshold, weightDecay, lastExchangeDynamicFeeRate[currencyKey]); + } } function getAmountsForExchange( @@ -832,7 +846,7 @@ contract Exchanger is Owned, MixinSystemSettings, IExchanger { uint exchangeFeeRate ) { - (amountReceived, fee, exchangeFeeRate, , , ) = _getAmountsForExchangeMinusFees( + (amountReceived, fee, exchangeFeeRate, , , , ) = _getAmountsForExchangeMinusFees( sourceAmount, sourceCurrencyKey, destinationCurrencyKey @@ -851,23 +865,30 @@ contract Exchanger is Owned, MixinSystemSettings, IExchanger { uint fee, uint exchangeFeeRate, uint exchangeDynamicFeeRate, + uint currentDestinationRoundId, uint sourceRate, uint destinationRate ) { + (exchangeFeeRate, exchangeDynamicFeeRate, currentDestinationRoundId) = _feeRateForExchange( + sourceCurrencyKey, + destinationCurrencyKey + ); + uint destinationAmount; - (destinationAmount, sourceRate, destinationRate) = exchangeRates().effectiveValueAndRates( + (destinationAmount, sourceRate, destinationRate) = exchangeRates().effectiveValueAndRatesAtRound( sourceCurrencyKey, sourceAmount, - destinationCurrencyKey + destinationCurrencyKey, + 0, + currentDestinationRoundId ); // Return when invalid rate if (destinationAmount == 0 && destinationRate == 0) { - return (destinationAmount, 0, 0, 0, sourceRate, destinationRate); + return (destinationAmount, 0, 0, 0, currentDestinationRoundId, sourceRate, destinationRate); } - (exchangeFeeRate, exchangeDynamicFeeRate) = _feeRateForExchange(sourceCurrencyKey, destinationCurrencyKey); amountReceived = _getAmountReceivedForExchange(destinationAmount, exchangeFeeRate); fee = destinationAmount.sub(amountReceived); } diff --git a/contracts/interfaces/IExchangeRates.sol b/contracts/interfaces/IExchangeRates.sol index 33cc69fe6e..2f64b8a099 100644 --- a/contracts/interfaces/IExchangeRates.sol +++ b/contracts/interfaces/IExchangeRates.sol @@ -56,6 +56,21 @@ interface IExchangeRates { uint roundIdForDest ) external view returns (uint value); + function effectiveValueAndRatesAtRound( + bytes32 sourceCurrencyKey, + uint sourceAmount, + bytes32 destinationCurrencyKey, + uint roundIdForSrc, + uint roundIdForDest + ) + external + view + returns ( + uint value, + uint sourceRate, + uint destinationRate + ); + function getCurrentRoundId(bytes32 currencyKey) external view returns (uint); function getLastRoundIdBeforeElapsedSecs( diff --git a/contracts/test-helpers/TestableDynamicFee.sol b/contracts/test-helpers/TestableDynamicFee.sol index 36dd57a630..5657b0c1ed 100644 --- a/contracts/test-helpers/TestableDynamicFee.sol +++ b/contracts/test-helpers/TestableDynamicFee.sol @@ -15,7 +15,7 @@ contract TestableDynamicFee { return DynamicFee.getRoundDecay(round, weightDecay); } - function testGetDynamicFee(uint[] memory prices) public view returns (uint) { - return DynamicFee.getDynamicFee(prices, threshold, weightDecay); + function testGetDynamicFee(uint[] memory prices, uint lastExchangeDynamicFeeRate) public view returns (uint) { + return DynamicFee.getDynamicFee(prices, threshold, weightDecay, lastExchangeDynamicFeeRate); } } diff --git a/test/contracts/TestableDynamicFee.js b/test/contracts/TestableDynamicFee.js index 3a86fcfca5..a38c2fb950 100644 --- a/test/contracts/TestableDynamicFee.js +++ b/test/contracts/TestableDynamicFee.js @@ -48,7 +48,7 @@ contract('TestableDynamicFee', () => { toUnit('49933.34034209'), toUnit('49871.92313713'), ]; - const dynamicFee = await testableDynamicFee.testGetDynamicFee(prices); + const dynamicFee = await testableDynamicFee.testGetDynamicFee(prices, '0'); assert.bnEqual(dynamicFee, '2064427530203592'); }); }); From e4438e07d712c1f08f104cb898d98fd24ef7ab5f Mon Sep 17 00:00:00 2001 From: Lecky Date: Tue, 16 Nov 2021 22:02:31 +1100 Subject: [PATCH 035/133] Using cache to improve performance - Added effectiveValueAndRatesAtRound in ExchangeRates --- contracts/ExchangeRates.sol | 109 ++++++++++-------- contracts/Exchanger.sol | 90 +++++++-------- contracts/ExchangerWithFeeRecAlternatives.sol | 4 +- contracts/interfaces/IExchangeRates.sol | 1 - 4 files changed, 108 insertions(+), 96 deletions(-) diff --git a/contracts/ExchangeRates.sol b/contracts/ExchangeRates.sol index 79e336f8c9..ad575eca95 100644 --- a/contracts/ExchangeRates.sol +++ b/contracts/ExchangeRates.sol @@ -57,7 +57,7 @@ contract ExchangeRates is Owned, MixinSystemSettings, IExchangeRates { oracle = _oracle; // The sUSD rate is always 1 and is never stale. - _setRate("sUSD", SafeDecimalMath.unit(), now); + _setRate("sUSD", 0, SafeDecimalMath.unit(), now); internalUpdateRates(_currencyKeys, _newRates, now); } @@ -118,6 +118,51 @@ contract ExchangeRates is Owned, MixinSystemSettings, IExchangeRates { } } + /// @notice Same as effectiveValueAndRates but at historical rates. + /// @dev Needs to be mutative as it's calling setRate to cache the rate. + /// It can be call by anyone as rate is always returned by the oracle. + /// @param sourceCurrencyKey The currency key of the source currency. + /// @param sourceAmount The amount of the source currency. + /// @param destinationCurrencyKey The currency key of the destination currency. + /// @param roundIdForSrc The round id of the source currency. + /// @param roundIdForDest The round id of the target currency. + /// @return value of the target currency. + /// @return rate of the source currency. + /// @return rate of the destination currency. + function effectiveValueAndRatesAtRound( + bytes32 sourceCurrencyKey, + uint sourceAmount, + bytes32 destinationCurrencyKey, + uint roundIdForSrc, + uint roundIdForDest + ) + external + returns ( + uint value, + uint sourceRate, + uint destinationRate + ) + { + uint time; + (sourceRate, time) = _getRateAndTimestampAtRound(sourceCurrencyKey, roundIdForSrc); + // cache for fast read + _setRate(sourceCurrencyKey, roundIdForSrc, sourceRate, time); + // If there's no change in the currency, then just return the amount they gave us + if (sourceCurrencyKey == destinationCurrencyKey) { + destinationRate = sourceRate; + value = sourceAmount; + } else { + (destinationRate, time) = _getRateAndTimestampAtRound(destinationCurrencyKey, roundIdForDest); + // cache for fast read + _setRate(destinationCurrencyKey, roundIdForDest, destinationRate, time); + // prevent divide-by 0 error (this happens if the dest is not a valid rate) + if (destinationRate > 0) { + // Calculate the effective value by going from source -> USD -> destination + value = sourceAmount.multiplyDecimalRound(sourceRate).divideDecimalRound(destinationRate); + } + } + } + /* ========== VIEWS ========== */ function currenciesUsingAggregator(address aggregator) external view returns (bytes32[] memory currencies) { @@ -188,44 +233,6 @@ contract ExchangeRates is Owned, MixinSystemSettings, IExchangeRates { value = sourceAmount.multiplyDecimalRound(srcRate).divideDecimalRound(destRate); } - function effectiveValueAndRatesAtRound( - bytes32 sourceCurrencyKey, - uint sourceAmount, - bytes32 destinationCurrencyKey, - uint roundIdForSrc, - uint roundIdForDest - ) - external - view - returns ( - uint value, - uint sourceRate, - uint destinationRate - ) - { - if (roundIdForSrc > 0) { - (sourceRate, ) = _getRateAndTimestampAtRound(sourceCurrencyKey, roundIdForSrc); - } else { - sourceRate = _getRate(sourceCurrencyKey); - } - // If there's no change in the currency, then just return the amount they gave us - if (sourceCurrencyKey == destinationCurrencyKey) { - destinationRate = sourceRate; - value = sourceAmount; - } else { - if (roundIdForDest > 0) { - (destinationRate, ) = _getRateAndTimestampAtRound(destinationCurrencyKey, roundIdForDest); - } else { - destinationRate = _getRate(destinationCurrencyKey); - } - // prevent divide-by 0 error (this happens if the dest is not a valid rate) - if (destinationRate > 0) { - // Calculate the effective value by going from source -> USD -> destination - value = sourceAmount.multiplyDecimalRound(sourceRate).divideDecimalRound(destinationRate); - } - } - } - function rateAndTimestampAtRound(bytes32 currencyKey, uint roundId) external view returns (uint rate, uint time) { return _getRateAndTimestampAtRound(currencyKey, roundId); } @@ -410,13 +417,23 @@ contract ExchangeRates is Owned, MixinSystemSettings, IExchangeRates { } } + /// @notice setting the rate for a currency + /// @param currencyKey the currency key + /// @param roundId the round id + /// @param rate the rate to set + /// @param time the time of the rate function _setRate( bytes32 currencyKey, + uint256 roundId, uint256 rate, uint256 time ) internal { - // Note: this will effectively start the rounds at 1, which matches Chainlink's Agggregators - currentRoundForRate[currencyKey]++; + if (roundId > 0) { + currentRoundForRate[currencyKey] = roundId; + } else { + // Note: this will effectively start the rounds at 1, which matches Chainlink's Agggregators + currentRoundForRate[currencyKey]++; + } _rates[currencyKey][currentRoundForRate[currencyKey]] = RateAndUpdatedTime({ rate: uint216(rate), @@ -448,7 +465,7 @@ contract ExchangeRates is Owned, MixinSystemSettings, IExchangeRates { } // Ok, go ahead with the update. - _setRate(currencyKey, newRates[i], timeSent); + _setRate(currencyKey, 0, newRates[i], timeSent); } emit RatesUpdated(currencyKeys, newRates); @@ -484,7 +501,7 @@ contract ExchangeRates is Owned, MixinSystemSettings, IExchangeRates { return uint(rate); } - function _getRateAndUpdatedTime(bytes32 currencyKey) internal view returns (RateAndUpdatedTime memory) { + function _getRateAndUpdatedTime(bytes32 currencyKey) internal view returns (RateAndUpdatedTime memory entry) { AggregatorV2V3Interface aggregator = aggregators[currencyKey]; if (aggregator != AggregatorV2V3Interface(0)) { @@ -505,9 +522,7 @@ contract ExchangeRates is Owned, MixinSystemSettings, IExchangeRates { } } else { uint roundId = currentRoundForRate[currencyKey]; - RateAndUpdatedTime memory entry = _rates[currencyKey][roundId]; - - return RateAndUpdatedTime({rate: uint216(entry.rate), time: entry.time}); + entry = _rates[currencyKey][roundId]; } } @@ -526,7 +541,7 @@ contract ExchangeRates is Owned, MixinSystemSettings, IExchangeRates { RateAndUpdatedTime memory update = _rates[currencyKey][roundId]; // Try to get rate from cache if possible - if (update.rate != 0) { + if (update.rate > 0) { return (update.rate, update.time); } diff --git a/contracts/Exchanger.sol b/contracts/Exchanger.sol index 52bb9de826..78d0ee2cce 100644 --- a/contracts/Exchanger.sol +++ b/contracts/Exchanger.sol @@ -85,6 +85,16 @@ contract Exchanger is Owned, MixinSystemSettings, IExchanger { uint timestamp; } + struct ExchangeEntry { + uint sourceRate; + uint destinationRate; + uint destinationAmount; + uint exchangeFeeRate; + uint exchangeDynamicFeeRate; + uint roundIdForSrc; + uint roundIdForDest; + } + bytes32 public constant CONTRACT_NAME = "Exchanger"; bytes32 internal constant sUSD = "sUSD"; @@ -467,34 +477,26 @@ contract Exchanger is Owned, MixinSystemSettings, IExchanger { return (0, 0, IVirtualSynth(0)); } - // Using struct ExchangeEntry here to fix stack too deep error - IExchangeState.ExchangeEntry memory entry = - IExchangeState.ExchangeEntry({ - src: 0, // N/A - amount: 0, // sourceRate - dest: 0, // N/A - amountReceived: 0, // destinationRate - exchangeFeeRate: 0, // exchangeFeeRate - timestamp: 0, // exchangeDynamicFeeRate - roundIdForSrc: 0, // N/A - roundIdForDest: 0 // currentDestinationRoundId - }); + // Using struct to resolve stack too deep error + ExchangeEntry memory entry; - // Note: `fee` is denominated in the destinationCurrencyKey. - ( - amountReceived, - fee, - entry.exchangeFeeRate, // exchangeFeeRate - entry.timestamp, // exchangeDynamicFeeRate - entry.roundIdForDest, // currentDestinationRoundId - entry.amount, // sourceRate - entry.amountReceived // destinationRate - ) = _getAmountsForExchangeMinusFees(sourceAmountAfterSettlement, sourceCurrencyKey, destinationCurrencyKey); - - // SIP-184: Store last Exchange round ID and dynamic fee in the cache to optimise the dynamic fee rate calculation - // Put it here to cache after _getAmountsForExchangeMinusFees calculations + (entry.exchangeFeeRate, entry.exchangeDynamicFeeRate, entry.roundIdForDest) = _feeRateForExchange( + sourceCurrencyKey, + destinationCurrencyKey + ); + + // Puting the exchangeRate call here as it's mutative for fast cache reading + (entry.destinationAmount, entry.sourceRate, entry.destinationRate) = exchangeRates().effectiveValueAndRatesAtRound( + sourceCurrencyKey, + sourceAmount, + destinationCurrencyKey, + 0, + entry.roundIdForDest + ); + + // SIP-184: Store last Exchange round ID and dynamic fee to optimise calculation lastExchangeRoundId[destinationCurrencyKey] = entry.roundIdForDest; - lastExchangeDynamicFeeRate[destinationCurrencyKey] = entry.timestamp; + lastExchangeDynamicFeeRate[destinationCurrencyKey] = entry.exchangeDynamicFeeRate; // SIP-65: Decentralized Circuit Breaker if ( @@ -504,9 +506,12 @@ contract Exchanger is Owned, MixinSystemSettings, IExchanger { return (0, 0, IVirtualSynth(0)); } + amountReceived = _deductFeesFromAmount(entry.destinationAmount, entry.exchangeFeeRate); + // Note: `fee` is denominated in the destinationCurrencyKey. + fee = entry.destinationAmount.sub(amountReceived); + // Note: We don't need to check their balance as the _convert() below will do a safe subtraction which requires // the subtraction to not overflow, which would happen if their balance is not sufficient. - vSynth = _convert( sourceCurrencyKey, from, @@ -542,7 +547,7 @@ contract Exchanger is Owned, MixinSystemSettings, IExchanger { // in these currencies _updateSNXIssuedDebtOnExchange( [sourceCurrencyKey, destinationCurrencyKey], - [entry.amount, entry.amountReceived] // sourceRate, destinationRate + [entry.sourceRate, entry.destinationRate] ); // Let the DApps know there was a Synth exchange @@ -811,9 +816,9 @@ contract Exchanger is Owned, MixinSystemSettings, IExchanger { bytes32 destinationCurrencyKey ) internal - pure + view returns ( - uint exchangeFeeRate, + uint, uint exchangeDynamicFeeRate, uint currentDestinationRoundId ) @@ -843,6 +848,7 @@ contract Exchanger is Owned, MixinSystemSettings, IExchanger { exchangeFeeRate = exchangeFeeRate.add(exchangeDynamicFeeRate); // Cap max exchangeFeeRate to 100% exchangeFeeRate = exchangeFeeRate > SafeDecimalMath.unit() ? SafeDecimalMath.unit() : exchangeFeeRate; + return (exchangeFeeRate, exchangeDynamicFeeRate, currentDestinationRoundId); } /// @notice Get dynamic fee for a given currency key (SIP-184) @@ -883,7 +889,7 @@ contract Exchanger is Owned, MixinSystemSettings, IExchanger { uint exchangeFeeRate ) { - (amountReceived, fee, exchangeFeeRate, , , , ) = _getAmountsForExchangeMinusFees( + (amountReceived, fee, exchangeFeeRate) = _getAmountsForExchangeMinusFees( sourceAmount, sourceCurrencyKey, destinationCurrencyKey @@ -900,30 +906,22 @@ contract Exchanger is Owned, MixinSystemSettings, IExchanger { returns ( uint amountReceived, uint fee, - uint exchangeFeeRate, - uint exchangeDynamicFeeRate, - uint currentDestinationRoundId, - uint sourceRate, - uint destinationRate + uint exchangeFeeRate ) { - (exchangeFeeRate, exchangeDynamicFeeRate, currentDestinationRoundId) = _feeRateForExchange( - sourceCurrencyKey, - destinationCurrencyKey - ); + (exchangeFeeRate, , ) = _feeRateForExchange(sourceCurrencyKey, destinationCurrencyKey); uint destinationAmount; - (destinationAmount, sourceRate, destinationRate) = exchangeRates().effectiveValueAndRatesAtRound( + uint destinationRate; + (destinationAmount, , destinationRate) = exchangeRates().effectiveValueAndRates( sourceCurrencyKey, sourceAmount, - destinationCurrencyKey, - 0, - currentDestinationRoundId + destinationCurrencyKey ); // Return when invalid rate - if (destinationAmount == 0 && destinationRate == 0) { - return (destinationAmount, 0, 0, 0, currentDestinationRoundId, sourceRate, destinationRate); + if (destinationRate == 0) { + return (destinationAmount, 0, 0); } amountReceived = _deductFeesFromAmount(destinationAmount, exchangeFeeRate); diff --git a/contracts/ExchangerWithFeeRecAlternatives.sol b/contracts/ExchangerWithFeeRecAlternatives.sol index b6e767a958..f384b360ef 100644 --- a/contracts/ExchangerWithFeeRecAlternatives.sol +++ b/contracts/ExchangerWithFeeRecAlternatives.sol @@ -270,7 +270,7 @@ contract ExchangerWithFeeRecAlternatives is MinimalProxyFactory, Exchanger { function _feeRateForAtomicExchange(bytes32 sourceCurrencyKey, bytes32 destinationCurrencyKey) internal view - returns (uint) + returns (uint feeRate) { // Get the exchange fee rate as per destination currencyKey uint baseRate = getAtomicExchangeFeeRate(destinationCurrencyKey); @@ -279,7 +279,7 @@ contract ExchangerWithFeeRecAlternatives is MinimalProxyFactory, Exchanger { baseRate = getExchangeFeeRate(destinationCurrencyKey); } - return _calculateFeeRateFromExchangeSynths(baseRate, sourceCurrencyKey, destinationCurrencyKey); + (feeRate, , ) = _calculateFeeRateFromExchangeSynths(baseRate, sourceCurrencyKey, destinationCurrencyKey); } function _getAmountsForAtomicExchangeMinusFees( diff --git a/contracts/interfaces/IExchangeRates.sol b/contracts/interfaces/IExchangeRates.sol index 3659fa05be..3510370583 100644 --- a/contracts/interfaces/IExchangeRates.sol +++ b/contracts/interfaces/IExchangeRates.sol @@ -68,7 +68,6 @@ interface IExchangeRates { uint roundIdForDest ) external - view returns ( uint value, uint sourceRate, From 0667767c230f76e4158cb2a0108ed9769f84830e Mon Sep 17 00:00:00 2001 From: Lecky Date: Wed, 17 Nov 2021 16:01:15 +1100 Subject: [PATCH 036/133] fixing ExchangerWithFeeRecAlternatives test --- test/contracts/ExchangerWithFeeRecAlternatives.behaviors.js | 6 +++++- test/contracts/setup.js | 4 ++-- 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/test/contracts/ExchangerWithFeeRecAlternatives.behaviors.js b/test/contracts/ExchangerWithFeeRecAlternatives.behaviors.js index fe91a49508..8af30298ab 100644 --- a/test/contracts/ExchangerWithFeeRecAlternatives.behaviors.js +++ b/test/contracts/ExchangerWithFeeRecAlternatives.behaviors.js @@ -21,7 +21,11 @@ module.exports = function({ accounts }) { }); before(async () => { - ExchangerWithFeeRecAlternatives.link(await artifacts.require('SafeDecimalMath').new()); + const safeDecimalMath = await artifacts.require('SafeDecimalMath').new(); + ExchangerWithFeeRecAlternatives.link(safeDecimalMath); + const DynamicFee = artifacts.require('DynamicFee'); + DynamicFee.link(safeDecimalMath); + ExchangerWithFeeRecAlternatives.link(await DynamicFee.new()); }); beforeEach(async () => { diff --git a/test/contracts/setup.js b/test/contracts/setup.js index 5271703823..f1caa6482d 100644 --- a/test/contracts/setup.js +++ b/test/contracts/setup.js @@ -126,11 +126,11 @@ const setupContract = async ({ // Linking library if needed if (Object.keys((await artifacts.readArtifact(contract)).linkReferences).length > 0) { const safeDecimalMath = await artifacts.require('SafeDecimalMath').new(); - await artifact.link(safeDecimalMath); + artifact.link(safeDecimalMath); if (/^Exchanger$|^ExchangerWithVirtualSynth$/.test(artifact._json.contractName)) { const DynamicFee = artifacts.require('DynamicFee'); DynamicFee.link(safeDecimalMath); - await artifact.link(await DynamicFee.new()); + artifact.link(await DynamicFee.new()); } } From 9362687fbaaef1fc7881d93ba2ad0e669ab3ca60 Mon Sep 17 00:00:00 2001 From: Lecky Date: Thu, 18 Nov 2021 00:51:24 +1100 Subject: [PATCH 037/133] get rate before ensure so that feed can be cached - exchangeRate._getRateAndUpdatedTime also read from cache first --- contracts/ExchangeRates.sol | 19 +++--- contracts/Exchanger.sol | 67 ++++++++----------- contracts/ExchangerWithFeeRecAlternatives.sol | 2 +- 3 files changed, 41 insertions(+), 47 deletions(-) diff --git a/contracts/ExchangeRates.sol b/contracts/ExchangeRates.sol index ad575eca95..b9e1880ed1 100644 --- a/contracts/ExchangeRates.sol +++ b/contracts/ExchangeRates.sol @@ -40,6 +40,7 @@ contract ExchangeRates is Owned, MixinSystemSettings, IExchangeRates { // Do not allow the oracle to submit times any further forward into the future than this constant. uint private constant ORACLE_FUTURE_LIMIT = 10 minutes; + /// @notice ID for the current round mapping(bytes32 => uint) public currentRoundForRate; // @@ -503,6 +504,14 @@ contract ExchangeRates is Owned, MixinSystemSettings, IExchangeRates { function _getRateAndUpdatedTime(bytes32 currencyKey) internal view returns (RateAndUpdatedTime memory entry) { AggregatorV2V3Interface aggregator = aggregators[currencyKey]; + uint roundId = currentRoundForRate[currencyKey]; + + entry = _rates[currencyKey][roundId]; + + // Try to get rate from cache if possible + if (entry.rate > 0) { + return entry; + } if (aggregator != AggregatorV2V3Interface(0)) { // this view from the aggregator is the most gas efficient but it can throw when there's no data, @@ -514,15 +523,9 @@ contract ExchangeRates is Owned, MixinSystemSettings, IExchangeRates { if (success) { (, int256 answer, , uint256 updatedAt, ) = abi.decode(returnData, (uint80, int256, uint256, uint256, uint80)); - return - RateAndUpdatedTime({ - rate: uint216(_formatAggregatorAnswer(currencyKey, answer)), - time: uint40(updatedAt) - }); + entry.rate = uint216(_formatAggregatorAnswer(currencyKey, answer)); + entry.time = uint40(updatedAt); } - } else { - uint roundId = currentRoundForRate[currencyKey]; - entry = _rates[currencyKey][roundId]; } } diff --git a/contracts/Exchanger.sol b/contracts/Exchanger.sol index 78d0ee2cce..f7e0efb84a 100644 --- a/contracts/Exchanger.sol +++ b/contracts/Exchanger.sol @@ -467,6 +467,21 @@ contract Exchanger is Owned, MixinSystemSettings, IExchanger { IVirtualSynth vSynth ) { + // Using struct to resolve stack too deep error + ExchangeEntry memory entry; + + entry.roundIdForSrc = exchangeRates().getCurrentRoundId(sourceCurrencyKey); + entry.roundIdForDest = exchangeRates().getCurrentRoundId(destinationCurrencyKey); + + // Puting the exchangeRate call here as it's mutative for fast cache reading later on + (entry.destinationAmount, entry.sourceRate, entry.destinationRate) = exchangeRates().effectiveValueAndRatesAtRound( + sourceCurrencyKey, + sourceAmount, + destinationCurrencyKey, + entry.roundIdForSrc, + entry.roundIdForDest + ); + _ensureCanExchange(sourceCurrencyKey, sourceAmount, destinationCurrencyKey); uint sourceAmountAfterSettlement = _settleAndCalcSourceAmountRemaining(sourceAmount, from, sourceCurrencyKey); @@ -477,31 +492,19 @@ contract Exchanger is Owned, MixinSystemSettings, IExchanger { return (0, 0, IVirtualSynth(0)); } - // Using struct to resolve stack too deep error - ExchangeEntry memory entry; - - (entry.exchangeFeeRate, entry.exchangeDynamicFeeRate, entry.roundIdForDest) = _feeRateForExchange( + (entry.exchangeFeeRate, entry.exchangeDynamicFeeRate) = _feeRateForExchange( sourceCurrencyKey, destinationCurrencyKey ); - // Puting the exchangeRate call here as it's mutative for fast cache reading - (entry.destinationAmount, entry.sourceRate, entry.destinationRate) = exchangeRates().effectiveValueAndRatesAtRound( - sourceCurrencyKey, - sourceAmount, - destinationCurrencyKey, - 0, - entry.roundIdForDest - ); - // SIP-184: Store last Exchange round ID and dynamic fee to optimise calculation lastExchangeRoundId[destinationCurrencyKey] = entry.roundIdForDest; lastExchangeDynamicFeeRate[destinationCurrencyKey] = entry.exchangeDynamicFeeRate; // SIP-65: Decentralized Circuit Breaker if ( - _suspendIfRateInvalid(sourceCurrencyKey, entry.roundIdForSrc) || - _suspendIfRateInvalid(destinationCurrencyKey, entry.roundIdForDest) + _suspendIfRateInvalid(sourceCurrencyKey, entry.sourceRate) || + _suspendIfRateInvalid(destinationCurrencyKey, entry.destinationRate) ) { return (0, 0, IVirtualSynth(0)); } @@ -787,7 +790,7 @@ contract Exchanger is Owned, MixinSystemSettings, IExchanger { view returns (uint exchangeFeeRate) { - (exchangeFeeRate, , ) = _feeRateForExchange(sourceCurrencyKey, destinationCurrencyKey); + (exchangeFeeRate, ) = _feeRateForExchange(sourceCurrencyKey, destinationCurrencyKey); } /// @notice Calculate the exchange fee for a given source and destination currency key @@ -795,15 +798,10 @@ contract Exchanger is Owned, MixinSystemSettings, IExchanger { /// @param destinationCurrencyKey The destination currency key /// @return The exchange fee rate /// @return The exchange dynamic fee rate - /// @return Current exchange round ID function _feeRateForExchange(bytes32 sourceCurrencyKey, bytes32 destinationCurrencyKey) internal view - returns ( - uint exchangeFeeRate, - uint exchangeDynamicFeeRate, - uint currentDestinationRoundId - ) + returns (uint exchangeFeeRate, uint exchangeDynamicFeeRate) { // Get the exchange fee rate as per destination currencyKey uint baseRate = getExchangeFeeRate(destinationCurrencyKey); @@ -814,23 +812,15 @@ contract Exchanger is Owned, MixinSystemSettings, IExchanger { uint exchangeFeeRate, bytes32 sourceCurrencyKey, bytes32 destinationCurrencyKey - ) - internal - view - returns ( - uint, - uint exchangeDynamicFeeRate, - uint currentDestinationRoundId - ) - { + ) internal view returns (uint, uint exchangeDynamicFeeRate) { // Get the exchange dynamic fee rate as per destination currencyKey - (exchangeDynamicFeeRate, currentDestinationRoundId) = _getDynamicFeeForExchange(destinationCurrencyKey); + exchangeDynamicFeeRate = _getDynamicFeeForExchange(destinationCurrencyKey); if (sourceCurrencyKey == sUSD || destinationCurrencyKey == sUSD) { exchangeFeeRate = exchangeFeeRate.add(exchangeDynamicFeeRate); // Cap max exchangeFeeRate to 100% exchangeFeeRate = exchangeFeeRate > SafeDecimalMath.unit() ? SafeDecimalMath.unit() : exchangeFeeRate; - return (exchangeFeeRate, exchangeDynamicFeeRate, currentDestinationRoundId); + return (exchangeFeeRate, exchangeDynamicFeeRate); } // Is this a swing trade? long to short or short to long skipping sUSD. @@ -848,23 +838,24 @@ contract Exchanger is Owned, MixinSystemSettings, IExchanger { exchangeFeeRate = exchangeFeeRate.add(exchangeDynamicFeeRate); // Cap max exchangeFeeRate to 100% exchangeFeeRate = exchangeFeeRate > SafeDecimalMath.unit() ? SafeDecimalMath.unit() : exchangeFeeRate; - return (exchangeFeeRate, exchangeDynamicFeeRate, currentDestinationRoundId); + return (exchangeFeeRate, exchangeDynamicFeeRate); } /// @notice Get dynamic fee for a given currency key (SIP-184) /// @param currencyKey The given currency key /// @return The dyanmic fee /// @return The current round ID - function _getDynamicFeeForExchange(bytes32 currencyKey) internal view returns (uint dynamicFee, uint currentRoundId) { + function _getDynamicFeeForExchange(bytes32 currencyKey) internal view returns (uint dynamicFee) { // No dynamic fee for sUSD if (currencyKey == sUSD) { - return (0, 0); + return 0; } uint threshold = getExchangeDynamicFeeThreshold(); uint weightDecay = getExchangeDynamicFeeWeightDecay(); uint rounds = getExchangeDynamicFeeRounds(); uint[] memory prices; - currentRoundId = exchangeRates().getCurrentRoundId(currencyKey); + // Note that we are using cache round ID here for fast read + uint currentRoundId = exchangeRates().currentRoundForRate(currencyKey); uint remainingRounds = currentRoundId.sub(lastExchangeRoundId[currencyKey]); if (remainingRounds >= rounds) { (prices, ) = exchangeRates().ratesAndUpdatedTimeForCurrencyLastNRounds(currencyKey, rounds); @@ -909,7 +900,7 @@ contract Exchanger is Owned, MixinSystemSettings, IExchanger { uint exchangeFeeRate ) { - (exchangeFeeRate, , ) = _feeRateForExchange(sourceCurrencyKey, destinationCurrencyKey); + (exchangeFeeRate, ) = _feeRateForExchange(sourceCurrencyKey, destinationCurrencyKey); uint destinationAmount; uint destinationRate; diff --git a/contracts/ExchangerWithFeeRecAlternatives.sol b/contracts/ExchangerWithFeeRecAlternatives.sol index f384b360ef..50e17bcbcd 100644 --- a/contracts/ExchangerWithFeeRecAlternatives.sol +++ b/contracts/ExchangerWithFeeRecAlternatives.sol @@ -279,7 +279,7 @@ contract ExchangerWithFeeRecAlternatives is MinimalProxyFactory, Exchanger { baseRate = getExchangeFeeRate(destinationCurrencyKey); } - (feeRate, , ) = _calculateFeeRateFromExchangeSynths(baseRate, sourceCurrencyKey, destinationCurrencyKey); + (feeRate, ) = _calculateFeeRateFromExchangeSynths(baseRate, sourceCurrencyKey, destinationCurrencyKey); } function _getAmountsForAtomicExchangeMinusFees( From 8df051c2351916784190c45c41a32707aff062cf Mon Sep 17 00:00:00 2001 From: Lecky Date: Thu, 18 Nov 2021 15:19:49 +1100 Subject: [PATCH 038/133] Code refactor and fix slither code comment - adding nonReentrant --- contracts/DynamicFee.sol | 4 ++-- contracts/ExchangeRates.sol | 6 +++++- contracts/Exchanger.sol | 8 +++++--- contracts/test-helpers/TestableDynamicFee.sol | 6 +++--- 4 files changed, 15 insertions(+), 9 deletions(-) diff --git a/contracts/DynamicFee.sol b/contracts/DynamicFee.sol index 2863b75d07..dad5a30452 100644 --- a/contracts/DynamicFee.sol +++ b/contracts/DynamicFee.sol @@ -47,11 +47,11 @@ library DynamicFee { /// @param lastExchangeDynamicFeeRate The last exchange dynamic fee rate /// @return uint dynamic fee function getDynamicFee( - uint[] memory prices, + uint[] calldata prices, uint threshold, uint weightDecay, uint lastExchangeDynamicFeeRate - ) public pure returns (uint dynamicFee) { + ) external pure returns (uint dynamicFee) { uint size = prices.length; // disable dynamic fee when price feeds less than 2 rounds if (size < 2) { diff --git a/contracts/ExchangeRates.sol b/contracts/ExchangeRates.sol index b9e1880ed1..78d488a452 100644 --- a/contracts/ExchangeRates.sol +++ b/contracts/ExchangeRates.sol @@ -8,6 +8,7 @@ import "./interfaces/IExchangeRates.sol"; // Libraries import "./SafeDecimalMath.sol"; +import "openzeppelin-solidity-2.3.0/contracts/utils/ReentrancyGuard.sol"; // Internal references // AggregatorInterface from Chainlink represents a decentralized pricing network for a single currency key @@ -17,7 +18,7 @@ import "@chainlink/contracts-0.0.10/src/v0.5/interfaces/FlagsInterface.sol"; import "./interfaces/IExchanger.sol"; // https://docs.synthetix.io/contracts/source/contracts/exchangerates -contract ExchangeRates is Owned, MixinSystemSettings, IExchangeRates { +contract ExchangeRates is Owned, MixinSystemSettings, IExchangeRates, ReentrancyGuard { using SafeMath for uint; using SafeDecimalMath for uint; @@ -41,6 +42,8 @@ contract ExchangeRates is Owned, MixinSystemSettings, IExchangeRates { uint private constant ORACLE_FUTURE_LIMIT = 10 minutes; /// @notice ID for the current round + /// @param currencyKey is the currency key of the synth to be exchanged + /// @return the current exchange round ID mapping(bytes32 => uint) public currentRoundForRate; // @@ -138,6 +141,7 @@ contract ExchangeRates is Owned, MixinSystemSettings, IExchangeRates { uint roundIdForDest ) external + nonReentrant returns ( uint value, uint sourceRate, diff --git a/contracts/Exchanger.sol b/contracts/Exchanger.sol index f7e0efb84a..ecb75f47d8 100644 --- a/contracts/Exchanger.sol +++ b/contracts/Exchanger.sol @@ -9,6 +9,7 @@ import "./interfaces/IExchanger.sol"; // Libraries import "./SafeDecimalMath.sol"; import "./DynamicFee.sol"; +import "openzeppelin-solidity-2.3.0/contracts/utils/ReentrancyGuard.sol"; // Internal references import "./interfaces/ISystemStatus.sol"; @@ -70,7 +71,7 @@ interface IExchangerInternalDebtCache { } // https://docs.synthetix.io/contracts/source/contracts/exchanger -contract Exchanger is Owned, MixinSystemSettings, IExchanger { +contract Exchanger is Owned, MixinSystemSettings, IExchanger, ReentrancyGuard { using SafeMath for uint; using SafeDecimalMath for uint; @@ -431,7 +432,7 @@ contract Exchanger is Owned, MixinSystemSettings, IExchanger { rates[2] = SafeDecimalMath.unit(); } - // Note that exchanges can't invalidate the debt cache, since if a rate is invalid, + // Note: that exchanges can't invalidate the debt cache, since if a rate is invalid, // the exchange will have failed already. debtCache().updateCachedSynthDebtsWithRates(keys, rates); } @@ -461,6 +462,7 @@ contract Exchanger is Owned, MixinSystemSettings, IExchanger { bool virtualSynth ) internal + nonReentrant returns ( uint amountReceived, uint fee, @@ -854,7 +856,7 @@ contract Exchanger is Owned, MixinSystemSettings, IExchanger { uint weightDecay = getExchangeDynamicFeeWeightDecay(); uint rounds = getExchangeDynamicFeeRounds(); uint[] memory prices; - // Note that we are using cache round ID here for fast read + // Note: We are using cache round ID here for fast read uint currentRoundId = exchangeRates().currentRoundForRate(currencyKey); uint remainingRounds = currentRoundId.sub(lastExchangeRoundId[currencyKey]); if (remainingRounds >= rounds) { diff --git a/contracts/test-helpers/TestableDynamicFee.sol b/contracts/test-helpers/TestableDynamicFee.sol index 5657b0c1ed..cd00935d0f 100644 --- a/contracts/test-helpers/TestableDynamicFee.sol +++ b/contracts/test-helpers/TestableDynamicFee.sol @@ -7,15 +7,15 @@ contract TestableDynamicFee { uint public threshold = 4 * 10**uint(SafeDecimalMath.decimals() - 3); uint public weightDecay = 9 * 10**uint(SafeDecimalMath.decimals() - 1); - function testGetPriceDifferential(uint price, uint previousPrice) public view returns (uint) { + function testGetPriceDifferential(uint price, uint previousPrice) external view returns (uint) { return DynamicFee.getPriceDifferential(price, previousPrice, threshold); } - function testGetPriceWeight(uint round) public view returns (uint) { + function testGetPriceWeight(uint round) external view returns (uint) { return DynamicFee.getRoundDecay(round, weightDecay); } - function testGetDynamicFee(uint[] memory prices, uint lastExchangeDynamicFeeRate) public view returns (uint) { + function testGetDynamicFee(uint[] calldata prices, uint lastExchangeDynamicFeeRate) external view returns (uint) { return DynamicFee.getDynamicFee(prices, threshold, weightDecay, lastExchangeDynamicFeeRate); } } From 9bf189472783c18bc29e62b28664ea904fcff89b Mon Sep 17 00:00:00 2001 From: Lecky Date: Fri, 19 Nov 2021 00:31:54 +1100 Subject: [PATCH 039/133] move _suspendIfRateInvalid to check right after rate --- contracts/Exchanger.sol | 19 ++++++++++--------- 1 file changed, 10 insertions(+), 9 deletions(-) diff --git a/contracts/Exchanger.sol b/contracts/Exchanger.sol index ecb75f47d8..24b42ac421 100644 --- a/contracts/Exchanger.sol +++ b/contracts/Exchanger.sol @@ -484,6 +484,15 @@ contract Exchanger is Owned, MixinSystemSettings, IExchanger, ReentrancyGuard { entry.roundIdForDest ); + // SIP-65: Decentralized Circuit Breaker + // mutative call to suspend system if the rate is invalid + if ( + _suspendIfRateInvalid(sourceCurrencyKey, entry.sourceRate) || + _suspendIfRateInvalid(destinationCurrencyKey, entry.destinationRate) + ) { + return (0, 0, IVirtualSynth(0)); + } + _ensureCanExchange(sourceCurrencyKey, sourceAmount, destinationCurrencyKey); uint sourceAmountAfterSettlement = _settleAndCalcSourceAmountRemaining(sourceAmount, from, sourceCurrencyKey); @@ -499,18 +508,10 @@ contract Exchanger is Owned, MixinSystemSettings, IExchanger, ReentrancyGuard { destinationCurrencyKey ); - // SIP-184: Store last Exchange round ID and dynamic fee to optimise calculation + // SIP-184: Store last Exchange round ID and dynamic fee after _feeRateForExchange calculations lastExchangeRoundId[destinationCurrencyKey] = entry.roundIdForDest; lastExchangeDynamicFeeRate[destinationCurrencyKey] = entry.exchangeDynamicFeeRate; - // SIP-65: Decentralized Circuit Breaker - if ( - _suspendIfRateInvalid(sourceCurrencyKey, entry.sourceRate) || - _suspendIfRateInvalid(destinationCurrencyKey, entry.destinationRate) - ) { - return (0, 0, IVirtualSynth(0)); - } - amountReceived = _deductFeesFromAmount(entry.destinationAmount, entry.exchangeFeeRate); // Note: `fee` is denominated in the destinationCurrencyKey. fee = entry.destinationAmount.sub(amountReceived); From 7c0f2b8df6af6053d5a1738fc67589349e03cee9 Mon Sep 17 00:00:00 2001 From: Lecky Date: Fri, 19 Nov 2021 23:27:50 +1100 Subject: [PATCH 040/133] fixed ExchangerWithFeeRecAlternatives test --- contracts/Exchanger.sol | 6 +++--- .../ExchangerWithFeeRecAlternatives.behaviors.js | 10 ++++++++++ test/contracts/ExchangerWithFeeRecAlternatives.unit.js | 4 ++-- 3 files changed, 15 insertions(+), 5 deletions(-) diff --git a/contracts/Exchanger.sol b/contracts/Exchanger.sol index 24b42ac421..a1cda4060b 100644 --- a/contracts/Exchanger.sol +++ b/contracts/Exchanger.sol @@ -475,7 +475,7 @@ contract Exchanger is Owned, MixinSystemSettings, IExchanger, ReentrancyGuard { entry.roundIdForSrc = exchangeRates().getCurrentRoundId(sourceCurrencyKey); entry.roundIdForDest = exchangeRates().getCurrentRoundId(destinationCurrencyKey); - // Puting the exchangeRate call here as it's mutative for fast cache reading later on + // Puting the exchangeRate call first as it's mutative for fast cache reading later on (entry.destinationAmount, entry.sourceRate, entry.destinationRate) = exchangeRates().effectiveValueAndRatesAtRound( sourceCurrencyKey, sourceAmount, @@ -484,6 +484,8 @@ contract Exchanger is Owned, MixinSystemSettings, IExchanger, ReentrancyGuard { entry.roundIdForDest ); + _ensureCanExchange(sourceCurrencyKey, sourceAmount, destinationCurrencyKey); + // SIP-65: Decentralized Circuit Breaker // mutative call to suspend system if the rate is invalid if ( @@ -493,8 +495,6 @@ contract Exchanger is Owned, MixinSystemSettings, IExchanger, ReentrancyGuard { return (0, 0, IVirtualSynth(0)); } - _ensureCanExchange(sourceCurrencyKey, sourceAmount, destinationCurrencyKey); - uint sourceAmountAfterSettlement = _settleAndCalcSourceAmountRemaining(sourceAmount, from, sourceCurrencyKey); // If, after settlement the user has no balance left (highly unlikely), then return to prevent diff --git a/test/contracts/ExchangerWithFeeRecAlternatives.behaviors.js b/test/contracts/ExchangerWithFeeRecAlternatives.behaviors.js index 8af30298ab..6fc2e768ea 100644 --- a/test/contracts/ExchangerWithFeeRecAlternatives.behaviors.js +++ b/test/contracts/ExchangerWithFeeRecAlternatives.behaviors.js @@ -165,6 +165,16 @@ module.exports = function({ accounts }) { cb(); }); }, + whenMockedEffectiveRateAsEqualAtRound: cb => { + describe(`when mocked with exchange rates at round giving an effective value of 1:1`, () => { + beforeEach(async () => { + this.mocks.ExchangeRates.smocked.effectiveValueAndRatesAtRound.will.return.with( + (srcKey, amount, destKey) => [amount, (1e18).toString(), (1e18).toString()] + ); + }); + cb(); + }); + }, whenMockedLastNRates: cb => { describe(`when mocked 1e18 as last n rates`, () => { beforeEach(async () => { diff --git a/test/contracts/ExchangerWithFeeRecAlternatives.unit.js b/test/contracts/ExchangerWithFeeRecAlternatives.unit.js index 277a6651f2..967fad550b 100644 --- a/test/contracts/ExchangerWithFeeRecAlternatives.unit.js +++ b/test/contracts/ExchangerWithFeeRecAlternatives.unit.js @@ -253,7 +253,7 @@ contract('ExchangerWithFeeRecAlternatives (unit tests)', async accounts => { behaviors.whenMockedWithUintSystemSetting( { setting: 'waitingPeriodSecs', value: '0' }, () => { - behaviors.whenMockedEffectiveRateAsEqual(() => { + behaviors.whenMockedEffectiveRateAsEqualAtRound(() => { behaviors.whenMockedLastNRates(() => { behaviors.whenMockedASingleSynthToIssueAndBurn(() => { behaviors.whenMockedExchangeStatePersistance(() => { @@ -289,7 +289,7 @@ contract('ExchangerWithFeeRecAlternatives (unit tests)', async accounts => { behaviors.whenMockedWithUintSystemSetting( { setting: 'waitingPeriodSecs', value: '0' }, () => { - behaviors.whenMockedEffectiveRateAsEqual(() => { + behaviors.whenMockedEffectiveRateAsEqualAtRound(() => { behaviors.whenMockedLastNRates(() => { behaviors.whenMockedASingleSynthToIssueAndBurn(() => { behaviors.whenMockedExchangeStatePersistance(() => { From f6511d6fbb8b595bba145bd3a364034de000c773 Mon Sep 17 00:00:00 2001 From: Lecky Date: Mon, 22 Nov 2021 00:14:07 +1100 Subject: [PATCH 041/133] fixing exchangeRates, exchanger.spec test - remove time check in setRate as we using cache now; - getRateAndUpdatedTime always get the latest round - Added effectiveValueAndRatesAtRound as mutative function --- contracts/ExchangeRates.sol | 9 ++--- test/contracts/ExchangeRates.js | 59 ++++++++++++++++---------------- test/contracts/Exchanger.spec.js | 6 ++-- test/contracts/setup.js | 6 +++- 4 files changed, 39 insertions(+), 41 deletions(-) diff --git a/contracts/ExchangeRates.sol b/contracts/ExchangeRates.sol index 78d488a452..d2b071775f 100644 --- a/contracts/ExchangeRates.sol +++ b/contracts/ExchangeRates.sol @@ -464,12 +464,6 @@ contract ExchangeRates is Owned, MixinSystemSettings, IExchangeRates, Reentrancy require(newRates[i] != 0, "Zero is not a valid rate, please call deleteRate instead."); require(currencyKey != "sUSD", "Rate of sUSD cannot be updated, it's always UNIT."); - // We should only update the rate if it's at least the same age as the last rate we've got. - if (timeSent < _getUpdatedTime(currencyKey)) { - continue; - } - - // Ok, go ahead with the update. _setRate(currencyKey, 0, newRates[i], timeSent); } @@ -509,6 +503,9 @@ contract ExchangeRates is Owned, MixinSystemSettings, IExchangeRates, Reentrancy function _getRateAndUpdatedTime(bytes32 currencyKey) internal view returns (RateAndUpdatedTime memory entry) { AggregatorV2V3Interface aggregator = aggregators[currencyKey]; uint roundId = currentRoundForRate[currencyKey]; + uint currentRoundId = _getCurrentRoundId(currencyKey); + // Load the latest round ID + roundId = currentRoundId > roundId ? currentRoundId : roundId; entry = _rates[currencyKey][roundId]; diff --git a/test/contracts/ExchangeRates.js b/test/contracts/ExchangeRates.js index 15e7aae97d..43c2eacc73 100644 --- a/test/contracts/ExchangeRates.js +++ b/test/contracts/ExchangeRates.js @@ -85,6 +85,7 @@ contract('Exchange Rates', async accounts => { 'removeAggregator', 'setOracle', 'updateRates', + 'effectiveValueAndRatesAtRound', ]; const withDexPricingFunctions = baseFunctions.concat(['setDexPriceAggregator']); @@ -1512,19 +1513,14 @@ contract('Exchange Rates', async accounts => { }); describe('when a price already exists for sJPY', () => { - const oldPrice = 100; + const oldPrice = toUnit(100); let timeOldSent; beforeEach(async () => { timeOldSent = await currentTime(); - await instance.updateRates( - [sJPY], - [web3.utils.toWei(oldPrice.toString())], - timeOldSent, - { - from: oracle, - } - ); + await instance.updateRates([sJPY], [oldPrice], timeOldSent, { + from: oracle, + }); }); describe('when the ratesAndInvalidForCurrencies is queried with sJPY', () => { let response; @@ -1537,7 +1533,7 @@ contract('Exchange Rates', async accounts => { }); it('and equal to the value', () => { - assert.bnEqual(response[0][0], web3.utils.toWei(oldPrice.toString())); + assert.bnEqual(response[0][0], oldPrice); }); }); describe('when rateAndInvalid is queried with sJPY', () => { @@ -1551,7 +1547,7 @@ contract('Exchange Rates', async accounts => { }); it('and equal to the value', () => { - assert.bnEqual(response[0], web3.utils.toWei(oldPrice.toString())); + assert.bnEqual(response[0], oldPrice); }); }); @@ -1560,7 +1556,7 @@ contract('Exchange Rates', async accounts => { const result = await instance.rateForCurrency(sJPY, { from: accountOne, }); - assert.equal(result.toString(), toUnit(oldPrice)); + assert.equal(result.toString(), oldPrice); }); it('then the timestamp is returned as expected', async () => { const result = await instance.lastRateUpdateTimes(sJPY, { @@ -1577,19 +1573,19 @@ contract('Exchange Rates', async accounts => { }); }); describe('when the price is fetched for sJPY', () => { - it('0 is returned', async () => { + it('old rate is returned as reading from cache', async () => { const result = await instance.rateForCurrency(sJPY, { from: accountOne, }); - assert.equal(result.toNumber(), 0); + assert.bnEqual(result, oldPrice); }); }); describe('when the timestamp is fetched for sJPY', () => { - it('0 is returned', async () => { + it('old time is returned as reading from cache', async () => { const result = await instance.lastRateUpdateTimes(sJPY, { from: accountOne, }); - assert.equal(result.toNumber(), 0); + assert.equal(result.toNumber(), timeOldSent); }); }); describe('when the ratesAndInvalidForCurrencies is queried with sJPY', () => { @@ -1598,12 +1594,12 @@ contract('Exchange Rates', async accounts => { response = await instance.ratesAndInvalidForCurrencies([sJPY]); }); - it('then the rates are invalid', () => { - assert.equal(response[1], true); + it('then the rates are valid as reading from cache', () => { + assert.equal(response[1], false); }); - it('with no value', () => { - assert.bnEqual(response[0][0], '0'); + it('with cache value', () => { + assert.bnEqual(response[0][0], oldPrice); }); }); describe('when the rateAndInvalid is queried with sJPY', () => { @@ -1612,12 +1608,12 @@ contract('Exchange Rates', async accounts => { response = await instance.rateAndInvalid(sJPY); }); - it('then the rate is invalid', () => { - assert.equal(response[1], true); + it('then the rate is valid', () => { + assert.equal(response[1], false); }); - it('with no value', () => { - assert.bnEqual(response[0], '0'); + it('with cache value', () => { + assert.bnEqual(response[0], oldPrice); }); }); @@ -1627,6 +1623,9 @@ contract('Exchange Rates', async accounts => { beforeEach(async () => { await fastForward(50); timestamp = await currentTime(); + // Need to set twice in order to increase the roundId in aggregator + // to be greater than the one in the cache + await aggregatorJPY.setLatestAnswer(convertToDecimals(newRate, 8), timestamp); await aggregatorJPY.setLatestAnswer(convertToDecimals(newRate, 8), timestamp); }); @@ -1691,7 +1690,7 @@ contract('Exchange Rates', async accounts => { const result = await instance.rateForCurrency(sJPY, { from: accountOne, }); - assert.equal(result.toString(), toUnit(oldPrice)); + assert.equal(result.toString(), oldPrice); }); it('and the timestamp is returned as expected', async () => { const result = await instance.lastRateUpdateTimes(sJPY, { @@ -1711,7 +1710,7 @@ contract('Exchange Rates', async accounts => { }); it('and equal to the old value', () => { - assert.bnEqual(response[0][0], web3.utils.toWei(oldPrice.toString())); + assert.bnEqual(response[0][0], oldPrice); }); }); @@ -1726,7 +1725,7 @@ contract('Exchange Rates', async accounts => { }); it('and equal to the old value', () => { - assert.bnEqual(response[0], web3.utils.toWei(oldPrice.toString())); + assert.bnEqual(response[0], oldPrice); }); }); }); @@ -1750,7 +1749,7 @@ contract('Exchange Rates', async accounts => { }); it('with sXTZ having no value', () => { - assert.bnEqual(response[0][0], web3.utils.toWei(oldPrice.toString())); + assert.bnEqual(response[0][0], oldPrice); assert.bnEqual(response[0][1], '0'); }); }); @@ -1768,7 +1767,7 @@ contract('Exchange Rates', async accounts => { }); it('with sXTZ having no value', () => { - assert.bnEqual(responseJPY[0], web3.utils.toWei(oldPrice.toString())); + assert.bnEqual(responseJPY[0], oldPrice); assert.bnEqual(responseXTZ[0], '0'); }); }); @@ -1793,7 +1792,7 @@ contract('Exchange Rates', async accounts => { }); it('and equal to the values', () => { - assert.bnEqual(response[0][0], toUnit(oldPrice.toString())); + assert.bnEqual(response[0][0], oldPrice); assert.bnEqual(response[0][1], toUnit(newRate.toString())); }); }); diff --git a/test/contracts/Exchanger.spec.js b/test/contracts/Exchanger.spec.js index be54ae9beb..7b9567415e 100644 --- a/test/contracts/Exchanger.spec.js +++ b/test/contracts/Exchanger.spec.js @@ -3256,8 +3256,7 @@ contract('Exchanger (spec tests)', async accounts => { rebateAmount, numEntries, } = await exchanger.settlementOwing(account1, sETH); - // Reclaim dropped from 0.25 to 0.2 as fees increased by introducing dynamic fee - assert.bnClose(reclaimAmount, toUnit('0.2'), (1e16).toString()); + assert.bnClose(reclaimAmount, toUnit('0.25'), (1e16).toString()); assert.bnClose(rebateAmount, toUnit('0.25'), (1e16).toString()); assert.equal(numEntries, '2'); }); @@ -3276,8 +3275,7 @@ contract('Exchanger (spec tests)', async accounts => { rebateAmount, numEntries, } = await exchanger.settlementOwing(account1, sETH); - // Reclaim dropped from 0.25 to 0.2 as fees increased by introducing dynamic fee - assert.bnClose(reclaimAmount, toUnit('0.2'), (1e16).toString()); + assert.bnClose(reclaimAmount, toUnit('0.25'), (1e16).toString()); assert.bnClose(rebateAmount, toUnit('0.25'), (1e16).toString()); assert.equal(numEntries, '3'); }); diff --git a/test/contracts/setup.js b/test/contracts/setup.js index f1caa6482d..3bc9de56b0 100644 --- a/test/contracts/setup.js +++ b/test/contracts/setup.js @@ -127,7 +127,11 @@ const setupContract = async ({ if (Object.keys((await artifacts.readArtifact(contract)).linkReferences).length > 0) { const safeDecimalMath = await artifacts.require('SafeDecimalMath').new(); artifact.link(safeDecimalMath); - if (/^Exchanger$|^ExchangerWithVirtualSynth$/.test(artifact._json.contractName)) { + if ( + /^Exchanger$|^ExchangerWithVirtualSynth$|^ExchangerWithFeeRecAlternatives$/.test( + artifact._json.contractName + ) + ) { const DynamicFee = artifacts.require('DynamicFee'); DynamicFee.link(safeDecimalMath); artifact.link(await DynamicFee.new()); From b7bc872d550f286a48548ab143d1f1a328a1c436 Mon Sep 17 00:00:00 2001 From: Lecky Date: Mon, 22 Nov 2021 22:49:50 +1100 Subject: [PATCH 042/133] fixing exchangeAtomically test --- test/contracts/Exchanger.spec.js | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/test/contracts/Exchanger.spec.js b/test/contracts/Exchanger.spec.js index 7b9567415e..f4127390d3 100644 --- a/test/contracts/Exchanger.spec.js +++ b/test/contracts/Exchanger.spec.js @@ -2221,6 +2221,9 @@ contract('Exchanger (spec tests)', async accounts => { await exchangeRates.addAggregator(sETH, aggregator.address, { from: owner }); // set a 0 rate to prevent invalid rate from causing a revert on exchange for (let i = 0; i < DYNAMIC_FEE_ROUNDS; i++) { + // Need to run twice in order to increase the roundId + // to be greater than the one in the cache + await aggregator.setLatestAnswer('0', await currentTime()); await aggregator.setLatestAnswer('0', await currentTime()); } }); @@ -2550,11 +2553,18 @@ contract('Exchanger (spec tests)', async accounts => { const ethOnCL = toUnit('200'); // 1 over the ethOnDex beforeEach(async () => { + // Disable exchange dynamic fee on exchange atomically + await systemSettings.setExchangeDynamicFeeRounds('0', { from: owner }); // CL aggregator with past price data const aggregator = await MockAggregator.new({ from: owner }); await exchangeRates.addAggregator(sETH, aggregator.address, { from: owner }); // set prices with no valatility - await aggregator.setLatestAnswer(ethOnCL, (await currentTime()) - 20 * 60); + // Need to start from round 11 as the first 10 rounds are used for the setup + await aggregator.setLatestAnswerWithRound( + ethOnCL, + (await currentTime()) - 20 * 60, + '11' + ); await aggregator.setLatestAnswer(ethOnCL, (await currentTime()) - 15 * 60); await aggregator.setLatestAnswer(ethOnCL, (await currentTime()) - 10 * 60); await aggregator.setLatestAnswer(ethOnCL, (await currentTime()) - 5 * 60); From c01beb518e884e017801a1810b6ecd7967a0d389 Mon Sep 17 00:00:00 2001 From: Lecky Date: Tue, 23 Nov 2021 17:26:02 +1100 Subject: [PATCH 043/133] Updating releases.jon --- publish/releases.json | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/publish/releases.json b/publish/releases.json index 6a267fe806..b40fa3b2c9 100644 --- a/publish/releases.json +++ b/publish/releases.json @@ -393,6 +393,11 @@ ], "released": "both" }, + { + "sip": 184, + "layer": "both", + "sources": ["Exchanger", "ExchangeRates", "SystemSettings"] + }, { "sip": 187, "layer": "both", From 95bedc8e1cacfe0a60ad453b1d61c7be522c5c22 Mon Sep 17 00:00:00 2001 From: Lecky Date: Tue, 23 Nov 2021 18:04:29 +1100 Subject: [PATCH 044/133] adding more tests scenarios --- test/contracts/TestableDynamicFee.js | 36 +++++++++++++++++++++++++++- 1 file changed, 35 insertions(+), 1 deletion(-) diff --git a/test/contracts/TestableDynamicFee.js b/test/contracts/TestableDynamicFee.js index a38c2fb950..8755623754 100644 --- a/test/contracts/TestableDynamicFee.js +++ b/test/contracts/TestableDynamicFee.js @@ -35,7 +35,7 @@ contract('TestableDynamicFee', () => { assert.bnEqual(priceWeight2, toUnit('0.81')); }); - it('Can get dynamic fee', async () => { + it('Can get dynamic fee round 14-23', async () => { const prices = [ toUnit('49234.65005734'), toUnit('49535.05178912'), @@ -51,4 +51,38 @@ contract('TestableDynamicFee', () => { const dynamicFee = await testableDynamicFee.testGetDynamicFee(prices, '0'); assert.bnEqual(dynamicFee, '2064427530203592'); }); + + it('Can get dynamic fee round 15-24', async () => { + const prices = [ + toUnit('49190.99117585'), + toUnit('49234.65005734'), + toUnit('49535.05178912'), + toUnit('49714.05205647'), + toUnit('49691.8024553899'), + toUnit('49714.05205647'), + toUnit('49722.83886705'), + toUnit('49838.87627216'), + toUnit('49842.74988613'), + toUnit('49933.34034209'), + ]; + const dynamicFee = await testableDynamicFee.testGetDynamicFee(prices, '0'); + assert.bnEqual(dynamicFee, '1857984777183232'); + }); + + it('Can get dynamic fee round 34-43', async () => { + const prices = [ + toUnit('48364.4121895'), + toUnit('48954.93260767'), + toUnit('48964.89486113'), + toUnit('49054.03062906'), + toUnit('49009.46274509'), + toUnit('49054.03062906'), + toUnit('49093.89744338'), + toUnit('49095.24231598'), + toUnit('49101.41'), + toUnit('49208'), + ]; + const dynamicFee = await testableDynamicFee.testGetDynamicFee(prices, '0'); + assert.bnEqual(dynamicFee, '8062531530836597'); + }); }); From e7bec66d1e61573970572d7cf72e0ce8dac2dc8a Mon Sep 17 00:00:00 2001 From: Lecky Date: Tue, 23 Nov 2021 18:34:15 +1100 Subject: [PATCH 045/133] rename method _getRateAndUpdatedTimeAtRound to be consistent --- contracts/ExchangeRates.sol | 16 ++++++++-------- contracts/ExchangeRatesWithDexPricing.sol | 2 +- 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/contracts/ExchangeRates.sol b/contracts/ExchangeRates.sol index d2b071775f..a61e7e0568 100644 --- a/contracts/ExchangeRates.sol +++ b/contracts/ExchangeRates.sol @@ -149,7 +149,7 @@ contract ExchangeRates is Owned, MixinSystemSettings, IExchangeRates, Reentrancy ) { uint time; - (sourceRate, time) = _getRateAndTimestampAtRound(sourceCurrencyKey, roundIdForSrc); + (sourceRate, time) = _getRateAndUpdatedTimeAtRound(sourceCurrencyKey, roundIdForSrc); // cache for fast read _setRate(sourceCurrencyKey, roundIdForSrc, sourceRate, time); // If there's no change in the currency, then just return the amount they gave us @@ -157,7 +157,7 @@ contract ExchangeRates is Owned, MixinSystemSettings, IExchangeRates, Reentrancy destinationRate = sourceRate; value = sourceAmount; } else { - (destinationRate, time) = _getRateAndTimestampAtRound(destinationCurrencyKey, roundIdForDest); + (destinationRate, time) = _getRateAndUpdatedTimeAtRound(destinationCurrencyKey, roundIdForDest); // cache for fast read _setRate(destinationCurrencyKey, roundIdForDest, destinationRate, time); // prevent divide-by 0 error (this happens if the dest is not a valid rate) @@ -203,7 +203,7 @@ contract ExchangeRates is Owned, MixinSystemSettings, IExchangeRates, Reentrancy uint roundId = startingRoundId; uint nextTimestamp = 0; while (true) { - (, nextTimestamp) = _getRateAndTimestampAtRound(currencyKey, roundId + 1); + (, nextTimestamp) = _getRateAndUpdatedTimeAtRound(currencyKey, roundId + 1); // if there's no new round, then the previous roundId was the latest if (nextTimestamp == 0 || nextTimestamp > startingTimestamp + timediff) { return roundId; @@ -227,8 +227,8 @@ contract ExchangeRates is Owned, MixinSystemSettings, IExchangeRates, Reentrancy // If there's no change in the currency, then just return the amount they gave us if (sourceCurrencyKey == destinationCurrencyKey) return sourceAmount; - (uint srcRate, ) = _getRateAndTimestampAtRound(sourceCurrencyKey, roundIdForSrc); - (uint destRate, ) = _getRateAndTimestampAtRound(destinationCurrencyKey, roundIdForDest); + (uint srcRate, ) = _getRateAndUpdatedTimeAtRound(sourceCurrencyKey, roundIdForSrc); + (uint destRate, ) = _getRateAndUpdatedTimeAtRound(destinationCurrencyKey, roundIdForDest); if (destRate == 0) { // prevent divide-by 0 error (this can happen when roundIDs jump epochs due // to aggregator upgrades) @@ -239,7 +239,7 @@ contract ExchangeRates is Owned, MixinSystemSettings, IExchangeRates, Reentrancy } function rateAndTimestampAtRound(bytes32 currencyKey, uint roundId) external view returns (uint rate, uint time) { - return _getRateAndTimestampAtRound(currencyKey, roundId); + return _getRateAndUpdatedTimeAtRound(currencyKey, roundId); } function lastRateUpdateTimes(bytes32 currencyKey) external view returns (uint256) { @@ -314,7 +314,7 @@ contract ExchangeRates is Owned, MixinSystemSettings, IExchangeRates, Reentrancy for (uint i = 0; i < numRounds; i++) { // fetch the rate and treat is as current, so inverse limits if frozen will always be applied // regardless of current rate - (rates[i], times[i]) = _getRateAndTimestampAtRound(currencyKey, roundId); + (rates[i], times[i]) = _getRateAndUpdatedTimeAtRound(currencyKey, roundId); if (roundId == 0) { // if we hit the last round, then return what we have @@ -540,7 +540,7 @@ contract ExchangeRates is Owned, MixinSystemSettings, IExchangeRates, Reentrancy } } - function _getRateAndTimestampAtRound(bytes32 currencyKey, uint roundId) internal view returns (uint rate, uint time) { + function _getRateAndUpdatedTimeAtRound(bytes32 currencyKey, uint roundId) internal view returns (uint rate, uint time) { AggregatorV2V3Interface aggregator = aggregators[currencyKey]; RateAndUpdatedTime memory update = _rates[currencyKey][roundId]; diff --git a/contracts/ExchangeRatesWithDexPricing.sol b/contracts/ExchangeRatesWithDexPricing.sol index 1548a61030..0de6c565ca 100644 --- a/contracts/ExchangeRatesWithDexPricing.sol +++ b/contracts/ExchangeRatesWithDexPricing.sol @@ -143,7 +143,7 @@ contract ExchangeRatesWithDexPricing is ExchangeRates { uint considerationWindowStart = block.timestamp.sub(considerationWindow); uint roundId = _getCurrentRoundId(currencyKey); for (updateThreshold; updateThreshold > 0; updateThreshold--) { - (uint rate, uint time) = _getRateAndTimestampAtRound(currencyKey, roundId); + (uint rate, uint time) = _getRateAndUpdatedTimeAtRound(currencyKey, roundId); if (time != 0 && time < considerationWindowStart) { // Round was outside consideration window so we can stop querying further rounds return false; From c7041521b331bda31f3b6d68cdc6370c0e1d0c8e Mon Sep 17 00:00:00 2001 From: Lecky Date: Tue, 23 Nov 2021 21:00:34 +1100 Subject: [PATCH 046/133] Redundant expression "updateThreshold" inExchangeRatesWithDexPricing --- contracts/ExchangeRatesWithDexPricing.sol | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/contracts/ExchangeRatesWithDexPricing.sol b/contracts/ExchangeRatesWithDexPricing.sol index 0de6c565ca..14516482f1 100644 --- a/contracts/ExchangeRatesWithDexPricing.sol +++ b/contracts/ExchangeRatesWithDexPricing.sol @@ -142,7 +142,7 @@ contract ExchangeRatesWithDexPricing is ExchangeRates { // updates is a good proxy for price volatility. uint considerationWindowStart = block.timestamp.sub(considerationWindow); uint roundId = _getCurrentRoundId(currencyKey); - for (updateThreshold; updateThreshold > 0; updateThreshold--) { + for (; updateThreshold > 0; updateThreshold--) { (uint rate, uint time) = _getRateAndUpdatedTimeAtRound(currencyKey, roundId); if (time != 0 && time < considerationWindowStart) { // Round was outside consideration window so we can stop querying further rounds From 968455107f90d8182c7d07f5e6c708b57a226d8e Mon Sep 17 00:00:00 2001 From: Lecky Date: Tue, 23 Nov 2021 21:54:05 +1100 Subject: [PATCH 047/133] remove duplicated import --- contracts/Exchanger.sol | 1 - 1 file changed, 1 deletion(-) diff --git a/contracts/Exchanger.sol b/contracts/Exchanger.sol index a1cda4060b..7b51851336 100644 --- a/contracts/Exchanger.sol +++ b/contracts/Exchanger.sol @@ -7,7 +7,6 @@ import "./MixinSystemSettings.sol"; import "./interfaces/IExchanger.sol"; // Libraries -import "./SafeDecimalMath.sol"; import "./DynamicFee.sol"; import "openzeppelin-solidity-2.3.0/contracts/utils/ReentrancyGuard.sol"; From eabd924f5094fd14a0694d2af1a0f2437aabc7a5 Mon Sep 17 00:00:00 2001 From: Lecky Date: Wed, 24 Nov 2021 23:56:56 +1100 Subject: [PATCH 048/133] Adding DynamicFee into releases - linking DynamicFee in Deployer - adding DynamicFee to deployer.deployContract --- publish/releases.json | 2 +- publish/src/Deployer.js | 2 +- publish/src/commands/deploy/deploy-core.js | 4 ++++ 3 files changed, 6 insertions(+), 2 deletions(-) diff --git a/publish/releases.json b/publish/releases.json index b40fa3b2c9..65518b74bd 100644 --- a/publish/releases.json +++ b/publish/releases.json @@ -396,7 +396,7 @@ { "sip": 184, "layer": "both", - "sources": ["Exchanger", "ExchangeRates", "SystemSettings"] + "sources": ["Exchanger", "ExchangeRates", "SystemSettings", "DynamicFee"] }, { "sip": 187, diff --git a/publish/src/Deployer.js b/publish/src/Deployer.js index c29455712a..c30d2e0b57 100644 --- a/publish/src/Deployer.js +++ b/publish/src/Deployer.js @@ -184,7 +184,7 @@ class Deployer { // Any contract after SafeDecimalMath can automatically get linked. // Doing this with bytecode that doesn't require the library is a no-op. let bytecode = compiled.evm.bytecode.object; - ['SafeDecimalMath', 'Math'].forEach(contractName => { + ['SafeDecimalMath', 'Math', 'DynamicFee'].forEach(contractName => { if (this.deployedContracts[contractName]) { bytecode = linker.linkBytecode(bytecode, { [source + '.sol']: { diff --git a/publish/src/commands/deploy/deploy-core.js b/publish/src/commands/deploy/deploy-core.js index 76f39f1324..f1c6c6087d 100644 --- a/publish/src/commands/deploy/deploy-core.js +++ b/publish/src/commands/deploy/deploy-core.js @@ -22,6 +22,10 @@ module.exports = async ({ name: 'SafeDecimalMath', }); + await deployer.deployContract({ + name: 'DynamicFee', + }); + await deployer.deployContract({ name: 'Math', }); From 6cd657cfd807298b7bb0048bce1d3684371f9cd2 Mon Sep 17 00:00:00 2001 From: Lecky Date: Thu, 25 Nov 2021 12:09:27 +1100 Subject: [PATCH 049/133] Adding DynamicFee deploy true into local config --- publish/deployed/local-ovm/config.json | 3 +++ publish/deployed/local/config.json | 3 +++ 2 files changed, 6 insertions(+) diff --git a/publish/deployed/local-ovm/config.json b/publish/deployed/local-ovm/config.json index 65b9e6f5e0..2f339e6938 100644 --- a/publish/deployed/local-ovm/config.json +++ b/publish/deployed/local-ovm/config.json @@ -157,5 +157,8 @@ }, "SynthRedeemer": { "deploy": true + }, + "DynamicFee": { + "deploy": true } } diff --git a/publish/deployed/local/config.json b/publish/deployed/local/config.json index 961634750e..9bc9ea18c7 100644 --- a/publish/deployed/local/config.json +++ b/publish/deployed/local/config.json @@ -385,5 +385,8 @@ }, "SynthRedeemer": { "deploy": true + }, + "DynamicFee": { + "deploy": true } } From eb3bea77d65d8100be555a57f5e205594e298e77 Mon Sep 17 00:00:00 2001 From: Lecky Date: Thu, 25 Nov 2021 14:10:19 +1100 Subject: [PATCH 050/133] Add missing readTarget in configure-system-settings.js --- contracts/MixinSystemSettings.sol | 1 + publish/src/commands/deploy/configure-system-settings.js | 3 +++ 2 files changed, 4 insertions(+) diff --git a/contracts/MixinSystemSettings.sol b/contracts/MixinSystemSettings.sol index 2c11f67cfb..b080611bf7 100644 --- a/contracts/MixinSystemSettings.sol +++ b/contracts/MixinSystemSettings.sol @@ -22,6 +22,7 @@ contract MixinSystemSettings is MixinResolver { bytes32 internal constant SETTING_EXCHANGE_FEE_RATE = "exchangeFeeRate"; bytes32 internal constant SETTING_DYNAMIC_FEE_THRESHOLD = "dynamicFeeThreshold"; bytes32 internal constant SETTING_DYNAMIC_FEE_WEIGHT_DECAY = "dynamicFeeWeightDecay"; + /* ========== End Exchange Fees Related ========== */ bytes32 internal constant SETTING_DYNAMIC_FEE_ROUNDS = "dynamicFeeRounds"; bytes32 internal constant SETTING_MINIMUM_STAKE_TIME = "minimumStakeTime"; bytes32 internal constant SETTING_AGGREGATOR_WARNING_FLAGS = "aggregatorWarningFlags"; diff --git a/publish/src/commands/deploy/configure-system-settings.js b/publish/src/commands/deploy/configure-system-settings.js index aa3e83be28..2e3b23ae61 100644 --- a/publish/src/commands/deploy/configure-system-settings.js +++ b/publish/src/commands/deploy/configure-system-settings.js @@ -335,6 +335,7 @@ module.exports = async ({ contract: 'SystemSettings', target: SystemSettings, read: 'getExchangeDynamicFeeThreshold', + readTarget: previousSystemSettings, expected: input => input !== '0', // only change if zero write: 'setExchangeDynamicFeeThreshold', writeArg: await getDeployParameter('DYNAMIC_FEE_THRESHOLD'), @@ -344,6 +345,7 @@ module.exports = async ({ contract: 'SystemSettings', target: SystemSettings, read: 'getExchangeDynamicFeeWeightDecay', + readTarget: previousSystemSettings, expected: input => input !== '0', // only change if zero write: 'setExchangeDynamicFeeWeightDecay', writeArg: await getDeployParameter('DYNAMIC_FEE_WEIGHT_DECAY'), @@ -353,6 +355,7 @@ module.exports = async ({ contract: 'SystemSettings', target: SystemSettings, read: 'getExchangeDynamicFeeRounds', + readTarget: previousSystemSettings, expected: input => input !== '0', // only change if zero write: 'setExchangeDynamicFeeRounds', writeArg: await getDeployParameter('DYNAMIC_FEE_WEIGHT_DECAY'), From 72329bca70583edd1667bd34c2482e9359a42983 Mon Sep 17 00:00:00 2001 From: Lecky Date: Thu, 25 Nov 2021 15:27:15 +1100 Subject: [PATCH 051/133] fixing configure-system-settings to only change if non-default --- .../deploy/configure-system-settings.js | 17 ++++++++++------- 1 file changed, 10 insertions(+), 7 deletions(-) diff --git a/publish/src/commands/deploy/configure-system-settings.js b/publish/src/commands/deploy/configure-system-settings.js index 2e3b23ae61..3c76153f90 100644 --- a/publish/src/commands/deploy/configure-system-settings.js +++ b/publish/src/commands/deploy/configure-system-settings.js @@ -331,35 +331,38 @@ module.exports = async ({ }); // SIP-184 Exchange Dynamic Fee Rate + const dynamicFeeThreshold = await getDeployParameter('DYNAMIC_FEE_THRESHOLD'); await runStep({ contract: 'SystemSettings', target: SystemSettings, read: 'getExchangeDynamicFeeThreshold', readTarget: previousSystemSettings, - expected: input => input !== '0', // only change if zero + expected: input => input === dynamicFeeThreshold, // only change if non-default write: 'setExchangeDynamicFeeThreshold', - writeArg: await getDeployParameter('DYNAMIC_FEE_THRESHOLD'), + writeArg: dynamicFeeThreshold, comment: 'Set exchange dynamic fee threshold (SIP-184)', }); + const dynamicFeeWeightDecay = await getDeployParameter('DYNAMIC_FEE_WEIGHT_DECAY'); await runStep({ contract: 'SystemSettings', target: SystemSettings, read: 'getExchangeDynamicFeeWeightDecay', readTarget: previousSystemSettings, - expected: input => input !== '0', // only change if zero + expected: input => input === dynamicFeeWeightDecay, // only change if non-default write: 'setExchangeDynamicFeeWeightDecay', - writeArg: await getDeployParameter('DYNAMIC_FEE_WEIGHT_DECAY'), + writeArg: dynamicFeeWeightDecay, comment: 'Set exchange dynamic fee weight decay (SIP-184)', }); + const dynamicFeeRounds = await getDeployParameter('DYNAMIC_FEE_ROUNDS'); await runStep({ contract: 'SystemSettings', target: SystemSettings, read: 'getExchangeDynamicFeeRounds', readTarget: previousSystemSettings, - expected: input => input !== '0', // only change if zero + expected: input => input === dynamicFeeRounds, // only change if non-default write: 'setExchangeDynamicFeeRounds', - writeArg: await getDeployParameter('DYNAMIC_FEE_WEIGHT_DECAY'), - comment: 'Set exchange dynamic fee weight decay (SIP-184)', + writeArg: dynamicFeeRounds, + comment: 'Set exchange dynamic fee rounds (SIP-184)', }); // SIP-120 Atomic swap settings From ac1c91d05fd7db74a21e7092257d12c1fdabf033 Mon Sep 17 00:00:00 2001 From: Lecky Date: Thu, 25 Nov 2021 16:32:09 +1100 Subject: [PATCH 052/133] Add missing external functions into SystemSettings --- contracts/MixinSystemSettings.sol | 2 ++ contracts/SystemSettings.sol | 19 +++++++++++++++++++ .../deploy/configure-system-settings.js | 7 +++---- 3 files changed, 24 insertions(+), 4 deletions(-) diff --git a/contracts/MixinSystemSettings.sol b/contracts/MixinSystemSettings.sol index b080611bf7..e2a9625707 100644 --- a/contracts/MixinSystemSettings.sol +++ b/contracts/MixinSystemSettings.sol @@ -153,6 +153,8 @@ contract MixinSystemSettings is MixinResolver { return flexibleStorage().getUIntValue(SETTING_CONTRACT_NAME, SETTING_DYNAMIC_FEE_ROUNDS); } + /* ========== End Exchange Related Fees ========== */ + function getMinimumStakeTime() internal view returns (uint) { return flexibleStorage().getUIntValue(SETTING_CONTRACT_NAME, SETTING_MINIMUM_STAKE_TIME); } diff --git a/contracts/SystemSettings.sol b/contracts/SystemSettings.sol index d051784a01..ff772514ab 100644 --- a/contracts/SystemSettings.sol +++ b/contracts/SystemSettings.sol @@ -118,10 +118,29 @@ contract SystemSettings is Owned, MixinSystemSettings, ISystemSettings { return getRateStalePeriod(); } + /* ========== Exchange Related Fees ========== */ function exchangeFeeRate(bytes32 currencyKey) external view returns (uint) { return getExchangeFeeRate(currencyKey); } + // SIP-184 Dynamic Fee + /// @notice Get the dynamic fee threshold + function exchangeDynamicFeeThreshold() external view returns (uint) { + return getExchangeDynamicFeeThreshold(); + } + + /// @notice Get the dynamic fee weight decay per round + function exchangeDynamicFeeWeightDecay() external view returns (uint) { + return getExchangeDynamicFeeWeightDecay(); + } + + /// @notice Get the dynamic fee total rounds for calculation + function exchangeDynamicFeeRounds() external view returns (uint) { + return getExchangeDynamicFeeRounds(); + } + + /* ========== End Exchange Related Fees ========== */ + function minimumStakeTime() external view returns (uint) { return getMinimumStakeTime(); } diff --git a/publish/src/commands/deploy/configure-system-settings.js b/publish/src/commands/deploy/configure-system-settings.js index 3c76153f90..b41884334a 100644 --- a/publish/src/commands/deploy/configure-system-settings.js +++ b/publish/src/commands/deploy/configure-system-settings.js @@ -14,7 +14,6 @@ module.exports = async ({ deployer, methodCallGasLimit, useOvm, - generateSolidity, getDeployParameter, network, runStep, @@ -335,7 +334,7 @@ module.exports = async ({ await runStep({ contract: 'SystemSettings', target: SystemSettings, - read: 'getExchangeDynamicFeeThreshold', + read: 'exchangeDynamicFeeThreshold', readTarget: previousSystemSettings, expected: input => input === dynamicFeeThreshold, // only change if non-default write: 'setExchangeDynamicFeeThreshold', @@ -346,7 +345,7 @@ module.exports = async ({ await runStep({ contract: 'SystemSettings', target: SystemSettings, - read: 'getExchangeDynamicFeeWeightDecay', + read: 'exchangeDynamicFeeWeightDecay', readTarget: previousSystemSettings, expected: input => input === dynamicFeeWeightDecay, // only change if non-default write: 'setExchangeDynamicFeeWeightDecay', @@ -357,7 +356,7 @@ module.exports = async ({ await runStep({ contract: 'SystemSettings', target: SystemSettings, - read: 'getExchangeDynamicFeeRounds', + read: 'exchangeDynamicFeeRounds', readTarget: previousSystemSettings, expected: input => input === dynamicFeeRounds, // only change if non-default write: 'setExchangeDynamicFeeRounds', From fc3eb7e18e5fbf497bc16d31ee88fda435672b6a Mon Sep 17 00:00:00 2001 From: Lecky Date: Fri, 26 Nov 2021 22:12:09 +1100 Subject: [PATCH 053/133] Adding tests for systemSettings --- test/contracts/SystemSettings.js | 67 ++++++++++++++++++++++++++++++++ 1 file changed, 67 insertions(+) diff --git a/test/contracts/SystemSettings.js b/test/contracts/SystemSettings.js index 7618543a08..d5e42f17c0 100644 --- a/test/contracts/SystemSettings.js +++ b/test/contracts/SystemSettings.js @@ -1518,4 +1518,71 @@ contract('SystemSettings', async accounts => { }); }); }); + + describe('setExchangeDynamicFeeThreshold()', () => { + const threshold = toUnit('0.004'); + it('only owner can invoke', async () => { + await onlyGivenAddressCanInvoke({ + fnc: systemSettings.setExchangeDynamicFeeThreshold, + args: [threshold], + accounts, + address: owner, + reason: 'Only the contract owner may perform this action', + }); + }); + it('the owner can invoke and replace with emitted event', async () => { + const txn = await systemSettings.setExchangeDynamicFeeThreshold(threshold, { from: owner }); + const actual = await systemSettings.exchangeDynamicFeeThreshold(); + assert.bnEqual( + actual, + threshold, + 'Configured exchange dynamic fee threshold is set correctly' + ); + assert.eventEqual(txn, 'ExchangeDynamicFeeThresholdUpdated', [threshold]); + }); + }); + + describe('setExchangeDynamicFeeWeightDecay()', () => { + const weightDecay = toUnit('0.9'); + it('only owner can invoke', async () => { + await onlyGivenAddressCanInvoke({ + fnc: systemSettings.setExchangeDynamicFeeWeightDecay, + args: [weightDecay], + accounts, + address: owner, + reason: 'Only the contract owner may perform this action', + }); + }); + it('the owner can invoke and replace with emitted event', async () => { + const txn = await systemSettings.setExchangeDynamicFeeWeightDecay(weightDecay, { + from: owner, + }); + const actual = await systemSettings.exchangeDynamicFeeWeightDecay(); + assert.bnEqual( + actual, + weightDecay, + 'Configured exchange dynamic fee weight decay is set correctly' + ); + assert.eventEqual(txn, 'ExchangeDynamicFeeWeightDecayUpdated', [weightDecay]); + }); + }); + + describe('setExchangeDynamicFeeRounds()', () => { + const rounds = '10'; + it('only owner can invoke', async () => { + await onlyGivenAddressCanInvoke({ + fnc: systemSettings.setExchangeDynamicFeeRounds, + args: [rounds], + accounts, + address: owner, + reason: 'Only the contract owner may perform this action', + }); + }); + it('the owner can invoke and replace with emitted event', async () => { + const txn = await systemSettings.setExchangeDynamicFeeRounds(rounds, { from: owner }); + const actual = await systemSettings.exchangeDynamicFeeRounds(); + assert.equal(actual, rounds, 'Configured exchange dynamic fee rounds is set correctly'); + assert.eventEqual(txn, 'ExchangeDynamicFeeRoundsUpdated', [rounds]); + }); + }); }); From b2536351d9a87706adfef852484f307398713df5 Mon Sep 17 00:00:00 2001 From: Lecky Date: Sun, 28 Nov 2021 13:28:39 +1100 Subject: [PATCH 054/133] fix PR comment - move struct from exchanger into IExchanger - move nonReentrant into external exchange --- contracts/ExchangeRates.sol | 9 +++++-- contracts/Exchanger.sol | 37 ++++++----------------------- contracts/interfaces/IExchanger.sol | 21 ++++++++++++++++ 3 files changed, 35 insertions(+), 32 deletions(-) diff --git a/contracts/ExchangeRates.sol b/contracts/ExchangeRates.sol index a61e7e0568..eccaf33065 100644 --- a/contracts/ExchangeRates.sol +++ b/contracts/ExchangeRates.sol @@ -150,7 +150,7 @@ contract ExchangeRates is Owned, MixinSystemSettings, IExchangeRates, Reentrancy { uint time; (sourceRate, time) = _getRateAndUpdatedTimeAtRound(sourceCurrencyKey, roundIdForSrc); - // cache for fast read + // cacheing to save external call _setRate(sourceCurrencyKey, roundIdForSrc, sourceRate, time); // If there's no change in the currency, then just return the amount they gave us if (sourceCurrencyKey == destinationCurrencyKey) { @@ -158,7 +158,7 @@ contract ExchangeRates is Owned, MixinSystemSettings, IExchangeRates, Reentrancy value = sourceAmount; } else { (destinationRate, time) = _getRateAndUpdatedTimeAtRound(destinationCurrencyKey, roundIdForDest); - // cache for fast read + // cacheing to save external call _setRate(destinationCurrencyKey, roundIdForDest, destinationRate, time); // prevent divide-by 0 error (this happens if the dest is not a valid rate) if (destinationRate > 0) { @@ -464,6 +464,11 @@ contract ExchangeRates is Owned, MixinSystemSettings, IExchangeRates, Reentrancy require(newRates[i] != 0, "Zero is not a valid rate, please call deleteRate instead."); require(currencyKey != "sUSD", "Rate of sUSD cannot be updated, it's always UNIT."); + // We should only update the rate if it's at least the same age as the last rate we've got. + if (timeSent < _getUpdatedTime(currencyKey)) { + continue; + } + _setRate(currencyKey, 0, newRates[i], timeSent); } diff --git a/contracts/Exchanger.sol b/contracts/Exchanger.sol index 7b51851336..9ae98c4f3a 100644 --- a/contracts/Exchanger.sol +++ b/contracts/Exchanger.sol @@ -74,27 +74,6 @@ contract Exchanger is Owned, MixinSystemSettings, IExchanger, ReentrancyGuard { using SafeMath for uint; using SafeDecimalMath for uint; - struct ExchangeEntrySettlement { - bytes32 src; - uint amount; - bytes32 dest; - uint reclaim; - uint rebate; - uint srcRoundIdAtPeriodEnd; - uint destRoundIdAtPeriodEnd; - uint timestamp; - } - - struct ExchangeEntry { - uint sourceRate; - uint destinationRate; - uint destinationAmount; - uint exchangeFeeRate; - uint exchangeDynamicFeeRate; - uint roundIdForSrc; - uint roundIdForDest; - } - bytes32 public constant CONTRACT_NAME = "Exchanger"; bytes32 internal constant sUSD = "sUSD"; @@ -220,14 +199,14 @@ contract Exchanger is Owned, MixinSystemSettings, IExchanger, ReentrancyGuard { uint reclaimAmount, uint rebateAmount, uint numEntries, - ExchangeEntrySettlement[] memory + IExchanger.ExchangeEntrySettlement[] memory ) { // Need to sum up all reclaim and rebate amounts for the user and the currency key numEntries = exchangeState().getLengthOfEntries(account, currencyKey); // For each unsettled exchange - ExchangeEntrySettlement[] memory settlements = new ExchangeEntrySettlement[](numEntries); + IExchanger.ExchangeEntrySettlement[] memory settlements = new IExchanger.ExchangeEntrySettlement[](numEntries); for (uint i = 0; i < numEntries; i++) { uint reclaim; uint rebate; @@ -264,7 +243,7 @@ contract Exchanger is Owned, MixinSystemSettings, IExchanger, ReentrancyGuard { } } - settlements[i] = ExchangeEntrySettlement({ + settlements[i] = IExchanger.ExchangeEntrySettlement({ src: exchangeEntry.src, amount: exchangeEntry.amount, dest: exchangeEntry.dest, @@ -357,7 +336,7 @@ contract Exchanger is Owned, MixinSystemSettings, IExchanger, ReentrancyGuard { bool virtualSynth, address rewardAddress, bytes32 trackingCode - ) external onlySynthetixorSynth returns (uint amountReceived, IVirtualSynth vSynth) { + ) external onlySynthetixorSynth nonReentrant returns (uint amountReceived, IVirtualSynth vSynth) { uint fee; if (from != exchangeForAddress) { require(delegateApprovals().canExchangeFor(exchangeForAddress, from), "Not approved to act on behalf"); @@ -461,7 +440,6 @@ contract Exchanger is Owned, MixinSystemSettings, IExchanger, ReentrancyGuard { bool virtualSynth ) internal - nonReentrant returns ( uint amountReceived, uint fee, @@ -469,12 +447,12 @@ contract Exchanger is Owned, MixinSystemSettings, IExchanger, ReentrancyGuard { ) { // Using struct to resolve stack too deep error - ExchangeEntry memory entry; + IExchanger.ExchangeEntry memory entry; entry.roundIdForSrc = exchangeRates().getCurrentRoundId(sourceCurrencyKey); entry.roundIdForDest = exchangeRates().getCurrentRoundId(destinationCurrencyKey); - // Puting the exchangeRate call first as it's mutative for fast cache reading later on + // Puting the exchangeRate call first as it's mutative for cheap cache reading later on (entry.destinationAmount, entry.sourceRate, entry.destinationRate) = exchangeRates().effectiveValueAndRatesAtRound( sourceCurrencyKey, sourceAmount, @@ -713,7 +691,7 @@ contract Exchanger is Owned, MixinSystemSettings, IExchanger, ReentrancyGuard { { require(maxSecsLeftInWaitingPeriod(from, currencyKey) == 0, "Cannot settle during waiting period"); - (uint reclaimAmount, uint rebateAmount, uint entries, ExchangeEntrySettlement[] memory settlements) = + (uint reclaimAmount, uint rebateAmount, uint entries, IExchanger.ExchangeEntrySettlement[] memory settlements) = _settlementOwing(from, currencyKey); if (reclaimAmount > rebateAmount) { @@ -846,7 +824,6 @@ contract Exchanger is Owned, MixinSystemSettings, IExchanger, ReentrancyGuard { /// @notice Get dynamic fee for a given currency key (SIP-184) /// @param currencyKey The given currency key /// @return The dyanmic fee - /// @return The current round ID function _getDynamicFeeForExchange(bytes32 currencyKey) internal view returns (uint dynamicFee) { // No dynamic fee for sUSD if (currencyKey == sUSD) { diff --git a/contracts/interfaces/IExchanger.sol b/contracts/interfaces/IExchanger.sol index 1c5f6e31a8..2cd5323847 100644 --- a/contracts/interfaces/IExchanger.sol +++ b/contracts/interfaces/IExchanger.sol @@ -4,6 +4,27 @@ import "./IVirtualSynth.sol"; // https://docs.synthetix.io/contracts/source/interfaces/iexchanger interface IExchanger { + struct ExchangeEntrySettlement { + bytes32 src; + uint amount; + bytes32 dest; + uint reclaim; + uint rebate; + uint srcRoundIdAtPeriodEnd; + uint destRoundIdAtPeriodEnd; + uint timestamp; + } + + struct ExchangeEntry { + uint sourceRate; + uint destinationRate; + uint destinationAmount; + uint exchangeFeeRate; + uint exchangeDynamicFeeRate; + uint roundIdForSrc; + uint roundIdForDest; + } + // Views function calculateAmountAfterSettlement( address from, From a148f366fd67fefccc4768992c9c478d0d34b1c4 Mon Sep 17 00:00:00 2001 From: Lecky Date: Sun, 28 Nov 2021 20:22:33 +1100 Subject: [PATCH 055/133] Rollback DynamicFee formula to the previous iteration - removed lastDynamicFee cache as it doesn't help - Added ratesAndUpdatedTimeForCurrencyLastNRoundsAtRound - Adding more test cases --- contracts/DynamicFee.sol | 11 +-- contracts/ExchangeRates.sol | 27 ++++++ contracts/Exchanger.sol | 27 +----- contracts/interfaces/IExchangeRates.sol | 6 ++ contracts/test-helpers/TestableDynamicFee.sol | 4 +- test/contracts/TestableDynamicFee.js | 83 ++++++++++++++----- 6 files changed, 104 insertions(+), 54 deletions(-) diff --git a/contracts/DynamicFee.sol b/contracts/DynamicFee.sol index dad5a30452..0196b219cd 100644 --- a/contracts/DynamicFee.sol +++ b/contracts/DynamicFee.sol @@ -44,24 +44,21 @@ library DynamicFee { /// @param prices A list of prices from the current round to the previous rounds /// @param threshold A threshold to determine the price differential /// @param weightDecay A weight decay constant - /// @param lastExchangeDynamicFeeRate The last exchange dynamic fee rate /// @return uint dynamic fee function getDynamicFee( uint[] calldata prices, uint threshold, - uint weightDecay, - uint lastExchangeDynamicFeeRate + uint weightDecay ) external pure returns (uint dynamicFee) { uint size = prices.length; // disable dynamic fee when price feeds less than 2 rounds if (size < 2) { return dynamicFee; } - dynamicFee = lastExchangeDynamicFeeRate; - for (uint i = prices.length - 1; i > 0; i--) { + for (uint i = size - 1; i > 0; i--) { uint priceDifferential = getPriceDifferential(prices[i - 1], prices[i], threshold); - uint roundDecay = getRoundDecay(i, weightDecay); - dynamicFee = (dynamicFee.multiplyDecimal(roundDecay)).add(priceDifferential); + uint roundDecay = getRoundDecay(i - 1, weightDecay); + dynamicFee = dynamicFee.add(priceDifferential.multiplyDecimal(roundDecay)); } } } diff --git a/contracts/ExchangeRates.sol b/contracts/ExchangeRates.sol index eccaf33065..e4ee9519fe 100644 --- a/contracts/ExchangeRates.sol +++ b/contracts/ExchangeRates.sol @@ -302,6 +302,33 @@ contract ExchangeRates is Owned, MixinSystemSettings, IExchangeRates, Reentrancy return _getRateAndUpdatedTime(currencyKey).rate; } + /// @notice getting N rounds of rates for a currency at a specific round + /// @param currencyKey the currency key + /// @param numRounds the number of rounds to get + /// @param roundId the round id + /// @return a list of rates and a list of times + function ratesAndUpdatedTimeForCurrencyLastNRoundsAtRound( + bytes32 currencyKey, + uint numRounds, + uint roundId + ) external view returns (uint[] memory rates, uint[] memory times) { + rates = new uint[](numRounds); + times = new uint[](numRounds); + + for (uint i = 0; i < numRounds; i++) { + // fetch the rate and treat is as current, so inverse limits if frozen will always be applied + // regardless of current rate + (rates[i], times[i]) = _getRateAndUpdatedTimeAtRound(currencyKey, roundId); + + if (roundId == 0) { + // if we hit the last round, then return what we have + return (rates, times); + } else { + roundId--; + } + } + } + function ratesAndUpdatedTimeForCurrencyLastNRounds(bytes32 currencyKey, uint numRounds) external view diff --git a/contracts/Exchanger.sol b/contracts/Exchanger.sol index 9ae98c4f3a..8213dbd80b 100644 --- a/contracts/Exchanger.sol +++ b/contracts/Exchanger.sol @@ -86,16 +86,6 @@ contract Exchanger is Owned, MixinSystemSettings, IExchanger, ReentrancyGuard { /// @return the last exchange rate of the synth to sUSD mapping(bytes32 => uint) public lastExchangeRate; - /// @notice Return the last exchange dynamic fee rate - /// @param currencyKey is the currency key of the synth to be exchanged - /// @return the last exchange dynamic fee rate of the synth to sUSD - mapping(bytes32 => uint) public lastExchangeDynamicFeeRate; - - /// @notice Return the last exchange round ID - /// @param currencyKey is the currency key of the synth to be exchanged - /// @return the last exchange round ID - mapping(bytes32 => uint) public lastExchangeRoundId; - /* ========== ADDRESS RESOLVER CONFIGURATION ========== */ bytes32 private constant CONTRACT_SYSTEMSTATUS = "SystemStatus"; @@ -485,10 +475,6 @@ contract Exchanger is Owned, MixinSystemSettings, IExchanger, ReentrancyGuard { destinationCurrencyKey ); - // SIP-184: Store last Exchange round ID and dynamic fee after _feeRateForExchange calculations - lastExchangeRoundId[destinationCurrencyKey] = entry.roundIdForDest; - lastExchangeDynamicFeeRate[destinationCurrencyKey] = entry.exchangeDynamicFeeRate; - amountReceived = _deductFeesFromAmount(entry.destinationAmount, entry.exchangeFeeRate); // Note: `fee` is denominated in the destinationCurrencyKey. fee = entry.destinationAmount.sub(amountReceived); @@ -833,17 +819,10 @@ contract Exchanger is Owned, MixinSystemSettings, IExchanger, ReentrancyGuard { uint weightDecay = getExchangeDynamicFeeWeightDecay(); uint rounds = getExchangeDynamicFeeRounds(); uint[] memory prices; - // Note: We are using cache round ID here for fast read + // Note: We are using cache round ID here for cheap read uint currentRoundId = exchangeRates().currentRoundForRate(currencyKey); - uint remainingRounds = currentRoundId.sub(lastExchangeRoundId[currencyKey]); - if (remainingRounds >= rounds) { - (prices, ) = exchangeRates().ratesAndUpdatedTimeForCurrencyLastNRounds(currencyKey, rounds); - dynamicFee = DynamicFee.getDynamicFee(prices, threshold, weightDecay, 0); - } else { - // optimise calculation by using the last exchange dynamic fee rate - (prices, ) = exchangeRates().ratesAndUpdatedTimeForCurrencyLastNRounds(currencyKey, remainingRounds); - dynamicFee = DynamicFee.getDynamicFee(prices, threshold, weightDecay, lastExchangeDynamicFeeRate[currencyKey]); - } + (prices, ) = exchangeRates().ratesAndUpdatedTimeForCurrencyLastNRoundsAtRound(currencyKey, rounds, currentRoundId); + dynamicFee = DynamicFee.getDynamicFee(prices, threshold, weightDecay); } function getAmountsForExchange( diff --git a/contracts/interfaces/IExchangeRates.sol b/contracts/interfaces/IExchangeRates.sol index 3510370583..ca2729be25 100644 --- a/contracts/interfaces/IExchangeRates.sol +++ b/contracts/interfaces/IExchangeRates.sol @@ -108,6 +108,12 @@ interface IExchangeRates { view returns (uint[] memory rates, uint[] memory times); + function ratesAndUpdatedTimeForCurrencyLastNRoundsAtRound( + bytes32 currencyKey, + uint numRounds, + uint roundId + ) external view returns (uint[] memory rates, uint[] memory times); + function ratesAndInvalidForCurrencies(bytes32[] calldata currencyKeys) external view diff --git a/contracts/test-helpers/TestableDynamicFee.sol b/contracts/test-helpers/TestableDynamicFee.sol index cd00935d0f..d55b07ba10 100644 --- a/contracts/test-helpers/TestableDynamicFee.sol +++ b/contracts/test-helpers/TestableDynamicFee.sol @@ -15,7 +15,7 @@ contract TestableDynamicFee { return DynamicFee.getRoundDecay(round, weightDecay); } - function testGetDynamicFee(uint[] calldata prices, uint lastExchangeDynamicFeeRate) external view returns (uint) { - return DynamicFee.getDynamicFee(prices, threshold, weightDecay, lastExchangeDynamicFeeRate); + function testGetDynamicFee(uint[] calldata prices) external view returns (uint) { + return DynamicFee.getDynamicFee(prices, threshold, weightDecay); } } diff --git a/test/contracts/TestableDynamicFee.js b/test/contracts/TestableDynamicFee.js index 8755623754..819fc49d10 100644 --- a/test/contracts/TestableDynamicFee.js +++ b/test/contracts/TestableDynamicFee.js @@ -35,9 +35,8 @@ contract('TestableDynamicFee', () => { assert.bnEqual(priceWeight2, toUnit('0.81')); }); - it('Can get dynamic fee round 14-23', async () => { + it('Can get dynamic fee from dynamic-fee-calc.csv round 13-22, all below threshold', async () => { const prices = [ - toUnit('49234.65005734'), toUnit('49535.05178912'), toUnit('49714.05205647'), toUnit('49691.8024553899'), @@ -47,14 +46,16 @@ contract('TestableDynamicFee', () => { toUnit('49842.74988613'), toUnit('49933.34034209'), toUnit('49871.92313713'), + toUnit('49981'), + toUnit('49960.65493467'), + toUnit('49994'), ]; - const dynamicFee = await testableDynamicFee.testGetDynamicFee(prices, '0'); - assert.bnEqual(dynamicFee, '2064427530203592'); + const dynamicFee = await testableDynamicFee.testGetDynamicFee(prices); + assert.bnEqual(dynamicFee, '0'); }); - it('Can get dynamic fee round 15-24', async () => { + it('Can get dynamic fee from dynamic-fee-calc.csv round 14-23, last one above threshold', async () => { const prices = [ - toUnit('49190.99117585'), toUnit('49234.65005734'), toUnit('49535.05178912'), toUnit('49714.05205647'), @@ -64,25 +65,65 @@ contract('TestableDynamicFee', () => { toUnit('49838.87627216'), toUnit('49842.74988613'), toUnit('49933.34034209'), + toUnit('49871.92313713'), + toUnit('49981'), + ]; + const dynamicFee = await testableDynamicFee.testGetDynamicFee(prices); + assert.bnEqual(dynamicFee, '2064427530203592'); + }); + + it('Can get dynamic fee from dynamic-fee-calc.csv round 23-32, first one above threshold', async () => { + const prices = [ + toUnit('49198.77'), + toUnit('49143.5399999999'), + toUnit('49096.77'), + toUnit('49131.10261767'), + toUnit('49088.63670793'), + toUnit('49046.17079819'), + toUnit('49088.63670793'), + toUnit('49234.65005734'), + toUnit('49190.99117585'), + toUnit('49234.65005734'), + toUnit('49535.05178912'), + ]; + const dynamicFee = await testableDynamicFee.testGetDynamicFee(prices); + assert.bnEqual(dynamicFee, '799801523256537'); + }); + + it('Can get dynamic fee from dynamic-fee-calc.csv round 63-72, 70% above threshold', async () => { + const prices = [ + toUnit('44661.70868763'), + toUnit('44672.6561639399'), + toUnit('45483.8961602099'), + toUnit('45586.5085919099'), + toUnit('45919.00562933'), + toUnit('46183.17440371'), + toUnit('46217.7336139799'), + toUnit('46463.74676537'), + toUnit('46675.18493538'), + toUnit('46948.76815888'), + toUnit('47222.35138239'), + toUnit('47382.88726893'), ]; - const dynamicFee = await testableDynamicFee.testGetDynamicFee(prices, '0'); - assert.bnEqual(dynamicFee, '1857984777183232'); + const dynamicFee = await testableDynamicFee.testGetDynamicFee(prices); + assert.bnEqual(dynamicFee, '18366333809739328'); }); - it('Can get dynamic fee round 34-43', async () => { + it('Can get the same dynamic fee from dynamic-fee-calc.csv round 58-67, 50% above threshold', async () => { const prices = [ - toUnit('48364.4121895'), - toUnit('48954.93260767'), - toUnit('48964.89486113'), - toUnit('49054.03062906'), - toUnit('49009.46274509'), - toUnit('49054.03062906'), - toUnit('49093.89744338'), - toUnit('49095.24231598'), - toUnit('49101.41'), - toUnit('49208'), + toUnit('46183.17440371'), + toUnit('46217.7336139799'), + toUnit('46463.74676537'), + toUnit('46675.18493538'), + toUnit('46948.76815888'), + toUnit('47222.35138239'), + toUnit('47382.88726893'), + toUnit('47449.76309439'), + toUnit('47580.67384441'), + toUnit('47670.81054939'), + toUnit('47911.8471578599'), ]; - const dynamicFee = await testableDynamicFee.testGetDynamicFee(prices, '0'); - assert.bnEqual(dynamicFee, '8062531530836597'); + const dynamicFee = await testableDynamicFee.testGetDynamicFee(prices); + assert.bnEqual(dynamicFee, '4502723211780442'); }); }); From 026efc3d8d021d75bd10767c81ce5e47d737465b Mon Sep 17 00:00:00 2001 From: Lecky Date: Sun, 28 Nov 2021 21:08:37 +1100 Subject: [PATCH 056/133] Disable dynamic on itPricesSpikeDeviation settlement test --- test/contracts/Exchanger.spec.js | 2 ++ 1 file changed, 2 insertions(+) diff --git a/test/contracts/Exchanger.spec.js b/test/contracts/Exchanger.spec.js index f4127390d3..5f085fb292 100644 --- a/test/contracts/Exchanger.spec.js +++ b/test/contracts/Exchanger.spec.js @@ -3195,6 +3195,8 @@ contract('Exchanger (spec tests)', async accounts => { describe('settlement ignores deviations', () => { describe('when a user exchange 100 sUSD into sETH', () => { beforeEach(async () => { + // Disable Dynamic Fee by setting rounds to 0 + await systemSettings.setExchangeDynamicFeeRounds('0', { from: owner }); await synthetix.exchange(sUSD, toUnit('100'), sETH, { from: account1 }); }); describe('and the sETH rate moves up by a factor of 2 to 200', () => { From 0934b690e1b71c04b1af8235c6d66331eefb3cc9 Mon Sep 17 00:00:00 2001 From: Lecky Date: Sun, 28 Nov 2021 22:08:01 +1100 Subject: [PATCH 057/133] moving timestamp inside updateRates in test --- test/contracts/DebtCache.js | 4 ++-- test/contracts/Exchanger.spec.js | 2 +- test/contracts/FeePool.js | 5 +++-- test/contracts/Issuer.js | 4 ++-- test/contracts/RewardsIntegrationTests.js | 5 +++-- test/contracts/SynthUtil.js | 2 +- test/contracts/TradingRewards.spec.js | 5 +++-- test/contracts/helpers.js | 5 +++-- test/integration/utils/rates.js | 15 ++++++++++----- 9 files changed, 28 insertions(+), 19 deletions(-) diff --git a/test/contracts/DebtCache.js b/test/contracts/DebtCache.js index 1aee3f3ac8..9b149ddaac 100644 --- a/test/contracts/DebtCache.js +++ b/test/contracts/DebtCache.js @@ -284,9 +284,9 @@ contract('DebtCache', async accounts => { addSnapshotBeforeRestoreAfterEach(); beforeEach(async () => { - timestamp = await currentTime(); - for (let i = 0; i < DYNAMIC_FEE_ROUNDS; i++) { + timestamp = await currentTime(); + await exchangeRates.updateRates( [sAUD, sEUR, SNX, sETH, ETH, iETH], ['0.5', '1.25', '10', '200', '200', '200'].map(toUnit), diff --git a/test/contracts/Exchanger.spec.js b/test/contracts/Exchanger.spec.js index 5f085fb292..da05ad3957 100644 --- a/test/contracts/Exchanger.spec.js +++ b/test/contracts/Exchanger.spec.js @@ -3691,8 +3691,8 @@ contract('Exchanger (spec tests)', async accounts => { addSnapshotBeforeRestoreAfterEach(); beforeEach(async () => { - timestamp = await currentTime(); for (let i = 0; i < DYNAMIC_FEE_ROUNDS; i++) { + timestamp = await currentTime(); await exchangeRates.updateRates( [sAUD, sEUR, SNX, sETH, sBTC, iBTC], ['0.5', '2', '1', '100', '5000', '5000'].map(toUnit), diff --git a/test/contracts/FeePool.js b/test/contracts/FeePool.js index 8f0da2a2b5..0f183430e5 100644 --- a/test/contracts/FeePool.js +++ b/test/contracts/FeePool.js @@ -38,9 +38,10 @@ contract('FeePool', async accounts => { // Updates rates with defaults so they're not stale. const updateRatesWithDefaults = async () => { - const timestamp = await currentTime(); - + let timestamp; for (let i = 0; i < DYNAMIC_FEE_ROUNDS; i++) { + timestamp = await currentTime(); + await exchangeRates.updateRates([sAUD, SNX], ['0.5', '0.1'].map(toUnit), timestamp, { from: oracle, }); diff --git a/test/contracts/Issuer.js b/test/contracts/Issuer.js index b25ac85813..28f7c0f94e 100644 --- a/test/contracts/Issuer.js +++ b/test/contracts/Issuer.js @@ -116,9 +116,9 @@ contract('Issuer (via Synthetix)', async accounts => { addSnapshotBeforeRestoreAfterEach(); beforeEach(async () => { - timestamp = await currentTime(); - for (let i = 0; i < DYNAMIC_FEE_ROUNDS; i++) { + timestamp = await currentTime(); + await exchangeRates.updateRates( [sAUD, sEUR, SNX, sETH], ['0.5', '1.25', '0.1', '200'].map(toUnit), diff --git a/test/contracts/RewardsIntegrationTests.js b/test/contracts/RewardsIntegrationTests.js index 7f85ed110c..1e6ea0c95d 100644 --- a/test/contracts/RewardsIntegrationTests.js +++ b/test/contracts/RewardsIntegrationTests.js @@ -65,9 +65,10 @@ contract('Rewards Integration Tests', accounts => { // Updates rates with defaults so they're not stale. const updateRatesWithDefaults = async () => { - const timestamp = await currentTime(); - + let timestamp; for (let i = 0; i < DYNAMIC_FEE_ROUNDS; i++) { + timestamp = await currentTime(); + await exchangeRates.updateRates( [sAUD, sEUR, SNX, sBTC, iBTC, sETH, ETH], ['0.5', '1.25', '0.1', '5000', '4000', '172', '172'].map(toUnit), diff --git a/test/contracts/SynthUtil.js b/test/contracts/SynthUtil.js index 79d49e1dd4..6f97aebc0f 100644 --- a/test/contracts/SynthUtil.js +++ b/test/contracts/SynthUtil.js @@ -50,8 +50,8 @@ contract('SynthUtil', accounts => { addSnapshotBeforeRestoreAfterEach(); beforeEach(async () => { - timestamp = await currentTime(); for (let i = 0; i < DYNAMIC_FEE_ROUNDS; i++) { + timestamp = await currentTime(); await exchangeRates.updateRates([sBTC, iBTC], ['5000', '5000'].map(toUnit), timestamp, { from: oracle, }); diff --git a/test/contracts/TradingRewards.spec.js b/test/contracts/TradingRewards.spec.js index 98f46040ca..1a534b7426 100644 --- a/test/contracts/TradingRewards.spec.js +++ b/test/contracts/TradingRewards.spec.js @@ -104,9 +104,10 @@ contract('TradingRewards', accounts => { before('set exchange rates', async () => { const oracle = account1; - const timestamp = await currentTime(); - + let timestamp; for (let i = 0; i < DYNAMIC_FEE_ROUNDS; i++) { + timestamp = await currentTime(); + await exchangeRates.updateRates([sETH, sBTC], Object.values(rates), timestamp, { from: oracle, }); diff --git a/test/contracts/helpers.js b/test/contracts/helpers.js index fd8ae10889..38410c73e5 100644 --- a/test/contracts/helpers.js +++ b/test/contracts/helpers.js @@ -89,8 +89,6 @@ module.exports = { }, async updateRatesWithDefaults({ exchangeRates, oracle, debtCache }) { - const timestamp = await currentTime(); - const [SNX, sAUD, sEUR, sBTC, iBTC, sETH, ETH] = [ 'SNX', 'sAUD', @@ -101,7 +99,10 @@ module.exports = { 'ETH', ].map(toBytes32); + let timestamp; for (let i = 0; i < DYNAMIC_FEE_ROUNDS; i++) { + timestamp = await currentTime(); + await exchangeRates.updateRates( [SNX, sAUD, sEUR, sBTC, iBTC, sETH, ETH], ['0.1', '0.5', '1.25', '5000', '4000', '172', '172'].map(toUnit), diff --git a/test/integration/utils/rates.js b/test/integration/utils/rates.js index a620cebcc0..d6922706d7 100644 --- a/test/integration/utils/rates.js +++ b/test/integration/utils/rates.js @@ -1,6 +1,9 @@ const ethers = require('ethers'); const { setSystemSetting } = require('./settings'); -const { toBytes32 } = require('../../..'); +const { + toBytes32, + defaults: { DYNAMIC_FEE_ROUNDS }, +} = require('../../..'); async function updateExchangeRatesIfNeeded({ ctx }) { await setSystemSetting({ ctx, settingName: 'rateStalePeriod', newValue: '1000000000' }); @@ -94,12 +97,14 @@ async function _setNewRates({ ctx }) { ExchangeRates = ExchangeRates.connect(signer); const currencyKeys = await _getAvailableCurrencyKeys({ ctx }); - const { timestamp } = await ctx.provider.getBlock(); - const rates = await _getCurrentRates({ ctx, currencyKeys }); - const tx = await ExchangeRates.updateRates(currencyKeys, rates, timestamp); - await tx.wait(); + for (let i = 0; i < DYNAMIC_FEE_ROUNDS; i++) { + const { timestamp } = await ctx.provider.getBlock(); + + const tx = await ExchangeRates.updateRates(currencyKeys, rates, timestamp); + await tx.wait(); + } } async function _updateCache({ ctx }) { From a53300e776b5e3d45c627f9eb5a671fe85dc085e Mon Sep 17 00:00:00 2001 From: Lecky Date: Mon, 29 Nov 2021 15:16:17 +1100 Subject: [PATCH 058/133] Adding sourceCurrencyKey into total dynamic fee --- contracts/Exchanger.sol | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/contracts/Exchanger.sol b/contracts/Exchanger.sol index 8213dbd80b..246150b52c 100644 --- a/contracts/Exchanger.sol +++ b/contracts/Exchanger.sol @@ -781,6 +781,7 @@ contract Exchanger is Owned, MixinSystemSettings, IExchanger, ReentrancyGuard { ) internal view returns (uint, uint exchangeDynamicFeeRate) { // Get the exchange dynamic fee rate as per destination currencyKey exchangeDynamicFeeRate = _getDynamicFeeForExchange(destinationCurrencyKey); + exchangeDynamicFeeRate = exchangeDynamicFeeRate.add(_getDynamicFeeForExchange(sourceCurrencyKey)); if (sourceCurrencyKey == sUSD || destinationCurrencyKey == sUSD) { exchangeFeeRate = exchangeFeeRate.add(exchangeDynamicFeeRate); @@ -796,9 +797,6 @@ contract Exchanger is Owned, MixinSystemSettings, IExchanger, ReentrancyGuard { ) { // Double the exchange fee exchangeFeeRate = exchangeFeeRate.mul(2); - - // Double the exchange dynamic fee - exchangeDynamicFeeRate = exchangeDynamicFeeRate.mul(2); } exchangeFeeRate = exchangeFeeRate.add(exchangeDynamicFeeRate); From d98b3911ae18d2fc4d701c25e4860fd7f7bf592d Mon Sep 17 00:00:00 2001 From: Lecky Date: Mon, 29 Nov 2021 15:56:01 +1100 Subject: [PATCH 059/133] Rollback _getRateAndUpdatedTime as cache not help --- contracts/ExchangeRates.sol | 14 +++----------- 1 file changed, 3 insertions(+), 11 deletions(-) diff --git a/contracts/ExchangeRates.sol b/contracts/ExchangeRates.sol index e4ee9519fe..f73136b5bc 100644 --- a/contracts/ExchangeRates.sol +++ b/contracts/ExchangeRates.sol @@ -534,17 +534,6 @@ contract ExchangeRates is Owned, MixinSystemSettings, IExchangeRates, Reentrancy function _getRateAndUpdatedTime(bytes32 currencyKey) internal view returns (RateAndUpdatedTime memory entry) { AggregatorV2V3Interface aggregator = aggregators[currencyKey]; - uint roundId = currentRoundForRate[currencyKey]; - uint currentRoundId = _getCurrentRoundId(currencyKey); - // Load the latest round ID - roundId = currentRoundId > roundId ? currentRoundId : roundId; - - entry = _rates[currencyKey][roundId]; - - // Try to get rate from cache if possible - if (entry.rate > 0) { - return entry; - } if (aggregator != AggregatorV2V3Interface(0)) { // this view from the aggregator is the most gas efficient but it can throw when there's no data, @@ -559,6 +548,9 @@ contract ExchangeRates is Owned, MixinSystemSettings, IExchangeRates, Reentrancy entry.rate = uint216(_formatAggregatorAnswer(currencyKey, answer)); entry.time = uint40(updatedAt); } + } else { + uint roundId = currentRoundForRate[currencyKey]; + entry = _rates[currencyKey][roundId]; } } From dac4d0ab495fd5bb727b4dbae8baa7ab0d1d3042 Mon Sep 17 00:00:00 2001 From: Lecky Date: Tue, 30 Nov 2021 11:39:10 +1100 Subject: [PATCH 060/133] fixing CollateralShort test - adding more rounds on rates --- test/contracts/CollateralShort.js | 26 +++++++++++++++++--------- 1 file changed, 17 insertions(+), 9 deletions(-) diff --git a/test/contracts/CollateralShort.js b/test/contracts/CollateralShort.js index 9b1d5ea1d4..56fbbb094d 100644 --- a/test/contracts/CollateralShort.js +++ b/test/contracts/CollateralShort.js @@ -10,7 +10,10 @@ const { setupAllContracts } = require('./setup'); const { ensureOnlyExpectedMutativeFunctions, setExchangeFeeRateForSynths } = require('./helpers'); -const { toBytes32 } = require('../..'); +const { + toBytes32, + defaults: { DYNAMIC_FEE_ROUNDS }, +} = require('../..'); contract('CollateralShort', async accounts => { const YEAR = 31556926; @@ -54,17 +57,22 @@ contract('CollateralShort', async accounts => { }; const updateRatesWithDefaults = async () => { - const timestamp = await currentTime(); - - await exchangeRates.updateRates([sETH], ['100'].map(toUnit), timestamp, { - from: oracle, - }); + let timestamp; + for (let i = 0; i < DYNAMIC_FEE_ROUNDS; i++) { + timestamp = await currentTime(); + await exchangeRates.updateRates([sETH], ['100'].map(toUnit), timestamp, { + from: oracle, + }); + } const sBTC = toBytes32('sBTC'); - await exchangeRates.updateRates([sBTC], ['10000'].map(toUnit), timestamp, { - from: oracle, - }); + for (let i = 0; i < DYNAMIC_FEE_ROUNDS; i++) { + timestamp = await currentTime(); + await exchangeRates.updateRates([sBTC], ['10000'].map(toUnit), timestamp, { + from: oracle, + }); + } }; const setupShort = async () => { From 9b9cc5a29c2d41033d8f8ed6cfebcdfbaef95f52 Mon Sep 17 00:00:00 2001 From: Lecky Date: Tue, 30 Nov 2021 14:33:19 +1100 Subject: [PATCH 061/133] fixed PurgableSynth test - Adding more rounds for rates --- test/contracts/PurgeableSynth.js | 27 ++++++++++++++++----------- 1 file changed, 16 insertions(+), 11 deletions(-) diff --git a/test/contracts/PurgeableSynth.js b/test/contracts/PurgeableSynth.js index 812c1fcf0c..ba0ce15173 100644 --- a/test/contracts/PurgeableSynth.js +++ b/test/contracts/PurgeableSynth.js @@ -11,6 +11,7 @@ const PurgeableSynth = artifacts.require('PurgeableSynth'); const { currentTime, fastForward, toUnit } = require('../utils')(); const { toBytes32, + defaults: { DYNAMIC_FEE_ROUNDS }, constants: { ZERO_ADDRESS }, } = require('../..'); @@ -155,14 +156,16 @@ contract('PurgeableSynth', accounts => { describe("when there's a price for the purgeable synth", () => { beforeEach(async () => { - await exchangeRates.updateRates( - [sAUD, SNX, iETH], - ['0.5', '1', '170'].map(toUnit), - timestamp, - { - from: oracle, - } - ); + for (let i = 0; i < DYNAMIC_FEE_ROUNDS; i++) { + await exchangeRates.updateRates( + [sAUD, SNX, iETH], + ['0.5', '1', '170'].map(toUnit), + timestamp, + { + from: oracle, + } + ); + } await debtCache.takeDebtSnapshot(); }); @@ -322,9 +325,11 @@ contract('PurgeableSynth', accounts => { describe('Replacing an existing Synth with a Purgeable one to purge and remove it', () => { describe('when sAUD has a price', () => { beforeEach(async () => { - await exchangeRates.updateRates([sAUD], ['0.776845993'].map(toUnit), timestamp, { - from: oracle, - }); + for (let i = 0; i < DYNAMIC_FEE_ROUNDS; i++) { + await exchangeRates.updateRates([sAUD], ['0.776845993'].map(toUnit), timestamp, { + from: oracle, + }); + } await debtCache.takeDebtSnapshot(); }); describe('when a user holds some sAUD', () => { From 0b1d6120af0ee896c2341465b849ef06c6a6d0e7 Mon Sep 17 00:00:00 2001 From: Lecky Date: Tue, 30 Nov 2021 15:04:50 +1100 Subject: [PATCH 062/133] Fixed ExchangeRates.js test - Rollback as no cache for _getRateAndUpdatedTime --- test/contracts/ExchangeRates.js | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/test/contracts/ExchangeRates.js b/test/contracts/ExchangeRates.js index 43c2eacc73..8cbf60f94a 100644 --- a/test/contracts/ExchangeRates.js +++ b/test/contracts/ExchangeRates.js @@ -1573,19 +1573,19 @@ contract('Exchange Rates', async accounts => { }); }); describe('when the price is fetched for sJPY', () => { - it('old rate is returned as reading from cache', async () => { + it('0 is returned', async () => { const result = await instance.rateForCurrency(sJPY, { from: accountOne, }); - assert.bnEqual(result, oldPrice); + assert.equal(result.toNumber(), 0); }); }); describe('when the timestamp is fetched for sJPY', () => { - it('old time is returned as reading from cache', async () => { + it('0 is returned', async () => { const result = await instance.lastRateUpdateTimes(sJPY, { from: accountOne, }); - assert.equal(result.toNumber(), timeOldSent); + assert.equal(result.toNumber(), 0); }); }); describe('when the ratesAndInvalidForCurrencies is queried with sJPY', () => { @@ -1594,12 +1594,12 @@ contract('Exchange Rates', async accounts => { response = await instance.ratesAndInvalidForCurrencies([sJPY]); }); - it('then the rates are valid as reading from cache', () => { - assert.equal(response[1], false); + it('then the rates are invalid', () => { + assert.equal(response[1], true); }); - it('with cache value', () => { - assert.bnEqual(response[0][0], oldPrice); + it('with no value', () => { + assert.bnEqual(response[0][0], '0'); }); }); describe('when the rateAndInvalid is queried with sJPY', () => { @@ -1608,12 +1608,12 @@ contract('Exchange Rates', async accounts => { response = await instance.rateAndInvalid(sJPY); }); - it('then the rate is valid', () => { - assert.equal(response[1], false); + it('then the rate is invalid', () => { + assert.equal(response[1], true); }); - it('with cache value', () => { - assert.bnEqual(response[0], oldPrice); + it('with no value', () => { + assert.bnEqual(response[0], '0'); }); }); From 3831c9bec6429e1efa575c6b36038291a6ee253f Mon Sep 17 00:00:00 2001 From: Lecky Date: Tue, 30 Nov 2021 16:10:02 +1100 Subject: [PATCH 063/133] Fixing Exchanger.spec.js test - Disable Dynamic fee on test with base exchange fee rate --- test/contracts/Exchanger.spec.js | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/test/contracts/Exchanger.spec.js b/test/contracts/Exchanger.spec.js index da05ad3957..b8a96d2d60 100644 --- a/test/contracts/Exchanger.spec.js +++ b/test/contracts/Exchanger.spec.js @@ -629,7 +629,7 @@ contract('Exchanger (spec tests)', async accounts => { describe(`when ${section} is suspended`, () => { beforeEach(async () => { await setStatus({ owner, systemStatus, section, suspend: true, synth }); - // Disable Dynamic Fee by setting rounds to 0 + // Disable Dynamic Fee here as settlement is L1 and Dynamic fee is on L2 await systemSettings.setExchangeDynamicFeeRounds('0', { from: owner }); }); it('then calling settle() reverts', async () => { @@ -3485,6 +3485,9 @@ contract('Exchanger (spec tests)', async accounts => { const newCryptoBIPS = toUnit('0.04'); beforeEach(async () => { + // Disable Dynamic Fee here as it's testing for the base exchange fee rate + await systemSettings.setExchangeDynamicFeeRounds('0', { from: owner }); + // Store multiple rates await systemSettings.setExchangeFeeRateForSynths( [sUSD, sAUD, sBTC, sETH], From dc1d46fc409c8c652aab6d69a8b0df50376a3dc3 Mon Sep 17 00:00:00 2001 From: Lecky Date: Tue, 30 Nov 2021 17:04:57 +1100 Subject: [PATCH 064/133] Fixes Synth.js test; - Adding more rounds on rates; --- test/contracts/Synth.js | 23 +++++++++++++++-------- 1 file changed, 15 insertions(+), 8 deletions(-) diff --git a/test/contracts/Synth.js b/test/contracts/Synth.js index a0251a261e..4c9cf03506 100644 --- a/test/contracts/Synth.js +++ b/test/contracts/Synth.js @@ -20,6 +20,7 @@ const { const { toBytes32, constants: { ZERO_ADDRESS }, + defaults: { DYNAMIC_FEE_ROUNDS }, } = require('../..'); contract('Synth', async accounts => { @@ -77,12 +78,15 @@ contract('Synth', async accounts => { addSnapshotBeforeRestoreAfterEach(); beforeEach(async () => { - const timestamp = await currentTime(); + let timestamp; // Send a price update to guarantee we're not stale. - await exchangeRates.updateRates([SNX], ['0.1'].map(toUnit), timestamp, { - from: oracle, - }); + for (let i = 0; i < DYNAMIC_FEE_ROUNDS; i++) { + timestamp = await currentTime(); + await exchangeRates.updateRates([SNX], ['0.1'].map(toUnit), timestamp, { + from: oracle, + }); + } await debtCache.takeDebtSnapshot(); // set default issuanceRatio to 0.2 @@ -733,12 +737,15 @@ contract('Synth', async accounts => { contracts: [{ contract: 'Synth', properties: { currencyKey: sEUR } }], })); - const timestamp = await currentTime(); + let timestamp; // Send a price update to guarantee we're not stale. - await exchangeRates.updateRates([sEUR], ['1'].map(toUnit), timestamp, { - from: oracle, - }); + for (let i = 0; i < DYNAMIC_FEE_ROUNDS; i++) { + timestamp = await currentTime(); + await exchangeRates.updateRates([sEUR], ['1'].map(toUnit), timestamp, { + from: oracle, + }); + } await debtCache.takeDebtSnapshot(); }); From cd70265b43cab85116849be3aeb7842d225a324f Mon Sep 17 00:00:00 2001 From: Lecky Date: Tue, 30 Nov 2021 20:38:11 +1100 Subject: [PATCH 065/133] Fix(DebtCache test): - Disable Dynamic fee in order for debt calculation --- test/contracts/DebtCache.js | 3 +++ 1 file changed, 3 insertions(+) diff --git a/test/contracts/DebtCache.js b/test/contracts/DebtCache.js index 9b149ddaac..539d2e78e8 100644 --- a/test/contracts/DebtCache.js +++ b/test/contracts/DebtCache.js @@ -968,6 +968,9 @@ contract('DebtCache', async accounts => { }); it('exchanging between synths updates sUSD debt total due to fees', async () => { + // Disable Dynamic fee so that we can neglect it. + await systemSettings.setExchangeDynamicFeeRounds('0', { from: owner }); + await systemSettings.setExchangeFeeRateForSynths( [sAUD, sUSD, sEUR], [toUnit(0.1), toUnit(0.1), toUnit(0.1)], From 7dc75c1b639ee236386af5e868043c1cda01039e Mon Sep 17 00:00:00 2001 From: Lecky Date: Tue, 30 Nov 2021 21:03:55 +1100 Subject: [PATCH 066/133] Fix(job-fork-tests): - Adding generateSolidity into configure-system-settings --- publish/src/commands/deploy/configure-system-settings.js | 1 + 1 file changed, 1 insertion(+) diff --git a/publish/src/commands/deploy/configure-system-settings.js b/publish/src/commands/deploy/configure-system-settings.js index 212ec7bced..ffa05d00ae 100644 --- a/publish/src/commands/deploy/configure-system-settings.js +++ b/publish/src/commands/deploy/configure-system-settings.js @@ -14,6 +14,7 @@ module.exports = async ({ deployer, methodCallGasLimit, useOvm, + generateSolidity, getDeployParameter, network, runStep, From 178be91d525a16f3d23d8300d16a6f77d1c45359 Mon Sep 17 00:00:00 2001 From: Lecky Date: Tue, 30 Nov 2021 23:44:41 +1100 Subject: [PATCH 067/133] Fix(audit): - Adding MAX_DYNAMIC_FEE default to 1 - remove comment with system value - remove nonreentrant - removed swing trade double fee - created getExchangeDynamicFeeData for combine external call - _setRate skip writing if same rate --- contracts/DynamicFee.sol | 2 +- contracts/ExchangeRates.sol | 7 ++-- contracts/Exchanger.sol | 27 +++++--------- contracts/MixinSystemSettings.sol | 35 +++++++++++++++---- contracts/SystemSettings.sol | 29 +++++++++++++-- index.js | 7 ++-- .../deploy/configure-system-settings.js | 31 +++++++++++----- test/contracts/SystemSettings.js | 19 ++++++++++ 8 files changed, 113 insertions(+), 44 deletions(-) diff --git a/contracts/DynamicFee.sol b/contracts/DynamicFee.sol index 0196b219cd..f63b9afbaa 100644 --- a/contracts/DynamicFee.sol +++ b/contracts/DynamicFee.sol @@ -40,7 +40,7 @@ library DynamicFee { return weightDecay.powDecimal(round); } - /// @notice Calculate dynamic fee based on preceding 10 price differential + /// @notice Calculate dynamic fee /// @param prices A list of prices from the current round to the previous rounds /// @param threshold A threshold to determine the price differential /// @param weightDecay A weight decay constant diff --git a/contracts/ExchangeRates.sol b/contracts/ExchangeRates.sol index f73136b5bc..7f0d85df17 100644 --- a/contracts/ExchangeRates.sol +++ b/contracts/ExchangeRates.sol @@ -8,7 +8,6 @@ import "./interfaces/IExchangeRates.sol"; // Libraries import "./SafeDecimalMath.sol"; -import "openzeppelin-solidity-2.3.0/contracts/utils/ReentrancyGuard.sol"; // Internal references // AggregatorInterface from Chainlink represents a decentralized pricing network for a single currency key @@ -18,7 +17,7 @@ import "@chainlink/contracts-0.0.10/src/v0.5/interfaces/FlagsInterface.sol"; import "./interfaces/IExchanger.sol"; // https://docs.synthetix.io/contracts/source/contracts/exchangerates -contract ExchangeRates is Owned, MixinSystemSettings, IExchangeRates, ReentrancyGuard { +contract ExchangeRates is Owned, MixinSystemSettings, IExchangeRates { using SafeMath for uint; using SafeDecimalMath for uint; @@ -141,7 +140,6 @@ contract ExchangeRates is Owned, MixinSystemSettings, IExchangeRates, Reentrancy uint roundIdForDest ) external - nonReentrant returns ( uint value, uint sourceRate, @@ -467,6 +465,9 @@ contract ExchangeRates is Owned, MixinSystemSettings, IExchangeRates, Reentrancy currentRoundForRate[currencyKey]++; } + // skip writing if the rate is the same + if (rate == _rates[currencyKey][currentRoundForRate[currencyKey]].rate) return; + _rates[currencyKey][currentRoundForRate[currencyKey]] = RateAndUpdatedTime({ rate: uint216(rate), time: uint40(time) diff --git a/contracts/Exchanger.sol b/contracts/Exchanger.sol index 246150b52c..fbd6401488 100644 --- a/contracts/Exchanger.sol +++ b/contracts/Exchanger.sol @@ -8,7 +8,6 @@ import "./interfaces/IExchanger.sol"; // Libraries import "./DynamicFee.sol"; -import "openzeppelin-solidity-2.3.0/contracts/utils/ReentrancyGuard.sol"; // Internal references import "./interfaces/ISystemStatus.sol"; @@ -70,7 +69,7 @@ interface IExchangerInternalDebtCache { } // https://docs.synthetix.io/contracts/source/contracts/exchanger -contract Exchanger is Owned, MixinSystemSettings, IExchanger, ReentrancyGuard { +contract Exchanger is Owned, MixinSystemSettings, IExchanger { using SafeMath for uint; using SafeDecimalMath for uint; @@ -326,7 +325,7 @@ contract Exchanger is Owned, MixinSystemSettings, IExchanger, ReentrancyGuard { bool virtualSynth, address rewardAddress, bytes32 trackingCode - ) external onlySynthetixorSynth nonReentrant returns (uint amountReceived, IVirtualSynth vSynth) { + ) external onlySynthetixorSynth returns (uint amountReceived, IVirtualSynth vSynth) { uint fee; if (from != exchangeForAddress) { require(delegateApprovals().canExchangeFor(exchangeForAddress, from), "Not approved to act on behalf"); @@ -782,26 +781,18 @@ contract Exchanger is Owned, MixinSystemSettings, IExchanger, ReentrancyGuard { // Get the exchange dynamic fee rate as per destination currencyKey exchangeDynamicFeeRate = _getDynamicFeeForExchange(destinationCurrencyKey); exchangeDynamicFeeRate = exchangeDynamicFeeRate.add(_getDynamicFeeForExchange(sourceCurrencyKey)); + uint maxDynamicFee = getExchangeMaxDynamicFee(); if (sourceCurrencyKey == sUSD || destinationCurrencyKey == sUSD) { exchangeFeeRate = exchangeFeeRate.add(exchangeDynamicFeeRate); - // Cap max exchangeFeeRate to 100% - exchangeFeeRate = exchangeFeeRate > SafeDecimalMath.unit() ? SafeDecimalMath.unit() : exchangeFeeRate; + // Cap to max exchange dynamic fee + exchangeFeeRate = exchangeFeeRate > maxDynamicFee ? maxDynamicFee : exchangeFeeRate; return (exchangeFeeRate, exchangeDynamicFeeRate); } - // Is this a swing trade? long to short or short to long skipping sUSD. - if ( - (sourceCurrencyKey[0] == 0x73 && destinationCurrencyKey[0] == 0x69) || - (sourceCurrencyKey[0] == 0x69 && destinationCurrencyKey[0] == 0x73) - ) { - // Double the exchange fee - exchangeFeeRate = exchangeFeeRate.mul(2); - } - exchangeFeeRate = exchangeFeeRate.add(exchangeDynamicFeeRate); - // Cap max exchangeFeeRate to 100% - exchangeFeeRate = exchangeFeeRate > SafeDecimalMath.unit() ? SafeDecimalMath.unit() : exchangeFeeRate; + // Cap to max exchange dynamic fee + exchangeFeeRate = exchangeFeeRate > maxDynamicFee ? maxDynamicFee : exchangeFeeRate; return (exchangeFeeRate, exchangeDynamicFeeRate); } @@ -813,9 +804,7 @@ contract Exchanger is Owned, MixinSystemSettings, IExchanger, ReentrancyGuard { if (currencyKey == sUSD) { return 0; } - uint threshold = getExchangeDynamicFeeThreshold(); - uint weightDecay = getExchangeDynamicFeeWeightDecay(); - uint rounds = getExchangeDynamicFeeRounds(); + (uint threshold, uint weightDecay, uint rounds) = getExchangeDynamicFeeData(); uint[] memory prices; // Note: We are using cache round ID here for cheap read uint currentRoundId = exchangeRates().currentRoundForRate(currencyKey); diff --git a/contracts/MixinSystemSettings.sol b/contracts/MixinSystemSettings.sol index 5ac91ca7b1..3e891e4a57 100644 --- a/contracts/MixinSystemSettings.sol +++ b/contracts/MixinSystemSettings.sol @@ -20,10 +20,11 @@ contract MixinSystemSettings is MixinResolver { bytes32 internal constant SETTING_RATE_STALE_PERIOD = "rateStalePeriod"; /* ========== Exchange Fees Related ========== */ bytes32 internal constant SETTING_EXCHANGE_FEE_RATE = "exchangeFeeRate"; - bytes32 internal constant SETTING_DYNAMIC_FEE_THRESHOLD = "dynamicFeeThreshold"; - bytes32 internal constant SETTING_DYNAMIC_FEE_WEIGHT_DECAY = "dynamicFeeWeightDecay"; + bytes32 internal constant SETTING_EXCHANGE_DYNAMIC_FEE_THRESHOLD = "exchangeDynamicFeeThreshold"; + bytes32 internal constant SETTING_EXCHANGE_DYNAMIC_FEE_WEIGHT_DECAY = "exchangeDynamicFeeWeightDecay"; + bytes32 internal constant SETTING_EXCHANGE_DYNAMIC_FEE_ROUNDS = "exchangeDynamicFeeRounds"; + bytes32 internal constant SETTING_EXCHANGE_MAX_DYNAMIC_FEE = "exchangeMaxDynamicFee"; /* ========== End Exchange Fees Related ========== */ - bytes32 internal constant SETTING_DYNAMIC_FEE_ROUNDS = "dynamicFeeRounds"; bytes32 internal constant SETTING_MINIMUM_STAKE_TIME = "minimumStakeTime"; bytes32 internal constant SETTING_AGGREGATOR_WARNING_FLAGS = "aggregatorWarningFlags"; bytes32 internal constant SETTING_TRADING_REWARDS_ENABLED = "tradingRewardsEnabled"; @@ -141,19 +142,41 @@ contract MixinSystemSettings is MixinResolver { /// @notice Get exchange dynamic fee threshold constant default 40bps /// @return uint threshold constant function getExchangeDynamicFeeThreshold() internal view returns (uint) { - return flexibleStorage().getUIntValue(SETTING_CONTRACT_NAME, SETTING_DYNAMIC_FEE_THRESHOLD); + return flexibleStorage().getUIntValue(SETTING_CONTRACT_NAME, SETTING_EXCHANGE_DYNAMIC_FEE_THRESHOLD); } /// @notice Get exchange dynamic fee weight decay constant default 0.9 /// @return uint weight decay constant function getExchangeDynamicFeeWeightDecay() internal view returns (uint) { - return flexibleStorage().getUIntValue(SETTING_CONTRACT_NAME, SETTING_DYNAMIC_FEE_WEIGHT_DECAY); + return flexibleStorage().getUIntValue(SETTING_CONTRACT_NAME, SETTING_EXCHANGE_DYNAMIC_FEE_WEIGHT_DECAY); } /// @notice Get exchange dynamic fee rounds default to 10 rounds /// @return uint last N round function getExchangeDynamicFeeRounds() internal view returns (uint) { - return flexibleStorage().getUIntValue(SETTING_CONTRACT_NAME, SETTING_DYNAMIC_FEE_ROUNDS); + return flexibleStorage().getUIntValue(SETTING_CONTRACT_NAME, SETTING_EXCHANGE_DYNAMIC_FEE_ROUNDS); + } + + /// @notice Get exchange dynamic fee related data + /// @return threshold, weight decay, and rounds + function getExchangeDynamicFeeData() + internal + view + returns ( + uint threshold, + uint weightDecay, + uint rounds + ) + { + threshold = getExchangeDynamicFeeThreshold(); + weightDecay = getExchangeDynamicFeeWeightDecay(); + rounds = getExchangeDynamicFeeRounds(); + } + + /// @notice Get exchange max dynamic fee + /// @return max dynamic fee + function getExchangeMaxDynamicFee() internal view returns (uint) { + return flexibleStorage().getUIntValue(SETTING_CONTRACT_NAME, SETTING_EXCHANGE_MAX_DYNAMIC_FEE); } /* ========== End Exchange Related Fees ========== */ diff --git a/contracts/SystemSettings.sol b/contracts/SystemSettings.sol index ff772514ab..5d94ff882c 100644 --- a/contracts/SystemSettings.sol +++ b/contracts/SystemSettings.sol @@ -125,20 +125,29 @@ contract SystemSettings is Owned, MixinSystemSettings, ISystemSettings { // SIP-184 Dynamic Fee /// @notice Get the dynamic fee threshold + /// @return The dynamic fee threshold function exchangeDynamicFeeThreshold() external view returns (uint) { return getExchangeDynamicFeeThreshold(); } /// @notice Get the dynamic fee weight decay per round + /// @return The dynamic fee weight decay per round function exchangeDynamicFeeWeightDecay() external view returns (uint) { return getExchangeDynamicFeeWeightDecay(); } /// @notice Get the dynamic fee total rounds for calculation + /// @return The dynamic fee total rounds for calculation function exchangeDynamicFeeRounds() external view returns (uint) { return getExchangeDynamicFeeRounds(); } + /// @notice Get the max dynamic fee + /// @return The max dynamic fee + function exchangeMaxDynamicFee() external view returns (uint) { + return getExchangeMaxDynamicFee(); + } + /* ========== End Exchange Related Fees ========== */ function minimumStakeTime() external view returns (uint) { @@ -379,36 +388,48 @@ contract SystemSettings is Owned, MixinSystemSettings, ISystemSettings { } /// @notice Set exchange dynamic fee threshold constant default 40bps + /// @param threshold The exchange dynamic fee threshold /// @return uint threshold constant function setExchangeDynamicFeeThreshold(uint threshold) external onlyOwner { require(threshold != 0, "Threshold cannot be 0"); - flexibleStorage().setUIntValue(SETTING_CONTRACT_NAME, SETTING_DYNAMIC_FEE_THRESHOLD, threshold); + flexibleStorage().setUIntValue(SETTING_CONTRACT_NAME, SETTING_EXCHANGE_DYNAMIC_FEE_THRESHOLD, threshold); emit ExchangeDynamicFeeThresholdUpdated(threshold); } /// @notice Set exchange dynamic fee weight decay constant default 0.9 + /// @param weightDecay The exchange dynamic fee weight decay /// @return uint weight decay constant function setExchangeDynamicFeeWeightDecay(uint weightDecay) external onlyOwner { require(weightDecay != 0, "Weight decay cannot be 0"); - flexibleStorage().setUIntValue(SETTING_CONTRACT_NAME, SETTING_DYNAMIC_FEE_WEIGHT_DECAY, weightDecay); + flexibleStorage().setUIntValue(SETTING_CONTRACT_NAME, SETTING_EXCHANGE_DYNAMIC_FEE_WEIGHT_DECAY, weightDecay); emit ExchangeDynamicFeeWeightDecayUpdated(weightDecay); } /// @notice Set exchange dynamic fee last N rounds constant default to 10 + /// @param rounds The exchange dynamic fee last N rounds /// @return uint dynamic fee last N rounds function setExchangeDynamicFeeRounds(uint rounds) external onlyOwner { // Allowing to be 0 as a flag to disable Dynamic Fee // require(rounds != 0, "rounds cannot be 0"); - flexibleStorage().setUIntValue(SETTING_CONTRACT_NAME, SETTING_DYNAMIC_FEE_ROUNDS, rounds); + flexibleStorage().setUIntValue(SETTING_CONTRACT_NAME, SETTING_EXCHANGE_DYNAMIC_FEE_ROUNDS, rounds); emit ExchangeDynamicFeeRoundsUpdated(rounds); } + /// @notice Set max exchange dynamic fee + /// @param maxFee The max exchange dynamic fee + /// @return uint dynamic fee last N rounds + function setMaxExchangeDynamicFee(uint maxFee) external onlyOwner { + flexibleStorage().setUIntValue(SETTING_CONTRACT_NAME, SETTING_EXCHANGE_MAX_DYNAMIC_FEE, maxFee); + + emit ExchangeMaxDynamicFeeUpdated(maxFee); + } + function setMinimumStakeTime(uint _seconds) external onlyOwner { require(_seconds <= MAX_MINIMUM_STAKE_TIME, "stake time exceed maximum 1 week"); flexibleStorage().setUIntValue(SETTING_CONTRACT_NAME, SETTING_MINIMUM_STAKE_TIME, _seconds); @@ -613,6 +634,8 @@ contract SystemSettings is Owned, MixinSystemSettings, ISystemSettings { event ExchangeDynamicFeeThresholdUpdated(uint dynamicFeeThreshold); event ExchangeDynamicFeeWeightDecayUpdated(uint dynamicFeeWeightDecay); event ExchangeDynamicFeeRoundsUpdated(uint dynamicFeeRounds); + event ExchangeMaxDynamicFeeUpdated(uint maxDynamicFee); + /* ========== End Exchange Fees Related ========== */ event MinimumStakeTimeUpdated(uint minimumStakeTime); event DebtSnapshotStaleTimeUpdated(uint debtSnapshotStaleTime); event AggregatorWarningFlagsUpdated(address flags); diff --git a/index.js b/index.js index 35ebe32739..5b66697ea9 100644 --- a/index.js +++ b/index.js @@ -134,9 +134,10 @@ const defaults = { crypto: w3utils.toWei('0.01'), index: w3utils.toWei('0.01'), }, - DYNAMIC_FEE_THRESHOLD: w3utils.toWei('0.004'), // 40 bps - DYNAMIC_FEE_WEIGHT_DECAY: w3utils.toWei('0.9'), // dynamic fee weight decay for each round - DYNAMIC_FEE_ROUNDS: '10', // dynamic fee rounds + EXCHANGE_DYNAMIC_FEE_THRESHOLD: w3utils.toWei('0.004'), // 40 bps + EXCHANGE_DYNAMIC_FEE_WEIGHT_DECAY: w3utils.toWei('0.9'), // dynamic fee weight decay for each round + EXCHANGE_DYNAMIC_FEE_ROUNDS: '10', // dynamic fee rounds + EXCHANGE_MAX_DYNAMIC_FEE: w3utils.toWei('1'), // cap max dynamic fee to 100% MINIMUM_STAKE_TIME: (3600 * 24).toString(), // 1 days DEBT_SNAPSHOT_STALE_TIME: (43800).toString(), // 12 hour heartbeat + 10 minutes mining time AGGREGATOR_WARNING_FLAGS: { diff --git a/publish/src/commands/deploy/configure-system-settings.js b/publish/src/commands/deploy/configure-system-settings.js index ffa05d00ae..0a32096fe8 100644 --- a/publish/src/commands/deploy/configure-system-settings.js +++ b/publish/src/commands/deploy/configure-system-settings.js @@ -344,39 +344,52 @@ module.exports = async ({ }); // SIP-184 Exchange Dynamic Fee Rate - const dynamicFeeThreshold = await getDeployParameter('DYNAMIC_FEE_THRESHOLD'); + const exchangeDynamicFeeThreshold = await getDeployParameter('EXCHANGE_DYNAMIC_FEE_THRESHOLD'); await runStep({ contract: 'SystemSettings', target: SystemSettings, read: 'exchangeDynamicFeeThreshold', readTarget: previousSystemSettings, - expected: input => input === dynamicFeeThreshold, // only change if non-default + expected: input => input === exchangeDynamicFeeThreshold, // only change if non-default write: 'setExchangeDynamicFeeThreshold', - writeArg: dynamicFeeThreshold, + writeArg: exchangeDynamicFeeThreshold, comment: 'Set exchange dynamic fee threshold (SIP-184)', }); - const dynamicFeeWeightDecay = await getDeployParameter('DYNAMIC_FEE_WEIGHT_DECAY'); + const exchangeDynamicFeeWeightDecay = await getDeployParameter( + 'EXCHANGE_DYNAMIC_FEE_WEIGHT_DECAY' + ); await runStep({ contract: 'SystemSettings', target: SystemSettings, read: 'exchangeDynamicFeeWeightDecay', readTarget: previousSystemSettings, - expected: input => input === dynamicFeeWeightDecay, // only change if non-default + expected: input => input === exchangeDynamicFeeWeightDecay, // only change if non-default write: 'setExchangeDynamicFeeWeightDecay', - writeArg: dynamicFeeWeightDecay, + writeArg: exchangeDynamicFeeWeightDecay, comment: 'Set exchange dynamic fee weight decay (SIP-184)', }); - const dynamicFeeRounds = await getDeployParameter('DYNAMIC_FEE_ROUNDS'); + const exchangeDynamicFeeRounds = await getDeployParameter('EXCHANGE_DYNAMIC_FEE_ROUNDS'); await runStep({ contract: 'SystemSettings', target: SystemSettings, read: 'exchangeDynamicFeeRounds', readTarget: previousSystemSettings, - expected: input => input === dynamicFeeRounds, // only change if non-default + expected: input => input === exchangeDynamicFeeRounds, // only change if non-default write: 'setExchangeDynamicFeeRounds', - writeArg: dynamicFeeRounds, + writeArg: exchangeDynamicFeeRounds, comment: 'Set exchange dynamic fee rounds (SIP-184)', }); + const exchangeMaxDynamicFee = await getDeployParameter('EXCHANGE_MAX_DYNAMIC_FEE'); + await runStep({ + contract: 'SystemSettings', + target: SystemSettings, + read: 'exchangeMaxDynamicFee', + readTarget: previousSystemSettings, + expected: input => input === exchangeMaxDynamicFee, // only change if non-default + write: 'setExchangeMaxDynamicFee', + writeArg: exchangeMaxDynamicFee, + comment: 'Set exchange max dynamic fee (SIP-184)', + }); // SIP-120 Atomic swap settings if (SystemSettings.atomicMaxVolumePerBlock) { diff --git a/test/contracts/SystemSettings.js b/test/contracts/SystemSettings.js index d09b49f8d7..e1101da5b9 100644 --- a/test/contracts/SystemSettings.js +++ b/test/contracts/SystemSettings.js @@ -1563,4 +1563,23 @@ contract('SystemSettings', async accounts => { assert.eventEqual(txn, 'ExchangeDynamicFeeRoundsUpdated', [rounds]); }); }); + + describe('setExchangeMaxDynamicFee()', () => { + const maxDynamicFee = toUnit('1'); + it('only owner can invoke', async () => { + await onlyGivenAddressCanInvoke({ + fnc: systemSettings.setExchangeMaxDynamicFee, + args: [maxDynamicFee], + accounts, + address: owner, + reason: 'Only the contract owner may perform this action', + }); + }); + it('the owner can invoke and replace with emitted event', async () => { + const txn = await systemSettings.setExchangeMaxDynamicFee(maxDynamicFee, { from: owner }); + const actual = await systemSettings.exchangeMaxDynamicFee(); + assert.equal(actual, maxDynamicFee, 'Configured exchange max dynamic fee is set correctly'); + assert.eventEqual(txn, 'ExchangeMaxDynamicFeeUpdated', [maxDynamicFee]); + }); + }); }); From b8f3a8229a5a554e4fd0c155857c19c249bd6d3e Mon Sep 17 00:00:00 2001 From: Lecky Date: Wed, 1 Dec 2021 15:22:20 +1100 Subject: [PATCH 068/133] Merge ratesAndUpdatedTimeForCurrencyLastNRounds --- contracts/ExchangeRates.sol | 26 ++----------------------- contracts/Exchanger.sol | 4 ++-- contracts/SystemSettings.sol | 2 +- contracts/interfaces/IExchangeRates.sol | 7 +------ test/contracts/ExchangeRates.js | 14 ++++++------- test/contracts/setup.js | 21 ++++++++++++++------ 6 files changed, 28 insertions(+), 46 deletions(-) diff --git a/contracts/ExchangeRates.sol b/contracts/ExchangeRates.sol index 7f0d85df17..033f1047e0 100644 --- a/contracts/ExchangeRates.sol +++ b/contracts/ExchangeRates.sol @@ -305,7 +305,7 @@ contract ExchangeRates is Owned, MixinSystemSettings, IExchangeRates { /// @param numRounds the number of rounds to get /// @param roundId the round id /// @return a list of rates and a list of times - function ratesAndUpdatedTimeForCurrencyLastNRoundsAtRound( + function ratesAndUpdatedTimeForCurrencyLastNRounds( bytes32 currencyKey, uint numRounds, uint roundId @@ -313,29 +313,7 @@ contract ExchangeRates is Owned, MixinSystemSettings, IExchangeRates { rates = new uint[](numRounds); times = new uint[](numRounds); - for (uint i = 0; i < numRounds; i++) { - // fetch the rate and treat is as current, so inverse limits if frozen will always be applied - // regardless of current rate - (rates[i], times[i]) = _getRateAndUpdatedTimeAtRound(currencyKey, roundId); - - if (roundId == 0) { - // if we hit the last round, then return what we have - return (rates, times); - } else { - roundId--; - } - } - } - - function ratesAndUpdatedTimeForCurrencyLastNRounds(bytes32 currencyKey, uint numRounds) - external - view - returns (uint[] memory rates, uint[] memory times) - { - rates = new uint[](numRounds); - times = new uint[](numRounds); - - uint roundId = _getCurrentRoundId(currencyKey); + roundId = roundId > 0 ? roundId : _getCurrentRoundId(currencyKey); for (uint i = 0; i < numRounds; i++) { // fetch the rate and treat is as current, so inverse limits if frozen will always be applied // regardless of current rate diff --git a/contracts/Exchanger.sol b/contracts/Exchanger.sol index fbd6401488..aa25128a44 100644 --- a/contracts/Exchanger.sol +++ b/contracts/Exchanger.sol @@ -634,7 +634,7 @@ contract Exchanger is Owned, MixinSystemSettings, IExchanger { } // if no last exchange for this synth, then we need to look up last 3 rates (+1 for current rate) - (uint[] memory rates, ) = exchangeRates().ratesAndUpdatedTimeForCurrencyLastNRounds(currencyKey, 4); + (uint[] memory rates, ) = exchangeRates().ratesAndUpdatedTimeForCurrencyLastNRounds(currencyKey, 4, 0); // start at index 1 to ignore current rate for (uint i = 1; i < rates.length; i++) { @@ -808,7 +808,7 @@ contract Exchanger is Owned, MixinSystemSettings, IExchanger { uint[] memory prices; // Note: We are using cache round ID here for cheap read uint currentRoundId = exchangeRates().currentRoundForRate(currencyKey); - (prices, ) = exchangeRates().ratesAndUpdatedTimeForCurrencyLastNRoundsAtRound(currencyKey, rounds, currentRoundId); + (prices, ) = exchangeRates().ratesAndUpdatedTimeForCurrencyLastNRounds(currencyKey, rounds, currentRoundId); dynamicFee = DynamicFee.getDynamicFee(prices, threshold, weightDecay); } diff --git a/contracts/SystemSettings.sol b/contracts/SystemSettings.sol index 5d94ff882c..e746599f7b 100644 --- a/contracts/SystemSettings.sol +++ b/contracts/SystemSettings.sol @@ -424,7 +424,7 @@ contract SystemSettings is Owned, MixinSystemSettings, ISystemSettings { /// @notice Set max exchange dynamic fee /// @param maxFee The max exchange dynamic fee /// @return uint dynamic fee last N rounds - function setMaxExchangeDynamicFee(uint maxFee) external onlyOwner { + function setExchangeMaxDynamicFee(uint maxFee) external onlyOwner { flexibleStorage().setUIntValue(SETTING_CONTRACT_NAME, SETTING_EXCHANGE_MAX_DYNAMIC_FEE, maxFee); emit ExchangeMaxDynamicFeeUpdated(maxFee); diff --git a/contracts/interfaces/IExchangeRates.sol b/contracts/interfaces/IExchangeRates.sol index ca2729be25..b26391f985 100644 --- a/contracts/interfaces/IExchangeRates.sol +++ b/contracts/interfaces/IExchangeRates.sol @@ -103,12 +103,7 @@ interface IExchangeRates { function rateStalePeriod() external view returns (uint); - function ratesAndUpdatedTimeForCurrencyLastNRounds(bytes32 currencyKey, uint numRounds) - external - view - returns (uint[] memory rates, uint[] memory times); - - function ratesAndUpdatedTimeForCurrencyLastNRoundsAtRound( + function ratesAndUpdatedTimeForCurrencyLastNRounds( bytes32 currencyKey, uint numRounds, uint roundId diff --git a/test/contracts/ExchangeRates.js b/test/contracts/ExchangeRates.js index 8cbf60f94a..b358a7e52c 100644 --- a/test/contracts/ExchangeRates.js +++ b/test/contracts/ExchangeRates.js @@ -1870,14 +1870,14 @@ contract('Exchange Rates', async accounts => { it('ratesAndUpdatedTimeForCurrencyLastNRounds() shows first entry for sUSD', async () => { const timeOfsUSDRateSetOnInit = await instance.lastRateUpdateTimes(sUSD); - assert.deepEqual(await instance.ratesAndUpdatedTimeForCurrencyLastNRounds(sUSD, '3'), [ + assert.deepEqual(await instance.ratesAndUpdatedTimeForCurrencyLastNRounds(sUSD, '3', '0'), [ [toUnit('1'), '0', '0'], [timeOfsUSDRateSetOnInit, '0', '0'], ]); }); it('ratesAndUpdatedTimeForCurrencyLastNRounds() returns 0s for other currency keys', async () => { const fiveZeros = new Array(5).fill('0'); - assert.deepEqual(await instance.ratesAndUpdatedTimeForCurrencyLastNRounds(sAUD, '5'), [ + assert.deepEqual(await instance.ratesAndUpdatedTimeForCurrencyLastNRounds(sAUD, '5', '0'), [ fiveZeros, fiveZeros, ]); @@ -1963,7 +1963,7 @@ contract('Exchange Rates', async accounts => { it('then it returns 0s', async () => { const fiveZeros = new Array(5).fill('0'); assert.deepEqual( - await instance.ratesAndUpdatedTimeForCurrencyLastNRounds(sAUD, '5'), + await instance.ratesAndUpdatedTimeForCurrencyLastNRounds(sAUD, '5', '0'), [fiveZeros, fiveZeros] ); }); @@ -1971,7 +1971,7 @@ contract('Exchange Rates', async accounts => { describe('when invoked for an aggregated price', () => { it('then it returns the rates as expected', async () => { assert.deepEqual( - await instance.ratesAndUpdatedTimeForCurrencyLastNRounds(sJPY, '3'), + await instance.ratesAndUpdatedTimeForCurrencyLastNRounds(sJPY, '3', '0'), [ [toUnit('102'), toUnit('101'), toUnit('100')], ['1002', '1001', '1000'], @@ -1981,7 +1981,7 @@ contract('Exchange Rates', async accounts => { it('then it returns the rates as expected, even over the edge', async () => { assert.deepEqual( - await instance.ratesAndUpdatedTimeForCurrencyLastNRounds(sJPY, '5'), + await instance.ratesAndUpdatedTimeForCurrencyLastNRounds(sJPY, '5', '0'), [ [toUnit('102'), toUnit('101'), toUnit('100'), '0', '0'], ['1002', '1001', '1000', '0', '0'], @@ -1993,7 +1993,7 @@ contract('Exchange Rates', async accounts => { describe('when invoked for a regular price', () => { it('then it returns the rates as expected', async () => { assert.deepEqual( - await instance.ratesAndUpdatedTimeForCurrencyLastNRounds(sBNB, '3'), + await instance.ratesAndUpdatedTimeForCurrencyLastNRounds(sBNB, '3', '0'), [ [toUnit('1002'), toUnit('1001'), toUnit('1000')], ['10002', '10001', '10000'], @@ -2002,7 +2002,7 @@ contract('Exchange Rates', async accounts => { }); it('then it returns the rates as expected, even over the edge', async () => { assert.deepEqual( - await instance.ratesAndUpdatedTimeForCurrencyLastNRounds(sBNB, '5'), + await instance.ratesAndUpdatedTimeForCurrencyLastNRounds(sBNB, '5', '0'), [ [toUnit('1002'), toUnit('1001'), toUnit('1000'), '0', '0'], ['10002', '10001', '10000', '0', '0'], diff --git a/test/contracts/setup.js b/test/contracts/setup.js index 3bc9de56b0..a2836e33f5 100644 --- a/test/contracts/setup.js +++ b/test/contracts/setup.js @@ -18,9 +18,10 @@ const { LIQUIDATION_RATIO, LIQUIDATION_PENALTY, RATE_STALE_PERIOD, - DYNAMIC_FEE_THRESHOLD, - DYNAMIC_FEE_WEIGHT_DECAY, - DYNAMIC_FEE_ROUNDS, + EXCHANGE_DYNAMIC_FEE_THRESHOLD, + EXCHANGE_DYNAMIC_FEE_WEIGHT_DECAY, + EXCHANGE_DYNAMIC_FEE_ROUNDS, + EXCHANGE_MAX_DYNAMIC_FEE, MINIMUM_STAKE_TIME, DEBT_SNAPSHOT_STALE_TIME, ATOMIC_MAX_VOLUME_PER_BLOCK, @@ -1116,13 +1117,21 @@ const setupAllContracts = async ({ returnObj['SystemSettings'].setLiquidationRatio(LIQUIDATION_RATIO, { from: owner }), returnObj['SystemSettings'].setLiquidationPenalty(LIQUIDATION_PENALTY, { from: owner }), returnObj['SystemSettings'].setRateStalePeriod(RATE_STALE_PERIOD, { from: owner }), - returnObj['SystemSettings'].setExchangeDynamicFeeThreshold(DYNAMIC_FEE_THRESHOLD, { + returnObj['SystemSettings'].setExchangeDynamicFeeThreshold(EXCHANGE_DYNAMIC_FEE_THRESHOLD, { from: owner, }), - returnObj['SystemSettings'].setExchangeDynamicFeeWeightDecay(DYNAMIC_FEE_WEIGHT_DECAY, { + returnObj['SystemSettings'].setExchangeDynamicFeeWeightDecay( + EXCHANGE_DYNAMIC_FEE_WEIGHT_DECAY, + { + from: owner, + } + ), + returnObj['SystemSettings'].setExchangeDynamicFeeRounds(EXCHANGE_DYNAMIC_FEE_ROUNDS, { + from: owner, + }), + returnObj['SystemSettings'].setExchangeMaxDynamicFee(EXCHANGE_MAX_DYNAMIC_FEE, { from: owner, }), - returnObj['SystemSettings'].setExchangeDynamicFeeRounds(DYNAMIC_FEE_ROUNDS, { from: owner }), returnObj['SystemSettings'].setMinimumStakeTime(MINIMUM_STAKE_TIME, { from: owner }), returnObj['SystemSettings'].setDebtSnapshotStaleTime(DEBT_SNAPSHOT_STALE_TIME, { from: owner, From a18babbb0507f6b7aab7704a39462653737f6a0c Mon Sep 17 00:00:00 2001 From: Lecky Date: Thu, 2 Dec 2021 11:37:56 +1100 Subject: [PATCH 069/133] Fix(ExchangerWithFeeRecAlternatives.unit.js): Adding default max dynamic fee - remove duplicated code in _calculateFeeRateFromExchangeSynths - Added test cases for up/down price differential --- contracts/Exchanger.sol | 8 - .../ExchangerWithFeeRecAlternatives.unit.js | 522 +++++++++--------- test/contracts/TestableDynamicFee.js | 11 +- 3 files changed, 283 insertions(+), 258 deletions(-) diff --git a/contracts/Exchanger.sol b/contracts/Exchanger.sol index aa25128a44..1f3168a021 100644 --- a/contracts/Exchanger.sol +++ b/contracts/Exchanger.sol @@ -778,18 +778,10 @@ contract Exchanger is Owned, MixinSystemSettings, IExchanger { bytes32 sourceCurrencyKey, bytes32 destinationCurrencyKey ) internal view returns (uint, uint exchangeDynamicFeeRate) { - // Get the exchange dynamic fee rate as per destination currencyKey exchangeDynamicFeeRate = _getDynamicFeeForExchange(destinationCurrencyKey); exchangeDynamicFeeRate = exchangeDynamicFeeRate.add(_getDynamicFeeForExchange(sourceCurrencyKey)); uint maxDynamicFee = getExchangeMaxDynamicFee(); - if (sourceCurrencyKey == sUSD || destinationCurrencyKey == sUSD) { - exchangeFeeRate = exchangeFeeRate.add(exchangeDynamicFeeRate); - // Cap to max exchange dynamic fee - exchangeFeeRate = exchangeFeeRate > maxDynamicFee ? maxDynamicFee : exchangeFeeRate; - return (exchangeFeeRate, exchangeDynamicFeeRate); - } - exchangeFeeRate = exchangeFeeRate.add(exchangeDynamicFeeRate); // Cap to max exchange dynamic fee exchangeFeeRate = exchangeFeeRate > maxDynamicFee ? maxDynamicFee : exchangeFeeRate; diff --git a/test/contracts/ExchangerWithFeeRecAlternatives.unit.js b/test/contracts/ExchangerWithFeeRecAlternatives.unit.js index 967fad550b..d8577c3509 100644 --- a/test/contracts/ExchangerWithFeeRecAlternatives.unit.js +++ b/test/contracts/ExchangerWithFeeRecAlternatives.unit.js @@ -76,38 +76,21 @@ contract('ExchangerWithFeeRecAlternatives (unit tests)', async accounts => { ); }); - describe('feeRateForAtomicExchange()', () => { - // Mimic settings not being configured - behaviors.whenMockedWithSynthUintSystemSetting( - { setting: 'exchangeFeeRate', synth: sETH, value: '0' }, - () => { - it('is set to 0', async () => { - assert.bnEqual(await this.instance.feeRateForAtomicExchange(sUSD, sETH), '0'); - }); - } - ); - - // With configured override value - behaviors.whenMockedWithSynthUintSystemSetting( - { setting: 'atomicExchangeFeeRate', synth: sETH, value: overrideFeeRate }, - () => { - it('is set to the configured atomic override value', async () => { - assert.bnEqual( - await this.instance.feeRateForAtomicExchange(sUSD, sETH), - overrideFeeRate - ); - }); - } - ); - - // With configured base and override values - behaviors.whenMockedWithSynthUintSystemSetting( - { setting: 'exchangeFeeRate', synth: sETH, value: baseFeeRate }, - () => { - it('is set to the configured base value', async () => { - assert.bnEqual(await this.instance.feeRateForAtomicExchange(sUSD, sETH), baseFeeRate); - }); + behaviors.whenMockedWithUintSystemSetting( + { setting: 'exchangeMaxDynamicFee', value: toUnit('1') }, + () => { + describe('feeRateForAtomicExchange()', () => { + // Mimic settings not being configured + behaviors.whenMockedWithSynthUintSystemSetting( + { setting: 'exchangeFeeRate', synth: sETH, value: '0' }, + () => { + it('is set to 0', async () => { + assert.bnEqual(await this.instance.feeRateForAtomicExchange(sUSD, sETH), '0'); + }); + } + ); + // With configured override value behaviors.whenMockedWithSynthUintSystemSetting( { setting: 'atomicExchangeFeeRate', synth: sETH, value: overrideFeeRate }, () => { @@ -119,9 +102,34 @@ contract('ExchangerWithFeeRecAlternatives (unit tests)', async accounts => { }); } ); - } - ); - }); + + // With configured base and override values + behaviors.whenMockedWithSynthUintSystemSetting( + { setting: 'exchangeFeeRate', synth: sETH, value: baseFeeRate }, + () => { + it('is set to the configured base value', async () => { + assert.bnEqual( + await this.instance.feeRateForAtomicExchange(sUSD, sETH), + baseFeeRate + ); + }); + + behaviors.whenMockedWithSynthUintSystemSetting( + { setting: 'atomicExchangeFeeRate', synth: sETH, value: overrideFeeRate }, + () => { + it('is set to the configured atomic override value', async () => { + assert.bnEqual( + await this.instance.feeRateForAtomicExchange(sUSD, sETH), + overrideFeeRate + ); + }); + } + ); + } + ); + }); + } + ); describe('getAmountsForAtomicExchange()', () => { const atomicRate = toUnit('0.01'); @@ -636,224 +644,246 @@ contract('ExchangerWithFeeRecAlternatives (unit tests)', async accounts => { }, () => { behaviors.whenMockedWithUintSystemSetting( - { setting: 'atomicMaxVolumePerBlock', value: maxAtomicValuePerBlock }, + { setting: 'exchangeMaxDynamicFee', value: toUnit('1') }, () => { - const itExchangesCorrectly = ({ - exchangeFeeRate, - setAsOverrideRate, - tradingRewardsEnabled, - trackingCode, - }) => { - behaviors.whenMockedWithBoolSystemSetting( - { - setting: 'tradingRewardsEnabled', - value: !!tradingRewardsEnabled, - }, - () => { - behaviors.whenMockedWithSynthUintSystemSetting( + behaviors.whenMockedWithUintSystemSetting( + { setting: 'atomicMaxVolumePerBlock', value: maxAtomicValuePerBlock }, + () => { + const itExchangesCorrectly = ({ + exchangeFeeRate, + setAsOverrideRate, + tradingRewardsEnabled, + trackingCode, + }) => { + behaviors.whenMockedWithBoolSystemSetting( { - setting: setAsOverrideRate - ? 'atomicExchangeFeeRate' - : 'exchangeFeeRate', - synth: sETH, - value: exchangeFeeRate, + setting: 'tradingRewardsEnabled', + value: !!tradingRewardsEnabled, }, () => { - let expectedAmountReceived; - let expectedFee; - beforeEach('attempt exchange', async () => { - expectedFee = multiplyDecimal(amountIn, exchangeFeeRate); - expectedAmountReceived = divideDecimal( - amountIn.sub(expectedFee), - lastEthRate - ); - - await this.instance.exchangeAtomically( - ...getExchangeArgs({ - trackingCode, - }) - ); - }); - it('burned correct amount of sUSD', () => { - assert.equal( - this.mocks.sUSD.smocked.burn.calls[0][0], - owner - ); - assert.bnEqual( - this.mocks.sUSD.smocked.burn.calls[0][1], - amountIn - ); - }); - it('issued correct amount of sETH', () => { - assert.equal( - this.mocks.sETH.smocked.issue.calls[0][0], - owner - ); - assert.bnEqual( - this.mocks.sETH.smocked.issue.calls[0][1], - expectedAmountReceived - ); - }); - it('tracked atomic volume', async () => { - assert.bnEqual( - (await this.instance.lastAtomicVolume()).volume, - amountIn - ); - }); - it('updated debt cache', () => { - const debtCacheUpdateCall = this.mocks.DebtCache.smocked - .updateCachedSynthDebtsWithRates; - assert.deepEqual(debtCacheUpdateCall.calls[0][0], [ - sUSD, - sETH, - ]); - assert.deepEqual(debtCacheUpdateCall.calls[0][1], [ - lastUsdRate, - lastEthRate, - ]); - }); - it('asked Synthetix to emit an exchange event', () => { - const synthetixEmitExchangeCall = this.mocks.Synthetix - .smocked.emitSynthExchange; - assert.equal(synthetixEmitExchangeCall.calls[0][0], owner); - assert.equal(synthetixEmitExchangeCall.calls[0][1], sUSD); - assert.bnEqual( - synthetixEmitExchangeCall.calls[0][2], - amountIn - ); - assert.equal(synthetixEmitExchangeCall.calls[0][3], sETH); - assert.bnEqual( - synthetixEmitExchangeCall.calls[0][4], - expectedAmountReceived - ); - assert.equal(synthetixEmitExchangeCall.calls[0][5], owner); - }); - it('asked Synthetix to emit an atomic exchange event', () => { - const synthetixEmitAtomicExchangeCall = this.mocks.Synthetix - .smocked.emitAtomicSynthExchange; - assert.equal( - synthetixEmitAtomicExchangeCall.calls[0][0], - owner - ); - assert.equal( - synthetixEmitAtomicExchangeCall.calls[0][1], - sUSD - ); - assert.bnEqual( - synthetixEmitAtomicExchangeCall.calls[0][2], - amountIn - ); - assert.equal( - synthetixEmitAtomicExchangeCall.calls[0][3], - sETH - ); - assert.bnEqual( - synthetixEmitAtomicExchangeCall.calls[0][4], - expectedAmountReceived - ); - assert.equal( - synthetixEmitAtomicExchangeCall.calls[0][5], - owner - ); - }); - it('did not add any fee reclamation entries to exchange state', () => { - assert.equal( - this.mocks.ExchangeState.smocked.appendExchangeEntry.calls - .length, - 0 - ); - }); - - // Conditional based on test settings - if (toBN(exchangeFeeRate).isZero()) { - it('did not report a fee', () => { - assert.equal( - this.mocks.FeePool.smocked.recordFeePaid.calls.length, - 0 - ); - }); - } else { - it('remitted correct fee to fee pool', () => { - assert.equal( - this.mocks.sUSD.smocked.issue.calls[0][0], - getUsers({ network: 'mainnet', user: 'fee' }).address - ); - assert.bnEqual( - this.mocks.sUSD.smocked.issue.calls[0][1], - expectedFee - ); - assert.bnEqual( - this.mocks.FeePool.smocked.recordFeePaid.calls[0], - expectedFee - ); - }); - } - if (!tradingRewardsEnabled) { - it('did not report trading rewards', () => { - assert.equal( - this.mocks.TradingRewards.smocked - .recordExchangeFeeForAccount.calls.length, - 0 - ); - }); - } else { - it('reported trading rewards', () => { - const trRecordCall = this.mocks.TradingRewards.smocked - .recordExchangeFeeForAccount; - assert.bnEqual(trRecordCall.calls[0][0], expectedFee); - assert.equal(trRecordCall.calls[0][1], owner); - }); - } - if (!trackingCode) { - it('did not ask Synthetix to emit tracking event', () => { - assert.equal( - this.mocks.Synthetix.smocked.emitExchangeTracking.calls - .length, - 0 - ); - }); - } else { - it('asked Synthetix to emit tracking event', () => { - const synthetixEmitTrackingCall = this.mocks.Synthetix - .smocked.emitExchangeTracking; - assert.equal( - synthetixEmitTrackingCall.calls[0][0], - trackingCode - ); - }); - } + behaviors.whenMockedWithSynthUintSystemSetting( + { + setting: setAsOverrideRate + ? 'atomicExchangeFeeRate' + : 'exchangeFeeRate', + synth: sETH, + value: exchangeFeeRate, + }, + () => { + let expectedAmountReceived; + let expectedFee; + beforeEach('attempt exchange', async () => { + expectedFee = multiplyDecimal( + amountIn, + exchangeFeeRate + ); + expectedAmountReceived = divideDecimal( + amountIn.sub(expectedFee), + lastEthRate + ); + + await this.instance.exchangeAtomically( + ...getExchangeArgs({ + trackingCode, + }) + ); + }); + it('burned correct amount of sUSD', () => { + assert.equal( + this.mocks.sUSD.smocked.burn.calls[0][0], + owner + ); + assert.bnEqual( + this.mocks.sUSD.smocked.burn.calls[0][1], + amountIn + ); + }); + it('issued correct amount of sETH', () => { + assert.equal( + this.mocks.sETH.smocked.issue.calls[0][0], + owner + ); + assert.bnEqual( + this.mocks.sETH.smocked.issue.calls[0][1], + expectedAmountReceived + ); + }); + it('tracked atomic volume', async () => { + assert.bnEqual( + (await this.instance.lastAtomicVolume()).volume, + amountIn + ); + }); + it('updated debt cache', () => { + const debtCacheUpdateCall = this.mocks.DebtCache.smocked + .updateCachedSynthDebtsWithRates; + assert.deepEqual(debtCacheUpdateCall.calls[0][0], [ + sUSD, + sETH, + ]); + assert.deepEqual(debtCacheUpdateCall.calls[0][1], [ + lastUsdRate, + lastEthRate, + ]); + }); + it('asked Synthetix to emit an exchange event', () => { + const synthetixEmitExchangeCall = this.mocks.Synthetix + .smocked.emitSynthExchange; + assert.equal( + synthetixEmitExchangeCall.calls[0][0], + owner + ); + assert.equal( + synthetixEmitExchangeCall.calls[0][1], + sUSD + ); + assert.bnEqual( + synthetixEmitExchangeCall.calls[0][2], + amountIn + ); + assert.equal( + synthetixEmitExchangeCall.calls[0][3], + sETH + ); + assert.bnEqual( + synthetixEmitExchangeCall.calls[0][4], + expectedAmountReceived + ); + assert.equal( + synthetixEmitExchangeCall.calls[0][5], + owner + ); + }); + it('asked Synthetix to emit an atomic exchange event', () => { + const synthetixEmitAtomicExchangeCall = this.mocks + .Synthetix.smocked.emitAtomicSynthExchange; + assert.equal( + synthetixEmitAtomicExchangeCall.calls[0][0], + owner + ); + assert.equal( + synthetixEmitAtomicExchangeCall.calls[0][1], + sUSD + ); + assert.bnEqual( + synthetixEmitAtomicExchangeCall.calls[0][2], + amountIn + ); + assert.equal( + synthetixEmitAtomicExchangeCall.calls[0][3], + sETH + ); + assert.bnEqual( + synthetixEmitAtomicExchangeCall.calls[0][4], + expectedAmountReceived + ); + assert.equal( + synthetixEmitAtomicExchangeCall.calls[0][5], + owner + ); + }); + it('did not add any fee reclamation entries to exchange state', () => { + assert.equal( + this.mocks.ExchangeState.smocked.appendExchangeEntry + .calls.length, + 0 + ); + }); + + // Conditional based on test settings + if (toBN(exchangeFeeRate).isZero()) { + it('did not report a fee', () => { + assert.equal( + this.mocks.FeePool.smocked.recordFeePaid.calls + .length, + 0 + ); + }); + } else { + it('remitted correct fee to fee pool', () => { + assert.equal( + this.mocks.sUSD.smocked.issue.calls[0][0], + getUsers({ network: 'mainnet', user: 'fee' }) + .address + ); + assert.bnEqual( + this.mocks.sUSD.smocked.issue.calls[0][1], + expectedFee + ); + assert.bnEqual( + this.mocks.FeePool.smocked.recordFeePaid.calls[0], + expectedFee + ); + }); + } + if (!tradingRewardsEnabled) { + it('did not report trading rewards', () => { + assert.equal( + this.mocks.TradingRewards.smocked + .recordExchangeFeeForAccount.calls.length, + 0 + ); + }); + } else { + it('reported trading rewards', () => { + const trRecordCall = this.mocks.TradingRewards.smocked + .recordExchangeFeeForAccount; + assert.bnEqual(trRecordCall.calls[0][0], expectedFee); + assert.equal(trRecordCall.calls[0][1], owner); + }); + } + if (!trackingCode) { + it('did not ask Synthetix to emit tracking event', () => { + assert.equal( + this.mocks.Synthetix.smocked.emitExchangeTracking + .calls.length, + 0 + ); + }); + } else { + it('asked Synthetix to emit tracking event', () => { + const synthetixEmitTrackingCall = this.mocks.Synthetix + .smocked.emitExchangeTracking; + assert.equal( + synthetixEmitTrackingCall.calls[0][0], + trackingCode + ); + }); + } + } + ); } ); - } - ); - }; - - describe('when no exchange fees are configured', () => { - itExchangesCorrectly({ - exchangeFeeRate: '0', - }); - }); - - describe('with tracking code', () => { - itExchangesCorrectly({ - exchangeFeeRate: '0', - trackingCode: toBytes32('TRACKING'), - }); - }); - - describe('when an exchange fee is configured', () => { - itExchangesCorrectly({ - exchangeFeeRate: baseFeeRate, - tradingRewardsEnabled: true, - }); - }); - describe('when an exchange fee override for atomic exchanges is configured', () => { - itExchangesCorrectly({ - exchangeFeeRate: overrideFeeRate, - setAsOverrideRate: true, - tradingRewardsEnabled: true, - }); - }); + }; + + describe('when no exchange fees are configured', () => { + itExchangesCorrectly({ + exchangeFeeRate: '0', + }); + }); + + describe('with tracking code', () => { + itExchangesCorrectly({ + exchangeFeeRate: '0', + trackingCode: toBytes32('TRACKING'), + }); + }); + + describe('when an exchange fee is configured', () => { + itExchangesCorrectly({ + exchangeFeeRate: baseFeeRate, + tradingRewardsEnabled: true, + }); + }); + describe('when an exchange fee override for atomic exchanges is configured', () => { + itExchangesCorrectly({ + exchangeFeeRate: overrideFeeRate, + setAsOverrideRate: true, + tradingRewardsEnabled: true, + }); + }); + } + ); } ); } diff --git a/test/contracts/TestableDynamicFee.js b/test/contracts/TestableDynamicFee.js index 819fc49d10..b6b5c52a99 100644 --- a/test/contracts/TestableDynamicFee.js +++ b/test/contracts/TestableDynamicFee.js @@ -17,11 +17,14 @@ contract('TestableDynamicFee', () => { }); it('Can get price differential', async () => { - const priceDiff = await testableDynamicFee.testGetPriceDifferential( - toUnit('102'), - toUnit('101') + const priceDiff1 = await testableDynamicFee.testGetPriceDifferential(toUnit('8'), toUnit('10')); + assert.bnEqual(priceDiff1, '196000000000000000'); + const priceDiff2 = await testableDynamicFee.testGetPriceDifferential( + toUnit('12'), + toUnit('10') ); - assert.bnEqual(priceDiff, '5900990099009900'); + assert.bnEqual(priceDiff2, '196000000000000000'); + assert.bnEqual(priceDiff1, priceDiff2); }); it('Can get price weight', async () => { From 0237bd41929438c20431a92233c11f83f57de5af Mon Sep 17 00:00:00 2001 From: Lecky Date: Thu, 2 Dec 2021 15:25:00 +1100 Subject: [PATCH 070/133] Rename DYNAMIC_FEE_ROUNDS to prepend EXCHANGE to be consistent --- publish/deployed/mainnet/params.json | 2 +- test/contracts/CollateralShort.js | 6 +++--- test/contracts/DebtCache.js | 4 ++-- test/contracts/Exchanger.spec.js | 10 +++++----- test/contracts/FeePool.js | 4 ++-- test/contracts/Issuer.js | 6 +++--- test/contracts/PurgeableSynth.js | 6 +++--- test/contracts/RewardsIntegrationTests.js | 4 ++-- test/contracts/Synth.js | 6 +++--- test/contracts/SynthUtil.js | 4 ++-- test/contracts/TradingRewards.spec.js | 4 ++-- test/contracts/helpers.js | 4 ++-- test/integration/utils/rates.js | 4 ++-- 13 files changed, 32 insertions(+), 32 deletions(-) diff --git a/publish/deployed/mainnet/params.json b/publish/deployed/mainnet/params.json index 9abc2616a9..55615d3800 100644 --- a/publish/deployed/mainnet/params.json +++ b/publish/deployed/mainnet/params.json @@ -1,6 +1,6 @@ [ { - "name": "DYNAMIC_FEE_ROUNDS", + "name": "EXCHANGE_DYNAMIC_FEE_ROUNDS", "value": "0" }, { diff --git a/test/contracts/CollateralShort.js b/test/contracts/CollateralShort.js index 56fbbb094d..158444161f 100644 --- a/test/contracts/CollateralShort.js +++ b/test/contracts/CollateralShort.js @@ -12,7 +12,7 @@ const { ensureOnlyExpectedMutativeFunctions, setExchangeFeeRateForSynths } = req const { toBytes32, - defaults: { DYNAMIC_FEE_ROUNDS }, + defaults: { EXCHANGE_DYNAMIC_FEE_ROUNDS }, } = require('../..'); contract('CollateralShort', async accounts => { @@ -58,7 +58,7 @@ contract('CollateralShort', async accounts => { const updateRatesWithDefaults = async () => { let timestamp; - for (let i = 0; i < DYNAMIC_FEE_ROUNDS; i++) { + for (let i = 0; i < EXCHANGE_DYNAMIC_FEE_ROUNDS; i++) { timestamp = await currentTime(); await exchangeRates.updateRates([sETH], ['100'].map(toUnit), timestamp, { from: oracle, @@ -67,7 +67,7 @@ contract('CollateralShort', async accounts => { const sBTC = toBytes32('sBTC'); - for (let i = 0; i < DYNAMIC_FEE_ROUNDS; i++) { + for (let i = 0; i < EXCHANGE_DYNAMIC_FEE_ROUNDS; i++) { timestamp = await currentTime(); await exchangeRates.updateRates([sBTC], ['10000'].map(toUnit), timestamp, { from: oracle, diff --git a/test/contracts/DebtCache.js b/test/contracts/DebtCache.js index 539d2e78e8..003608713b 100644 --- a/test/contracts/DebtCache.js +++ b/test/contracts/DebtCache.js @@ -19,7 +19,7 @@ const { const { toBytes32, - defaults: { DEBT_SNAPSHOT_STALE_TIME, DYNAMIC_FEE_ROUNDS }, + defaults: { DEBT_SNAPSHOT_STALE_TIME, EXCHANGE_DYNAMIC_FEE_ROUNDS }, constants: { ZERO_ADDRESS }, } = require('../..'); @@ -284,7 +284,7 @@ contract('DebtCache', async accounts => { addSnapshotBeforeRestoreAfterEach(); beforeEach(async () => { - for (let i = 0; i < DYNAMIC_FEE_ROUNDS; i++) { + for (let i = 0; i < EXCHANGE_DYNAMIC_FEE_ROUNDS; i++) { timestamp = await currentTime(); await exchangeRates.updateRates( diff --git a/test/contracts/Exchanger.spec.js b/test/contracts/Exchanger.spec.js index b8a96d2d60..c8b1a0b67d 100644 --- a/test/contracts/Exchanger.spec.js +++ b/test/contracts/Exchanger.spec.js @@ -26,7 +26,7 @@ const { WAITING_PERIOD_SECS, PRICE_DEVIATION_THRESHOLD_FACTOR, ATOMIC_MAX_VOLUME_PER_BLOCK, - DYNAMIC_FEE_ROUNDS, + EXCHANGE_DYNAMIC_FEE_ROUNDS, }, } = require('../..'); @@ -2220,7 +2220,7 @@ contract('Exchanger (spec tests)', async accounts => { aggregator = await MockAggregator.new({ from: owner }); await exchangeRates.addAggregator(sETH, aggregator.address, { from: owner }); // set a 0 rate to prevent invalid rate from causing a revert on exchange - for (let i = 0; i < DYNAMIC_FEE_ROUNDS; i++) { + for (let i = 0; i < EXCHANGE_DYNAMIC_FEE_ROUNDS; i++) { // Need to run twice in order to increase the roundId // to be greater than the one in the cache await aggregator.setLatestAnswer('0', await currentTime()); @@ -3312,7 +3312,7 @@ contract('Exchanger (spec tests)', async accounts => { describe('and the aggregator has a rate (so the exchange succeeds)', () => { beforeEach(async () => { - for (let i = 0; i < DYNAMIC_FEE_ROUNDS; i++) { + for (let i = 0; i < EXCHANGE_DYNAMIC_FEE_ROUNDS; i++) { await aggregator.setLatestAnswer( convertToAggregatorPrice(100), await currentTime() @@ -3590,7 +3590,7 @@ contract('Exchanger (spec tests)', async accounts => { addSnapshotBeforeRestoreAfterEach(); beforeEach(async () => { - for (let i = 0; i < DYNAMIC_FEE_ROUNDS; i++) { + for (let i = 0; i < EXCHANGE_DYNAMIC_FEE_ROUNDS; i++) { timestamp = await currentTime(); await exchangeRates.updateRates( [sAUD, sEUR, SNX, sETH, sBTC, iBTC], @@ -3694,7 +3694,7 @@ contract('Exchanger (spec tests)', async accounts => { addSnapshotBeforeRestoreAfterEach(); beforeEach(async () => { - for (let i = 0; i < DYNAMIC_FEE_ROUNDS; i++) { + for (let i = 0; i < EXCHANGE_DYNAMIC_FEE_ROUNDS; i++) { timestamp = await currentTime(); await exchangeRates.updateRates( [sAUD, sEUR, SNX, sETH, sBTC, iBTC], diff --git a/test/contracts/FeePool.js b/test/contracts/FeePool.js index 0f183430e5..2c14550911 100644 --- a/test/contracts/FeePool.js +++ b/test/contracts/FeePool.js @@ -30,7 +30,7 @@ const { setupAllContracts } = require('./setup'); const { toBytes32, - defaults: { ISSUANCE_RATIO, FEE_PERIOD_DURATION, TARGET_THRESHOLD, DYNAMIC_FEE_ROUNDS }, + defaults: { ISSUANCE_RATIO, FEE_PERIOD_DURATION, TARGET_THRESHOLD, EXCHANGE_DYNAMIC_FEE_ROUNDS }, } = require('../..'); contract('FeePool', async accounts => { @@ -39,7 +39,7 @@ contract('FeePool', async accounts => { // Updates rates with defaults so they're not stale. const updateRatesWithDefaults = async () => { let timestamp; - for (let i = 0; i < DYNAMIC_FEE_ROUNDS; i++) { + for (let i = 0; i < EXCHANGE_DYNAMIC_FEE_ROUNDS; i++) { timestamp = await currentTime(); await exchangeRates.updateRates([sAUD, SNX], ['0.5', '0.1'].map(toUnit), timestamp, { diff --git a/test/contracts/Issuer.js b/test/contracts/Issuer.js index 28f7c0f94e..d49754f592 100644 --- a/test/contracts/Issuer.js +++ b/test/contracts/Issuer.js @@ -30,7 +30,7 @@ const { const { toBytes32, constants: { ZERO_ADDRESS }, - defaults: { ISSUANCE_RATIO, MINIMUM_STAKE_TIME, DYNAMIC_FEE_ROUNDS }, + defaults: { ISSUANCE_RATIO, MINIMUM_STAKE_TIME, EXCHANGE_DYNAMIC_FEE_ROUNDS }, } = require('../..'); contract('Issuer (via Synthetix)', async accounts => { @@ -116,7 +116,7 @@ contract('Issuer (via Synthetix)', async accounts => { addSnapshotBeforeRestoreAfterEach(); beforeEach(async () => { - for (let i = 0; i < DYNAMIC_FEE_ROUNDS; i++) { + for (let i = 0; i < EXCHANGE_DYNAMIC_FEE_ROUNDS; i++) { timestamp = await currentTime(); await exchangeRates.updateRates( @@ -721,7 +721,7 @@ contract('Issuer (via Synthetix)', async accounts => { }); describe('when the synth has a rate', () => { beforeEach(async () => { - for (let i = 0; i < DYNAMIC_FEE_ROUNDS; i++) { + for (let i = 0; i < EXCHANGE_DYNAMIC_FEE_ROUNDS; i++) { await exchangeRates.updateRates([currencyKey], [toUnit('2')], timestamp, { from: oracle, }); diff --git a/test/contracts/PurgeableSynth.js b/test/contracts/PurgeableSynth.js index ba0ce15173..1c5d53e550 100644 --- a/test/contracts/PurgeableSynth.js +++ b/test/contracts/PurgeableSynth.js @@ -11,7 +11,7 @@ const PurgeableSynth = artifacts.require('PurgeableSynth'); const { currentTime, fastForward, toUnit } = require('../utils')(); const { toBytes32, - defaults: { DYNAMIC_FEE_ROUNDS }, + defaults: { EXCHANGE_DYNAMIC_FEE_ROUNDS }, constants: { ZERO_ADDRESS }, } = require('../..'); @@ -156,7 +156,7 @@ contract('PurgeableSynth', accounts => { describe("when there's a price for the purgeable synth", () => { beforeEach(async () => { - for (let i = 0; i < DYNAMIC_FEE_ROUNDS; i++) { + for (let i = 0; i < EXCHANGE_DYNAMIC_FEE_ROUNDS; i++) { await exchangeRates.updateRates( [sAUD, SNX, iETH], ['0.5', '1', '170'].map(toUnit), @@ -325,7 +325,7 @@ contract('PurgeableSynth', accounts => { describe('Replacing an existing Synth with a Purgeable one to purge and remove it', () => { describe('when sAUD has a price', () => { beforeEach(async () => { - for (let i = 0; i < DYNAMIC_FEE_ROUNDS; i++) { + for (let i = 0; i < EXCHANGE_DYNAMIC_FEE_ROUNDS; i++) { await exchangeRates.updateRates([sAUD], ['0.776845993'].map(toUnit), timestamp, { from: oracle, }); diff --git a/test/contracts/RewardsIntegrationTests.js b/test/contracts/RewardsIntegrationTests.js index 1e6ea0c95d..84f280bb0d 100644 --- a/test/contracts/RewardsIntegrationTests.js +++ b/test/contracts/RewardsIntegrationTests.js @@ -6,7 +6,7 @@ const { assert, addSnapshotBeforeRestoreAfterEach } = require('./common'); const { toBytes32, - defaults: { DYNAMIC_FEE_ROUNDS }, + defaults: { EXCHANGE_DYNAMIC_FEE_ROUNDS }, } = require('../..'); const { currentTime, fastForward, toUnit, toPreciseUnit, multiplyDecimal } = require('../utils')(); @@ -66,7 +66,7 @@ contract('Rewards Integration Tests', accounts => { // Updates rates with defaults so they're not stale. const updateRatesWithDefaults = async () => { let timestamp; - for (let i = 0; i < DYNAMIC_FEE_ROUNDS; i++) { + for (let i = 0; i < EXCHANGE_DYNAMIC_FEE_ROUNDS; i++) { timestamp = await currentTime(); await exchangeRates.updateRates( diff --git a/test/contracts/Synth.js b/test/contracts/Synth.js index 4c9cf03506..a9e1833265 100644 --- a/test/contracts/Synth.js +++ b/test/contracts/Synth.js @@ -20,7 +20,7 @@ const { const { toBytes32, constants: { ZERO_ADDRESS }, - defaults: { DYNAMIC_FEE_ROUNDS }, + defaults: { EXCHANGE_DYNAMIC_FEE_ROUNDS }, } = require('../..'); contract('Synth', async accounts => { @@ -81,7 +81,7 @@ contract('Synth', async accounts => { let timestamp; // Send a price update to guarantee we're not stale. - for (let i = 0; i < DYNAMIC_FEE_ROUNDS; i++) { + for (let i = 0; i < EXCHANGE_DYNAMIC_FEE_ROUNDS; i++) { timestamp = await currentTime(); await exchangeRates.updateRates([SNX], ['0.1'].map(toUnit), timestamp, { from: oracle, @@ -740,7 +740,7 @@ contract('Synth', async accounts => { let timestamp; // Send a price update to guarantee we're not stale. - for (let i = 0; i < DYNAMIC_FEE_ROUNDS; i++) { + for (let i = 0; i < EXCHANGE_DYNAMIC_FEE_ROUNDS; i++) { timestamp = await currentTime(); await exchangeRates.updateRates([sEUR], ['1'].map(toUnit), timestamp, { from: oracle, diff --git a/test/contracts/SynthUtil.js b/test/contracts/SynthUtil.js index 6f97aebc0f..afbd47d4ba 100644 --- a/test/contracts/SynthUtil.js +++ b/test/contracts/SynthUtil.js @@ -4,7 +4,7 @@ const { contract } = require('hardhat'); const { assert, addSnapshotBeforeRestoreAfterEach } = require('./common'); const { toBytes32, - defaults: { DYNAMIC_FEE_ROUNDS }, + defaults: { EXCHANGE_DYNAMIC_FEE_ROUNDS }, } = require('../..'); const { toUnit, currentTime } = require('../utils')(); const { setExchangeFeeRateForSynths } = require('./helpers'); @@ -50,7 +50,7 @@ contract('SynthUtil', accounts => { addSnapshotBeforeRestoreAfterEach(); beforeEach(async () => { - for (let i = 0; i < DYNAMIC_FEE_ROUNDS; i++) { + for (let i = 0; i < EXCHANGE_DYNAMIC_FEE_ROUNDS; i++) { timestamp = await currentTime(); await exchangeRates.updateRates([sBTC, iBTC], ['5000', '5000'].map(toUnit), timestamp, { from: oracle, diff --git a/test/contracts/TradingRewards.spec.js b/test/contracts/TradingRewards.spec.js index 1a534b7426..69bf8fcf9e 100644 --- a/test/contracts/TradingRewards.spec.js +++ b/test/contracts/TradingRewards.spec.js @@ -6,7 +6,7 @@ const { currentTime, toUnit, multiplyDecimal } = require('../utils')(); const { setExchangeFeeRateForSynths, getDecodedLogs, decodedEventEqual } = require('./helpers'); const { toBytes32, - defaults: { DYNAMIC_FEE_ROUNDS }, + defaults: { EXCHANGE_DYNAMIC_FEE_ROUNDS }, } = require('../..'); /* @@ -105,7 +105,7 @@ contract('TradingRewards', accounts => { before('set exchange rates', async () => { const oracle = account1; let timestamp; - for (let i = 0; i < DYNAMIC_FEE_ROUNDS; i++) { + for (let i = 0; i < EXCHANGE_DYNAMIC_FEE_ROUNDS; i++) { timestamp = await currentTime(); await exchangeRates.updateRates([sETH, sBTC], Object.values(rates), timestamp, { diff --git a/test/contracts/helpers.js b/test/contracts/helpers.js index 38410c73e5..e08c4d7846 100644 --- a/test/contracts/helpers.js +++ b/test/contracts/helpers.js @@ -8,7 +8,7 @@ const { assert } = require('./common'); const { currentTime, toUnit } = require('../utils')(); const { toBytes32, - defaults: { DYNAMIC_FEE_ROUNDS }, + defaults: { EXCHANGE_DYNAMIC_FEE_ROUNDS }, constants: { ZERO_ADDRESS, ZERO_BYTES32 }, } = require('../..'); @@ -100,7 +100,7 @@ module.exports = { ].map(toBytes32); let timestamp; - for (let i = 0; i < DYNAMIC_FEE_ROUNDS; i++) { + for (let i = 0; i < EXCHANGE_DYNAMIC_FEE_ROUNDS; i++) { timestamp = await currentTime(); await exchangeRates.updateRates( diff --git a/test/integration/utils/rates.js b/test/integration/utils/rates.js index d6922706d7..09de49582e 100644 --- a/test/integration/utils/rates.js +++ b/test/integration/utils/rates.js @@ -2,7 +2,7 @@ const ethers = require('ethers'); const { setSystemSetting } = require('./settings'); const { toBytes32, - defaults: { DYNAMIC_FEE_ROUNDS }, + defaults: { EXCHANGE_DYNAMIC_FEE_ROUNDS }, } = require('../../..'); async function updateExchangeRatesIfNeeded({ ctx }) { @@ -99,7 +99,7 @@ async function _setNewRates({ ctx }) { const currencyKeys = await _getAvailableCurrencyKeys({ ctx }); const rates = await _getCurrentRates({ ctx, currencyKeys }); - for (let i = 0; i < DYNAMIC_FEE_ROUNDS; i++) { + for (let i = 0; i < EXCHANGE_DYNAMIC_FEE_ROUNDS; i++) { const { timestamp } = await ctx.provider.getBlock(); const tx = await ExchangeRates.updateRates(currencyKeys, rates, timestamp); From e1ec184888ac8b9282be34680682fd6daa94c51d Mon Sep 17 00:00:00 2001 From: Lecky Date: Thu, 2 Dec 2021 15:55:49 +1100 Subject: [PATCH 071/133] Fix(SystemSettings.js): Add missing max dynamic fee --- test/contracts/SystemSettings.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/test/contracts/SystemSettings.js b/test/contracts/SystemSettings.js index e1101da5b9..3408bf74b3 100644 --- a/test/contracts/SystemSettings.js +++ b/test/contracts/SystemSettings.js @@ -90,6 +90,7 @@ contract('SystemSettings', async accounts => { 'setExchangeDynamicFeeThreshold', 'setExchangeDynamicFeeWeightDecay', 'setExchangeDynamicFeeRounds', + 'setExchangeMaxDynamicFee', ], }); }); @@ -1578,7 +1579,7 @@ contract('SystemSettings', async accounts => { it('the owner can invoke and replace with emitted event', async () => { const txn = await systemSettings.setExchangeMaxDynamicFee(maxDynamicFee, { from: owner }); const actual = await systemSettings.exchangeMaxDynamicFee(); - assert.equal(actual, maxDynamicFee, 'Configured exchange max dynamic fee is set correctly'); + assert.bnEqual(actual, maxDynamicFee, 'Configured exchange max dynamic fee is set correctly'); assert.eventEqual(txn, 'ExchangeMaxDynamicFeeUpdated', [maxDynamicFee]); }); }); From 9c1c92c951ec4ed9468847bf0a1126d1a57a19a7 Mon Sep 17 00:00:00 2001 From: Lecky Date: Sat, 4 Dec 2021 17:53:42 +1100 Subject: [PATCH 072/133] Fixing publish test - Disable dynamic fee to neglect it in systemSettings --- test/publish/index.js | 3 +++ 1 file changed, 3 insertions(+) diff --git a/test/publish/index.js b/test/publish/index.js index b98bc6805e..cd63f26847 100644 --- a/test/publish/index.js +++ b/test/publish/index.js @@ -266,6 +266,9 @@ describe('publish scripts', () => { sBTCContract = getContract({ target: 'ProxysBTC', source: 'Synth' }); sETHContract = getContract({ target: 'ProxysETH', source: 'Synth' }); SystemSettings = getContract({ target: 'SystemSettings' }); + // Disable exchange dynamic fee so that we can neglect it + let tx = await SystemSettings.setExchangeDynamicFeeRounds('0', overrides); + await tx.wait(); Liquidations = getContract({ target: 'Liquidations' }); From 364ad77fc79b454ef46e8935768c8e3bd6595a41 Mon Sep 17 00:00:00 2001 From: Lecky Date: Sat, 4 Dec 2021 19:10:15 +1100 Subject: [PATCH 073/133] Fix publish test - change ConfigureSystemSettings to only change if zero --- publish/src/commands/deploy/configure-system-settings.js | 8 ++++---- test/publish/index.js | 9 ++++++++- 2 files changed, 12 insertions(+), 5 deletions(-) diff --git a/publish/src/commands/deploy/configure-system-settings.js b/publish/src/commands/deploy/configure-system-settings.js index 0a32096fe8..8213638635 100644 --- a/publish/src/commands/deploy/configure-system-settings.js +++ b/publish/src/commands/deploy/configure-system-settings.js @@ -350,7 +350,7 @@ module.exports = async ({ target: SystemSettings, read: 'exchangeDynamicFeeThreshold', readTarget: previousSystemSettings, - expected: input => input === exchangeDynamicFeeThreshold, // only change if non-default + expected: input => input !== '0', // only change if zero write: 'setExchangeDynamicFeeThreshold', writeArg: exchangeDynamicFeeThreshold, comment: 'Set exchange dynamic fee threshold (SIP-184)', @@ -363,7 +363,7 @@ module.exports = async ({ target: SystemSettings, read: 'exchangeDynamicFeeWeightDecay', readTarget: previousSystemSettings, - expected: input => input === exchangeDynamicFeeWeightDecay, // only change if non-default + expected: input => input !== '0', // only change if zero write: 'setExchangeDynamicFeeWeightDecay', writeArg: exchangeDynamicFeeWeightDecay, comment: 'Set exchange dynamic fee weight decay (SIP-184)', @@ -374,7 +374,7 @@ module.exports = async ({ target: SystemSettings, read: 'exchangeDynamicFeeRounds', readTarget: previousSystemSettings, - expected: input => input === exchangeDynamicFeeRounds, // only change if non-default + expected: input => input !== '0', // only change if zero write: 'setExchangeDynamicFeeRounds', writeArg: exchangeDynamicFeeRounds, comment: 'Set exchange dynamic fee rounds (SIP-184)', @@ -385,7 +385,7 @@ module.exports = async ({ target: SystemSettings, read: 'exchangeMaxDynamicFee', readTarget: previousSystemSettings, - expected: input => input === exchangeMaxDynamicFee, // only change if non-default + expected: input => input !== '0', // only change if zero write: 'setExchangeMaxDynamicFee', writeArg: exchangeMaxDynamicFee, comment: 'Set exchange max dynamic fee (SIP-184)', diff --git a/test/publish/index.js b/test/publish/index.js index cd63f26847..798992bac8 100644 --- a/test/publish/index.js +++ b/test/publish/index.js @@ -267,7 +267,8 @@ describe('publish scripts', () => { sETHContract = getContract({ target: 'ProxysETH', source: 'Synth' }); SystemSettings = getContract({ target: 'SystemSettings' }); // Disable exchange dynamic fee so that we can neglect it - let tx = await SystemSettings.setExchangeDynamicFeeRounds('0', overrides); + // Using 1 so that it wouldn't get override back to default by redeployment + const tx = await SystemSettings.setExchangeDynamicFeeRounds('1', overrides); await tx.wait(); Liquidations = getContract({ target: 'Liquidations' }); @@ -341,6 +342,7 @@ describe('publish scripts', () => { let newRateForsUSD; let newMinimumStakeTime; let newDebtSnapshotStaleTime; + let newExchangeDynamicFeeRounds; beforeEach(async () => { newWaitingPeriod = '10'; @@ -357,6 +359,7 @@ describe('publish scripts', () => { newRateForsUSD = ethers.utils.parseEther('0.1').toString(); newMinimumStakeTime = '3999'; newDebtSnapshotStaleTime = '43200'; // Half a day + newExchangeDynamicFeeRounds = '1'; let tx; @@ -474,6 +477,10 @@ describe('publish scripts', () => { newAtomicTwapWindow ); assert.strictEqual((await Issuer.minimumStakeTime()).toString(), newMinimumStakeTime); + assert.strictEqual( + (await SystemSettings.exchangeDynamicFeeRounds()).toString(), + newExchangeDynamicFeeRounds + ); assert.strictEqual( ( await Exchanger.feeRateForExchange(toBytes32('(ignored)'), toBytes32('sUSD')) From 93f0b67bf41a15a083863daf5f6eaeceeb3fe629 Mon Sep 17 00:00:00 2001 From: Lecky Date: Sat, 4 Dec 2021 20:36:13 +1100 Subject: [PATCH 074/133] Chainging EXCHANGE_DYNAMIC_FEE_ROUNDS on mainnet to 1 so it won't be reset --- publish/deployed/mainnet/params.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/publish/deployed/mainnet/params.json b/publish/deployed/mainnet/params.json index 55615d3800..99633d4367 100644 --- a/publish/deployed/mainnet/params.json +++ b/publish/deployed/mainnet/params.json @@ -1,7 +1,7 @@ [ { "name": "EXCHANGE_DYNAMIC_FEE_ROUNDS", - "value": "0" + "value": "1" }, { "name": "DEX_PRICE_AGGREGATOR", From ef2443166fadf4ed7a41b60c1eeb88d9580cb8e3 Mon Sep 17 00:00:00 2001 From: Lecky Date: Sun, 5 Dec 2021 16:30:28 +1100 Subject: [PATCH 075/133] Remove disable exchange fee in atomicExchange test --- contracts/SystemSettings.sol | 1 - test/contracts/Exchanger.spec.js | 2 -- 2 files changed, 3 deletions(-) diff --git a/contracts/SystemSettings.sol b/contracts/SystemSettings.sol index e746599f7b..3801d66bbe 100644 --- a/contracts/SystemSettings.sol +++ b/contracts/SystemSettings.sol @@ -2,7 +2,6 @@ pragma solidity ^0.5.16; // Inheritance import "./Owned.sol"; -import "./MixinResolver.sol"; import "./MixinSystemSettings.sol"; import "./interfaces/ISystemSettings.sol"; diff --git a/test/contracts/Exchanger.spec.js b/test/contracts/Exchanger.spec.js index c8b1a0b67d..b33f58c133 100644 --- a/test/contracts/Exchanger.spec.js +++ b/test/contracts/Exchanger.spec.js @@ -2553,8 +2553,6 @@ contract('Exchanger (spec tests)', async accounts => { const ethOnCL = toUnit('200'); // 1 over the ethOnDex beforeEach(async () => { - // Disable exchange dynamic fee on exchange atomically - await systemSettings.setExchangeDynamicFeeRounds('0', { from: owner }); // CL aggregator with past price data const aggregator = await MockAggregator.new({ from: owner }); await exchangeRates.addAggregator(sETH, aggregator.address, { from: owner }); From 3427ffd74be1d4646afa5cb019052657ff543288 Mon Sep 17 00:00:00 2001 From: Lecky Date: Sun, 5 Dec 2021 22:48:35 +1100 Subject: [PATCH 076/133] Adding test cases for exchange with dynamic fee --- test/contracts/Exchanger.spec.js | 45 ++++++++++++++++++++++++++++++-- 1 file changed, 43 insertions(+), 2 deletions(-) diff --git a/test/contracts/Exchanger.spec.js b/test/contracts/Exchanger.spec.js index b33f58c133..6a77044cfd 100644 --- a/test/contracts/Exchanger.spec.js +++ b/test/contracts/Exchanger.spec.js @@ -574,6 +574,47 @@ contract('Exchanger (spec tests)', async accounts => { assert.bnEqual(amountReceived, effectiveValue.sub(tripleFee)); }); }); + describe('when sBTC price rate spike', () => { + let sUSDsBTCFee; + let sBTCBalance; + let sBTCsUSDFee; + beforeEach(async () => { + timestamp = await currentTime(); + await exchangeRates.updateRates([sETH, sBTC], ['110', '5100'].map(toUnit), timestamp, { + from: oracle, + }); + }); + describe('and swap sUSD to sBTC', () => { + let destinationFee; + beforeEach(async () => { + console.log('amountIssued', amountIssued.toString()); + await synthetix.exchange(sUSD, amountIssued, sBTC, { from: account1 }); + const { fee } = await exchanger.getAmountsForExchange(amountIssued, sUSD, sBTC); + destinationFee = fee; + }); + it('then destination fee is greater as included dynamic fee in destination currency', async () => { + const effectiveValue = await exchangeRates.effectiveValue(sUSD, amountIssued, sBTC); + sUSDsBTCFee = exchangeFeeIncurred(effectiveValue, bipsCrypto); + assert.bnGt(destinationFee, sUSDsBTCFee); + }); + it('then swap sBTC to sETH and fee is more than sUSD to sBTC as dynamic fee in both direction', async () => { + await fastForward(await systemSettings.waitingPeriodSecs()); + sBTCBalance = await sBTCContract.balanceOf(account1); + await synthetix.exchange(sBTC, sBTCBalance, sETH, { from: account1 }); + const { fee } = await exchanger.getAmountsForExchange(sBTCBalance, sBTC, sETH); + assert.bnGt(fee, destinationFee); + }); + it('then swap sBTC to sUSD and fee is greater as included dynamic fee in source currency', async () => { + await fastForward(await systemSettings.waitingPeriodSecs()); + sBTCBalance = await sBTCContract.balanceOf(account1); + await synthetix.exchange(sBTC, sBTCBalance, sUSD, { from: account1 }); + const { fee } = await exchanger.getAmountsForExchange(sBTCBalance, sBTC, sUSD); + const effectiveValue = await exchangeRates.effectiveValue(sBTC, sBTCBalance, sUSD); + sBTCsUSDFee = exchangeFeeIncurred(effectiveValue, bipsCrypto); + assert.bnGt(fee, sBTCsUSDFee); + }); + }); + }); }); }); }; @@ -2556,7 +2597,7 @@ contract('Exchanger (spec tests)', async accounts => { // CL aggregator with past price data const aggregator = await MockAggregator.new({ from: owner }); await exchangeRates.addAggregator(sETH, aggregator.address, { from: owner }); - // set prices with no valatility + // set prices with no volatility // Need to start from round 11 as the first 10 rounds are used for the setup await aggregator.setLatestAnswerWithRound( ethOnCL, @@ -3193,7 +3234,7 @@ contract('Exchanger (spec tests)', async accounts => { describe('settlement ignores deviations', () => { describe('when a user exchange 100 sUSD into sETH', () => { beforeEach(async () => { - // Disable Dynamic Fee by setting rounds to 0 + // Disable Dynamic Fee in settlement by setting rounds to 0 await systemSettings.setExchangeDynamicFeeRounds('0', { from: owner }); await synthetix.exchange(sUSD, toUnit('100'), sETH, { from: account1 }); }); From 59e3e7f4ce3f960c71979da6dbc9c2749524b7db Mon Sep 17 00:00:00 2001 From: Lecky Date: Mon, 6 Dec 2021 14:30:25 +1100 Subject: [PATCH 077/133] Rename effectiveValueAndRatesAtRound to mutativeEffectiveValueAndRatesAtRound --- contracts/ExchangeRates.sol | 2 +- contracts/Exchanger.sol | 3 ++- contracts/interfaces/IExchangeRates.sol | 2 +- test/contracts/ExchangeRates.js | 2 +- test/contracts/ExchangerWithFeeRecAlternatives.behaviors.js | 2 +- 5 files changed, 6 insertions(+), 5 deletions(-) diff --git a/contracts/ExchangeRates.sol b/contracts/ExchangeRates.sol index 033f1047e0..6bb1df2b4f 100644 --- a/contracts/ExchangeRates.sol +++ b/contracts/ExchangeRates.sol @@ -132,7 +132,7 @@ contract ExchangeRates is Owned, MixinSystemSettings, IExchangeRates { /// @return value of the target currency. /// @return rate of the source currency. /// @return rate of the destination currency. - function effectiveValueAndRatesAtRound( + function mutativeEffectiveValueAndRatesAtRound( bytes32 sourceCurrencyKey, uint sourceAmount, bytes32 destinationCurrencyKey, diff --git a/contracts/Exchanger.sol b/contracts/Exchanger.sol index 1f3168a021..0aee889225 100644 --- a/contracts/Exchanger.sol +++ b/contracts/Exchanger.sol @@ -442,7 +442,8 @@ contract Exchanger is Owned, MixinSystemSettings, IExchanger { entry.roundIdForDest = exchangeRates().getCurrentRoundId(destinationCurrencyKey); // Puting the exchangeRate call first as it's mutative for cheap cache reading later on - (entry.destinationAmount, entry.sourceRate, entry.destinationRate) = exchangeRates().effectiveValueAndRatesAtRound( + (entry.destinationAmount, entry.sourceRate, entry.destinationRate) = exchangeRates() + .mutativeEffectiveValueAndRatesAtRound( sourceCurrencyKey, sourceAmount, destinationCurrencyKey, diff --git a/contracts/interfaces/IExchangeRates.sol b/contracts/interfaces/IExchangeRates.sol index b26391f985..5ed0ffdf2c 100644 --- a/contracts/interfaces/IExchangeRates.sol +++ b/contracts/interfaces/IExchangeRates.sol @@ -60,7 +60,7 @@ interface IExchangeRates { uint roundIdForDest ) external view returns (uint value); - function effectiveValueAndRatesAtRound( + function mutativeEffectiveValueAndRatesAtRound( bytes32 sourceCurrencyKey, uint sourceAmount, bytes32 destinationCurrencyKey, diff --git a/test/contracts/ExchangeRates.js b/test/contracts/ExchangeRates.js index b358a7e52c..87ca3afd35 100644 --- a/test/contracts/ExchangeRates.js +++ b/test/contracts/ExchangeRates.js @@ -85,7 +85,7 @@ contract('Exchange Rates', async accounts => { 'removeAggregator', 'setOracle', 'updateRates', - 'effectiveValueAndRatesAtRound', + 'mutativeEffectiveValueAndRatesAtRound', ]; const withDexPricingFunctions = baseFunctions.concat(['setDexPriceAggregator']); diff --git a/test/contracts/ExchangerWithFeeRecAlternatives.behaviors.js b/test/contracts/ExchangerWithFeeRecAlternatives.behaviors.js index 6fc2e768ea..c8c6407149 100644 --- a/test/contracts/ExchangerWithFeeRecAlternatives.behaviors.js +++ b/test/contracts/ExchangerWithFeeRecAlternatives.behaviors.js @@ -168,7 +168,7 @@ module.exports = function({ accounts }) { whenMockedEffectiveRateAsEqualAtRound: cb => { describe(`when mocked with exchange rates at round giving an effective value of 1:1`, () => { beforeEach(async () => { - this.mocks.ExchangeRates.smocked.effectiveValueAndRatesAtRound.will.return.with( + this.mocks.ExchangeRates.smocked.mutativeEffectiveValueAndRatesAtRound.will.return.with( (srcKey, amount, destKey) => [amount, (1e18).toString(), (1e18).toString()] ); }); From d0ccd745c302d030a74a6b834cb40a4a7a23781c Mon Sep 17 00:00:00 2001 From: Lecky Date: Mon, 6 Dec 2021 14:39:29 +1100 Subject: [PATCH 078/133] Revert SafeDecimalMath.sol changes --- contracts/SafeDecimalMath.sol | 17 +++++------------ 1 file changed, 5 insertions(+), 12 deletions(-) diff --git a/contracts/SafeDecimalMath.sol b/contracts/SafeDecimalMath.sol index a1516221ad..b50326d62a 100644 --- a/contracts/SafeDecimalMath.sol +++ b/contracts/SafeDecimalMath.sol @@ -8,37 +8,30 @@ library SafeDecimalMath { using SafeMath for uint; /* Number of decimal places in the representations. */ - uint8 public constant DECIMALS = 18; + uint8 public constant decimals = 18; uint8 public constant highPrecisionDecimals = 27; /* The number representing 1.0. */ - uint public constant UNIT = 10**uint(DECIMALS); + uint public constant UNIT = 10**uint(decimals); /* The number representing 1.0 for higher fidelity numbers. */ uint public constant PRECISE_UNIT = 10**uint(highPrecisionDecimals); - uint private constant UNIT_TO_HIGH_PRECISION_CONVERSION_FACTOR = 10**uint(highPrecisionDecimals - DECIMALS); + uint private constant UNIT_TO_HIGH_PRECISION_CONVERSION_FACTOR = 10**uint(highPrecisionDecimals - decimals); /** - * @notice Provides an interface to UNIT. + * @return Provides an interface to UNIT. */ function unit() external pure returns (uint) { return UNIT; } /** - * @notice Provides an interface to PRECISE_UNIT. + * @return Provides an interface to PRECISE_UNIT. */ function preciseUnit() external pure returns (uint) { return PRECISE_UNIT; } - /** - * @notice Provides an interface to decimals. - */ - function decimals() external pure returns (uint8) { - return DECIMALS; - } - /** * @return The result of multiplying x and y, interpreting the operands as fixed-point * decimals. From 77eed96e0d10fefb50da6bbb17c602bbba5e1114 Mon Sep 17 00:00:00 2001 From: Lecky Date: Mon, 6 Dec 2021 14:51:24 +1100 Subject: [PATCH 079/133] Revert "Revert SafeDecimalMath.sol changes" This reverts commit d0ccd745c302d030a74a6b834cb40a4a7a23781c. --- contracts/SafeDecimalMath.sol | 17 ++++++++++++----- 1 file changed, 12 insertions(+), 5 deletions(-) diff --git a/contracts/SafeDecimalMath.sol b/contracts/SafeDecimalMath.sol index b50326d62a..a1516221ad 100644 --- a/contracts/SafeDecimalMath.sol +++ b/contracts/SafeDecimalMath.sol @@ -8,30 +8,37 @@ library SafeDecimalMath { using SafeMath for uint; /* Number of decimal places in the representations. */ - uint8 public constant decimals = 18; + uint8 public constant DECIMALS = 18; uint8 public constant highPrecisionDecimals = 27; /* The number representing 1.0. */ - uint public constant UNIT = 10**uint(decimals); + uint public constant UNIT = 10**uint(DECIMALS); /* The number representing 1.0 for higher fidelity numbers. */ uint public constant PRECISE_UNIT = 10**uint(highPrecisionDecimals); - uint private constant UNIT_TO_HIGH_PRECISION_CONVERSION_FACTOR = 10**uint(highPrecisionDecimals - decimals); + uint private constant UNIT_TO_HIGH_PRECISION_CONVERSION_FACTOR = 10**uint(highPrecisionDecimals - DECIMALS); /** - * @return Provides an interface to UNIT. + * @notice Provides an interface to UNIT. */ function unit() external pure returns (uint) { return UNIT; } /** - * @return Provides an interface to PRECISE_UNIT. + * @notice Provides an interface to PRECISE_UNIT. */ function preciseUnit() external pure returns (uint) { return PRECISE_UNIT; } + /** + * @notice Provides an interface to decimals. + */ + function decimals() external pure returns (uint8) { + return DECIMALS; + } + /** * @return The result of multiplying x and y, interpreting the operands as fixed-point * decimals. From f75031a21a6e516d13b1491a02e611be80fd7478 Mon Sep 17 00:00:00 2001 From: Lecky Date: Mon, 6 Dec 2021 14:53:13 +1100 Subject: [PATCH 080/133] Adding SafeDecimalMath for releases --- publish/releases.json | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/publish/releases.json b/publish/releases.json index 13953fe908..7406cb13f8 100644 --- a/publish/releases.json +++ b/publish/releases.json @@ -405,7 +405,13 @@ { "sip": 184, "layer": "both", - "sources": ["Exchanger", "ExchangeRates", "SystemSettings", "DynamicFee"] + "sources": [ + "Exchanger", + "ExchangeRates", + "SystemSettings", + "DynamicFee", + "SafeDecimalMath" + ] }, { "sip": 187, From 0a10dbc9b14f8ff54f263cfe794e413bc5370693 Mon Sep 17 00:00:00 2001 From: Lecky Date: Mon, 6 Dec 2021 22:54:18 +1100 Subject: [PATCH 081/133] Revert "Adding SafeDecimalMath for releases" This reverts commit f75031a21a6e516d13b1491a02e611be80fd7478. --- publish/releases.json | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/publish/releases.json b/publish/releases.json index 7406cb13f8..13953fe908 100644 --- a/publish/releases.json +++ b/publish/releases.json @@ -405,13 +405,7 @@ { "sip": 184, "layer": "both", - "sources": [ - "Exchanger", - "ExchangeRates", - "SystemSettings", - "DynamicFee", - "SafeDecimalMath" - ] + "sources": ["Exchanger", "ExchangeRates", "SystemSettings", "DynamicFee"] }, { "sip": 187, From 6d04ba1486bf64d0d42d1d494c65e6721e053e26 Mon Sep 17 00:00:00 2001 From: Lecky Date: Mon, 6 Dec 2021 22:54:43 +1100 Subject: [PATCH 082/133] Revert "Revert "Revert SafeDecimalMath.sol changes"" This reverts commit 77eed96e0d10fefb50da6bbb17c602bbba5e1114. --- contracts/SafeDecimalMath.sol | 17 +++++------------ 1 file changed, 5 insertions(+), 12 deletions(-) diff --git a/contracts/SafeDecimalMath.sol b/contracts/SafeDecimalMath.sol index a1516221ad..b50326d62a 100644 --- a/contracts/SafeDecimalMath.sol +++ b/contracts/SafeDecimalMath.sol @@ -8,37 +8,30 @@ library SafeDecimalMath { using SafeMath for uint; /* Number of decimal places in the representations. */ - uint8 public constant DECIMALS = 18; + uint8 public constant decimals = 18; uint8 public constant highPrecisionDecimals = 27; /* The number representing 1.0. */ - uint public constant UNIT = 10**uint(DECIMALS); + uint public constant UNIT = 10**uint(decimals); /* The number representing 1.0 for higher fidelity numbers. */ uint public constant PRECISE_UNIT = 10**uint(highPrecisionDecimals); - uint private constant UNIT_TO_HIGH_PRECISION_CONVERSION_FACTOR = 10**uint(highPrecisionDecimals - DECIMALS); + uint private constant UNIT_TO_HIGH_PRECISION_CONVERSION_FACTOR = 10**uint(highPrecisionDecimals - decimals); /** - * @notice Provides an interface to UNIT. + * @return Provides an interface to UNIT. */ function unit() external pure returns (uint) { return UNIT; } /** - * @notice Provides an interface to PRECISE_UNIT. + * @return Provides an interface to PRECISE_UNIT. */ function preciseUnit() external pure returns (uint) { return PRECISE_UNIT; } - /** - * @notice Provides an interface to decimals. - */ - function decimals() external pure returns (uint8) { - return DECIMALS; - } - /** * @return The result of multiplying x and y, interpreting the operands as fixed-point * decimals. From 671f019ffe0e5c6c92ce22fc815558eacd44d253 Mon Sep 17 00:00:00 2001 From: Lecky Date: Mon, 6 Dec 2021 23:02:44 +1100 Subject: [PATCH 083/133] Fix TestableDynamicFee to use SafeDecimalMath.unit --- contracts/test-helpers/TestableDynamicFee.sol | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/contracts/test-helpers/TestableDynamicFee.sol b/contracts/test-helpers/TestableDynamicFee.sol index d55b07ba10..bb88adca89 100644 --- a/contracts/test-helpers/TestableDynamicFee.sol +++ b/contracts/test-helpers/TestableDynamicFee.sol @@ -4,8 +4,8 @@ pragma solidity ^0.5.16; import "../DynamicFee.sol"; contract TestableDynamicFee { - uint public threshold = 4 * 10**uint(SafeDecimalMath.decimals() - 3); - uint public weightDecay = 9 * 10**uint(SafeDecimalMath.decimals() - 1); + uint public threshold = (4 * SafeDecimalMath.unit()) / 1000; + uint public weightDecay = (9 * SafeDecimalMath.unit()) / 10; function testGetPriceDifferential(uint price, uint previousPrice) external view returns (uint) { return DynamicFee.getPriceDifferential(price, previousPrice, threshold); From 6b6c1e81e9ccde5be655c0fb5efa16c1bab66ebe Mon Sep 17 00:00:00 2001 From: Lecky Date: Thu, 9 Dec 2021 11:52:26 +1100 Subject: [PATCH 084/133] Fixing Mark's comment: - remove unnecesary comments - remove unnecesary changes --- contracts/ExchangeRates.sol | 16 ++++++++-------- contracts/ExchangeRatesWithDexPricing.sol | 4 ++-- contracts/Exchanger.sol | 19 ++++++++----------- contracts/ExchangerWithFeeRecAlternatives.sol | 4 ++-- contracts/SystemSettings.sol | 5 +---- test/contracts/BaseSynthetix.js | 14 ++++++++++---- test/contracts/TestableDynamicFee.js | 12 ++++++------ 7 files changed, 37 insertions(+), 37 deletions(-) diff --git a/contracts/ExchangeRates.sol b/contracts/ExchangeRates.sol index 6bb1df2b4f..ac0902db53 100644 --- a/contracts/ExchangeRates.sol +++ b/contracts/ExchangeRates.sol @@ -147,7 +147,7 @@ contract ExchangeRates is Owned, MixinSystemSettings, IExchangeRates { ) { uint time; - (sourceRate, time) = _getRateAndUpdatedTimeAtRound(sourceCurrencyKey, roundIdForSrc); + (sourceRate, time) = _getRateAndTimestampAtRound(sourceCurrencyKey, roundIdForSrc); // cacheing to save external call _setRate(sourceCurrencyKey, roundIdForSrc, sourceRate, time); // If there's no change in the currency, then just return the amount they gave us @@ -155,7 +155,7 @@ contract ExchangeRates is Owned, MixinSystemSettings, IExchangeRates { destinationRate = sourceRate; value = sourceAmount; } else { - (destinationRate, time) = _getRateAndUpdatedTimeAtRound(destinationCurrencyKey, roundIdForDest); + (destinationRate, time) = _getRateAndTimestampAtRound(destinationCurrencyKey, roundIdForDest); // cacheing to save external call _setRate(destinationCurrencyKey, roundIdForDest, destinationRate, time); // prevent divide-by 0 error (this happens if the dest is not a valid rate) @@ -201,7 +201,7 @@ contract ExchangeRates is Owned, MixinSystemSettings, IExchangeRates { uint roundId = startingRoundId; uint nextTimestamp = 0; while (true) { - (, nextTimestamp) = _getRateAndUpdatedTimeAtRound(currencyKey, roundId + 1); + (, nextTimestamp) = _getRateAndTimestampAtRound(currencyKey, roundId + 1); // if there's no new round, then the previous roundId was the latest if (nextTimestamp == 0 || nextTimestamp > startingTimestamp + timediff) { return roundId; @@ -225,8 +225,8 @@ contract ExchangeRates is Owned, MixinSystemSettings, IExchangeRates { // If there's no change in the currency, then just return the amount they gave us if (sourceCurrencyKey == destinationCurrencyKey) return sourceAmount; - (uint srcRate, ) = _getRateAndUpdatedTimeAtRound(sourceCurrencyKey, roundIdForSrc); - (uint destRate, ) = _getRateAndUpdatedTimeAtRound(destinationCurrencyKey, roundIdForDest); + (uint srcRate, ) = _getRateAndTimestampAtRound(sourceCurrencyKey, roundIdForSrc); + (uint destRate, ) = _getRateAndTimestampAtRound(destinationCurrencyKey, roundIdForDest); if (destRate == 0) { // prevent divide-by 0 error (this can happen when roundIDs jump epochs due // to aggregator upgrades) @@ -237,7 +237,7 @@ contract ExchangeRates is Owned, MixinSystemSettings, IExchangeRates { } function rateAndTimestampAtRound(bytes32 currencyKey, uint roundId) external view returns (uint rate, uint time) { - return _getRateAndUpdatedTimeAtRound(currencyKey, roundId); + return _getRateAndTimestampAtRound(currencyKey, roundId); } function lastRateUpdateTimes(bytes32 currencyKey) external view returns (uint256) { @@ -317,7 +317,7 @@ contract ExchangeRates is Owned, MixinSystemSettings, IExchangeRates { for (uint i = 0; i < numRounds; i++) { // fetch the rate and treat is as current, so inverse limits if frozen will always be applied // regardless of current rate - (rates[i], times[i]) = _getRateAndUpdatedTimeAtRound(currencyKey, roundId); + (rates[i], times[i]) = _getRateAndTimestampAtRound(currencyKey, roundId); if (roundId == 0) { // if we hit the last round, then return what we have @@ -543,7 +543,7 @@ contract ExchangeRates is Owned, MixinSystemSettings, IExchangeRates { } } - function _getRateAndUpdatedTimeAtRound(bytes32 currencyKey, uint roundId) internal view returns (uint rate, uint time) { + function _getRateAndTimestampAtRound(bytes32 currencyKey, uint roundId) internal view returns (uint rate, uint time) { AggregatorV2V3Interface aggregator = aggregators[currencyKey]; RateAndUpdatedTime memory update = _rates[currencyKey][roundId]; diff --git a/contracts/ExchangeRatesWithDexPricing.sol b/contracts/ExchangeRatesWithDexPricing.sol index 14516482f1..1548a61030 100644 --- a/contracts/ExchangeRatesWithDexPricing.sol +++ b/contracts/ExchangeRatesWithDexPricing.sol @@ -142,8 +142,8 @@ contract ExchangeRatesWithDexPricing is ExchangeRates { // updates is a good proxy for price volatility. uint considerationWindowStart = block.timestamp.sub(considerationWindow); uint roundId = _getCurrentRoundId(currencyKey); - for (; updateThreshold > 0; updateThreshold--) { - (uint rate, uint time) = _getRateAndUpdatedTimeAtRound(currencyKey, roundId); + for (updateThreshold; updateThreshold > 0; updateThreshold--) { + (uint rate, uint time) = _getRateAndTimestampAtRound(currencyKey, roundId); if (time != 0 && time < considerationWindowStart) { // Round was outside consideration window so we can stop querying further rounds return false; diff --git a/contracts/Exchanger.sol b/contracts/Exchanger.sol index 0aee889225..6f308e219f 100644 --- a/contracts/Exchanger.sol +++ b/contracts/Exchanger.sol @@ -399,7 +399,7 @@ contract Exchanger is Owned, MixinSystemSettings, IExchanger { rates[2] = SafeDecimalMath.unit(); } - // Note: that exchanges can't invalidate the debt cache, since if a rate is invalid, + // Note that exchanges can't invalidate the debt cache, since if a rate is invalid, // the exchange will have failed already. debtCache().updateCachedSynthDebtsWithRates(keys, rates); } @@ -470,10 +470,7 @@ contract Exchanger is Owned, MixinSystemSettings, IExchanger { return (0, 0, IVirtualSynth(0)); } - (entry.exchangeFeeRate, entry.exchangeDynamicFeeRate) = _feeRateForExchange( - sourceCurrencyKey, - destinationCurrencyKey - ); + entry.exchangeFeeRate = _feeRateForExchange(sourceCurrencyKey, destinationCurrencyKey); amountReceived = _deductFeesFromAmount(entry.destinationAmount, entry.exchangeFeeRate); // Note: `fee` is denominated in the destinationCurrencyKey. @@ -756,7 +753,7 @@ contract Exchanger is Owned, MixinSystemSettings, IExchanger { view returns (uint exchangeFeeRate) { - (exchangeFeeRate, ) = _feeRateForExchange(sourceCurrencyKey, destinationCurrencyKey); + exchangeFeeRate = _feeRateForExchange(sourceCurrencyKey, destinationCurrencyKey); } /// @notice Calculate the exchange fee for a given source and destination currency key @@ -767,7 +764,7 @@ contract Exchanger is Owned, MixinSystemSettings, IExchanger { function _feeRateForExchange(bytes32 sourceCurrencyKey, bytes32 destinationCurrencyKey) internal view - returns (uint exchangeFeeRate, uint exchangeDynamicFeeRate) + returns (uint exchangeFeeRate) { // Get the exchange fee rate as per destination currencyKey uint baseRate = getExchangeFeeRate(destinationCurrencyKey); @@ -778,15 +775,15 @@ contract Exchanger is Owned, MixinSystemSettings, IExchanger { uint exchangeFeeRate, bytes32 sourceCurrencyKey, bytes32 destinationCurrencyKey - ) internal view returns (uint, uint exchangeDynamicFeeRate) { - exchangeDynamicFeeRate = _getDynamicFeeForExchange(destinationCurrencyKey); + ) internal view returns (uint) { + uint exchangeDynamicFeeRate = _getDynamicFeeForExchange(destinationCurrencyKey); exchangeDynamicFeeRate = exchangeDynamicFeeRate.add(_getDynamicFeeForExchange(sourceCurrencyKey)); uint maxDynamicFee = getExchangeMaxDynamicFee(); exchangeFeeRate = exchangeFeeRate.add(exchangeDynamicFeeRate); // Cap to max exchange dynamic fee exchangeFeeRate = exchangeFeeRate > maxDynamicFee ? maxDynamicFee : exchangeFeeRate; - return (exchangeFeeRate, exchangeDynamicFeeRate); + return exchangeFeeRate; } /// @notice Get dynamic fee for a given currency key (SIP-184) @@ -838,7 +835,7 @@ contract Exchanger is Owned, MixinSystemSettings, IExchanger { uint exchangeFeeRate ) { - (exchangeFeeRate, ) = _feeRateForExchange(sourceCurrencyKey, destinationCurrencyKey); + exchangeFeeRate = _feeRateForExchange(sourceCurrencyKey, destinationCurrencyKey); uint destinationAmount; uint destinationRate; diff --git a/contracts/ExchangerWithFeeRecAlternatives.sol b/contracts/ExchangerWithFeeRecAlternatives.sol index 50e17bcbcd..b6e767a958 100644 --- a/contracts/ExchangerWithFeeRecAlternatives.sol +++ b/contracts/ExchangerWithFeeRecAlternatives.sol @@ -270,7 +270,7 @@ contract ExchangerWithFeeRecAlternatives is MinimalProxyFactory, Exchanger { function _feeRateForAtomicExchange(bytes32 sourceCurrencyKey, bytes32 destinationCurrencyKey) internal view - returns (uint feeRate) + returns (uint) { // Get the exchange fee rate as per destination currencyKey uint baseRate = getAtomicExchangeFeeRate(destinationCurrencyKey); @@ -279,7 +279,7 @@ contract ExchangerWithFeeRecAlternatives is MinimalProxyFactory, Exchanger { baseRate = getExchangeFeeRate(destinationCurrencyKey); } - (feeRate, ) = _calculateFeeRateFromExchangeSynths(baseRate, sourceCurrencyKey, destinationCurrencyKey); + return _calculateFeeRateFromExchangeSynths(baseRate, sourceCurrencyKey, destinationCurrencyKey); } function _getAmountsForAtomicExchangeMinusFees( diff --git a/contracts/SystemSettings.sol b/contracts/SystemSettings.sol index 3801d66bbe..47566410e5 100644 --- a/contracts/SystemSettings.sol +++ b/contracts/SystemSettings.sol @@ -408,13 +408,10 @@ contract SystemSettings is Owned, MixinSystemSettings, ISystemSettings { emit ExchangeDynamicFeeWeightDecayUpdated(weightDecay); } - /// @notice Set exchange dynamic fee last N rounds constant default to 10 + /// @notice Set exchange dynamic fee last N rounds /// @param rounds The exchange dynamic fee last N rounds /// @return uint dynamic fee last N rounds function setExchangeDynamicFeeRounds(uint rounds) external onlyOwner { - // Allowing to be 0 as a flag to disable Dynamic Fee - // require(rounds != 0, "rounds cannot be 0"); - flexibleStorage().setUIntValue(SETTING_CONTRACT_NAME, SETTING_EXCHANGE_DYNAMIC_FEE_ROUNDS, rounds); emit ExchangeDynamicFeeRoundsUpdated(rounds); diff --git a/test/contracts/BaseSynthetix.js b/test/contracts/BaseSynthetix.js index 744cdc9e01..3d813fb13d 100644 --- a/test/contracts/BaseSynthetix.js +++ b/test/contracts/BaseSynthetix.js @@ -854,9 +854,12 @@ contract('BaseSynthetix', async accounts => { }); it("should lock newly received synthetix if the user's collaterisation is too high", async () => { + // Disable Dynamic fee so that we can neglect it. + await systemSettings.setExchangeDynamicFeeRounds('0', { from: owner }); + // Set sEUR for purposes of this test const timestamp1 = await currentTime(); - await exchangeRates.updateRates([sEUR], [toUnit('1')], timestamp1, { from: oracle }); + await exchangeRates.updateRates([sEUR], [toUnit('0.75')], timestamp1, { from: oracle }); await debtCache.takeDebtSnapshot(); const issuedSynthetixs = web3.utils.toBN('200000'); @@ -896,12 +899,15 @@ contract('BaseSynthetix', async accounts => { }); it('should unlock synthetix when collaterisation ratio changes', async () => { + // Disable Dynamic fee so that we can neglect it. + await systemSettings.setExchangeDynamicFeeRounds('0', { from: owner }); + // prevent circuit breaker from firing by upping the threshold to factor 5 await systemSettings.setPriceDeviationThresholdFactor(toUnit('5'), { from: owner }); // Set sAUD for purposes of this test const timestamp1 = await currentTime(); - const aud2usdrate = toUnit('1'); + const aud2usdrate = toUnit('2'); await exchangeRates.updateRates([sAUD], [aud2usdrate], timestamp1, { from: oracle }); await debtCache.takeDebtSnapshot(); @@ -926,12 +932,12 @@ contract('BaseSynthetix', async accounts => { // Increase the value of sAUD relative to synthetix const timestamp2 = await currentTime(); - const newAUDExchangeRate = toUnit('0.75'); + const newAUDExchangeRate = toUnit('1'); await exchangeRates.updateRates([sAUD], [newAUDExchangeRate], timestamp2, { from: oracle }); await debtCache.takeDebtSnapshot(); const transferable2 = await baseSynthetix.transferableSynthetix(account1); - assert.equal(transferable2.gt(toUnit('100')), true); + assert.equal(transferable2.gt(toUnit('1000')), true); }); describe('when the user has issued some sUSD and exchanged for other synths', () => { diff --git a/test/contracts/TestableDynamicFee.js b/test/contracts/TestableDynamicFee.js index b6b5c52a99..b040a13436 100644 --- a/test/contracts/TestableDynamicFee.js +++ b/test/contracts/TestableDynamicFee.js @@ -5,7 +5,7 @@ const SafeDecimalMath = artifacts.require('SafeDecimalMath'); const DynamicFee = artifacts.require('DynamicFee'); const TestableDynamicFee = artifacts.require('TestableDynamicFee'); -contract('TestableDynamicFee', () => { +contract('DynamicFee', () => { let testableDynamicFee; before(async () => { @@ -38,7 +38,7 @@ contract('TestableDynamicFee', () => { assert.bnEqual(priceWeight2, toUnit('0.81')); }); - it('Can get dynamic fee from dynamic-fee-calc.csv round 13-22, all below threshold', async () => { + it('Can get dynamic fee according to SIP feasibility spreadsheet round 13-22, all below threshold', async () => { const prices = [ toUnit('49535.05178912'), toUnit('49714.05205647'), @@ -57,7 +57,7 @@ contract('TestableDynamicFee', () => { assert.bnEqual(dynamicFee, '0'); }); - it('Can get dynamic fee from dynamic-fee-calc.csv round 14-23, last one above threshold', async () => { + it('Can get dynamic fee according to SIP feasibility spreadsheet round 14-23, last one above threshold', async () => { const prices = [ toUnit('49234.65005734'), toUnit('49535.05178912'), @@ -75,7 +75,7 @@ contract('TestableDynamicFee', () => { assert.bnEqual(dynamicFee, '2064427530203592'); }); - it('Can get dynamic fee from dynamic-fee-calc.csv round 23-32, first one above threshold', async () => { + it('Can get dynamic fee according to SIP feasibility spreadsheet round 23-32, first one above threshold', async () => { const prices = [ toUnit('49198.77'), toUnit('49143.5399999999'), @@ -93,7 +93,7 @@ contract('TestableDynamicFee', () => { assert.bnEqual(dynamicFee, '799801523256537'); }); - it('Can get dynamic fee from dynamic-fee-calc.csv round 63-72, 70% above threshold', async () => { + it('Can get dynamic fee according to SIP feasibility spreadsheet round 63-72, 70% above threshold', async () => { const prices = [ toUnit('44661.70868763'), toUnit('44672.6561639399'), @@ -112,7 +112,7 @@ contract('TestableDynamicFee', () => { assert.bnEqual(dynamicFee, '18366333809739328'); }); - it('Can get the same dynamic fee from dynamic-fee-calc.csv round 58-67, 50% above threshold', async () => { + it('Can get the same dynamic fee according to SIP feasibility spreadsheet round 58-67, 50% above threshold', async () => { const prices = [ toUnit('46183.17440371'), toUnit('46217.7336139799'), From c458dafbed54273c62ed0cfe118011d250ab5e2c Mon Sep 17 00:00:00 2001 From: Lecky Date: Thu, 9 Dec 2021 15:07:38 +1100 Subject: [PATCH 085/133] Adding hardhat node config allowUnlimitedContractSize to false --- hardhat/tasks/task-node.js | 1 + 1 file changed, 1 insertion(+) diff --git a/hardhat/tasks/task-node.js b/hardhat/tasks/task-node.js index 42dc834ca1..c0e0286abf 100644 --- a/hardhat/tasks/task-node.js +++ b/hardhat/tasks/task-node.js @@ -19,6 +19,7 @@ task('node', 'Run a node') const network = taskArguments.targetNetwork; if (network !== 'local') { if (network === 'mainnet') { + hre.config.networks.hardhat.allowUnlimitedContractSize = false; taskArguments.fork = process.env.PROVIDER_URL_MAINNET; } taskArguments.fork = From 18c006e9138be0fcae4ddb5a726e78aead1b3635 Mon Sep 17 00:00:00 2001 From: Lecky Date: Thu, 9 Dec 2021 21:05:52 +1100 Subject: [PATCH 086/133] Fixing comments - Adding dynamic-fee-calc.csv - Refactor getExchangeDynamicFeeData to be one flexibleStorage call - Remove default value in comment - Adding require non zero value check --- contracts/MixinSystemSettings.sol | 12 ++- contracts/SystemSettings.sol | 6 +- test/dynamic-fee-calc.csv | 124 ++++++++++++++++++++++++++++++ 3 files changed, 137 insertions(+), 5 deletions(-) create mode 100644 test/dynamic-fee-calc.csv diff --git a/contracts/MixinSystemSettings.sol b/contracts/MixinSystemSettings.sol index 3e891e4a57..1476cee4c4 100644 --- a/contracts/MixinSystemSettings.sol +++ b/contracts/MixinSystemSettings.sol @@ -168,9 +168,15 @@ contract MixinSystemSettings is MixinResolver { uint rounds ) { - threshold = getExchangeDynamicFeeThreshold(); - weightDecay = getExchangeDynamicFeeWeightDecay(); - rounds = getExchangeDynamicFeeRounds(); + bytes32[] memory data = new bytes32[](3); + uint[] memory result = new uint[](3); + data[0] = SETTING_EXCHANGE_DYNAMIC_FEE_THRESHOLD; + data[1] = SETTING_EXCHANGE_DYNAMIC_FEE_WEIGHT_DECAY; + data[2] = SETTING_EXCHANGE_DYNAMIC_FEE_ROUNDS; + result = flexibleStorage().getUIntValues(SETTING_CONTRACT_NAME, data); + threshold = result[0]; + weightDecay = result[1]; + rounds = result[2]; } /// @notice Get exchange max dynamic fee diff --git a/contracts/SystemSettings.sol b/contracts/SystemSettings.sol index 47566410e5..268e38ef64 100644 --- a/contracts/SystemSettings.sol +++ b/contracts/SystemSettings.sol @@ -386,7 +386,7 @@ contract SystemSettings is Owned, MixinSystemSettings, ISystemSettings { } } - /// @notice Set exchange dynamic fee threshold constant default 40bps + /// @notice Set exchange dynamic fee threshold constant in decimal ratio /// @param threshold The exchange dynamic fee threshold /// @return uint threshold constant function setExchangeDynamicFeeThreshold(uint threshold) external onlyOwner { @@ -397,7 +397,7 @@ contract SystemSettings is Owned, MixinSystemSettings, ISystemSettings { emit ExchangeDynamicFeeThresholdUpdated(threshold); } - /// @notice Set exchange dynamic fee weight decay constant default 0.9 + /// @notice Set exchange dynamic fee weight decay constant /// @param weightDecay The exchange dynamic fee weight decay /// @return uint weight decay constant function setExchangeDynamicFeeWeightDecay(uint weightDecay) external onlyOwner { @@ -421,6 +421,8 @@ contract SystemSettings is Owned, MixinSystemSettings, ISystemSettings { /// @param maxFee The max exchange dynamic fee /// @return uint dynamic fee last N rounds function setExchangeMaxDynamicFee(uint maxFee) external onlyOwner { + require(maxFee != 0, "Max dynamic fee cannot be 0"); + flexibleStorage().setUIntValue(SETTING_CONTRACT_NAME, SETTING_EXCHANGE_MAX_DYNAMIC_FEE, maxFee); emit ExchangeMaxDynamicFeeUpdated(maxFee); diff --git a/test/dynamic-fee-calc.csv b/test/dynamic-fee-calc.csv new file mode 100644 index 0000000000..8cb80d1c6b --- /dev/null +++ b/test/dynamic-fee-calc.csv @@ -0,0 +1,124 @@ +,,,,,,, +,round,price,ΔP (bp),boost,dynamic_fee (bp),,deviation threshold (bp) +,1,50007.73057899,,0,,,20 +,2,49953.81262846,10.8,0,,, +,3,50051.305,19.5,0,,,exchange fee (bp) +,4,50069.7111646299,3.7,0,,,20 +,5,50070.40589905,0.1,0,,, +,6,50078.36597318,1.6,0,,,boost threshold (bp) +,7,50080.61,0.4,0,,,40 +,8,50070.3465,2.0,0,,, +,9,50074.192,0.8,0,,,boost decay constant +,10,50076.03,0.4,0,,,0.9 +,11,49994,16.4,0,0,, +,12,49960.65493467,6.7,0,0,, +,13,49981,4.1,0,0,, +,14,49871.92313713,21.8,0,0,, +,15,49933.34034209,12.3,0,0,,max weight +,16,49842.74988613,18.1,0,0,,1 +,17,49838.87627216,0.8,0,0,, +,18,49722.83886705,23.3,0,0,,decay constant +,19,49714.05205647,1.8,0,0,,0.9 +,20,49691.8024553899,4.5,0,0,, +,21,49714.05205647,4.5,0,0,,0.387420489 +,22,49535.05178912,36.0,0,0,,0.43046721 +,23,49234.65005734,60.6,20.6,20.6442753020359,,0.4782969 +,24,49190.99117585,8.9,0,18.5798477718323,,0.531441 +,25,49234.65005734,8.9,0,16.7218629946491,,0.59049 +,26,49088.63670793,29.7,0,15.0496766951842,,0.6561 +,27,49046.17079819,8.7,0,13.5447090256658,,0.729 +,28,49088.63670793,8.7,0,12.2,,0.81 +,29,49131.10261767,8.7,0,11.0,,0.9 +,30,49096.77,7.0,0,9.9,,1 +,31,49143.5399999999,9.5,0,8.9,, +,32,49198.77,11.2,0,8.0,, +,33,49254,11.2,0,0,, +,34,49208,9.3,0,0,, +,35,49101.41,21.7,0,0,, +,36,49095.24231598,1.3,0,0,, +,37,49093.89744338,0.3,0,0,, +,38,49054.03062906,8.1,0,0,, +,39,49009.46274509,9.1,0,0,, +,40,49054.03062906,9.1,0,0,, +,41,48964.89486113,18.2,0,0,, +,42,48954.93260767,2.0,0,0,, +,43,48364.4121895,120.6,80.6,80.625315308366,, +,44,48342.34371195,4.6,0,72.5627837775294,, +,45,48267.37667219,15.5,0,65.3065053997765,, +,46,48225.00037245,8.8,0,58.7758548597988,, +,47,47783.75,91.5,51.5,104.396531487276,, +,48,48198.4621158,86.8,46.8,140.7,, +,49,48043.1218078999,32.2,0,126.7,, +,50,48198.4621158,32.3,0,114.0,, +,51,48044.80901916,31.9,0,102.6,, +,52,48108.4029515399,13.2,0,92.3,, +,53,48112,0.7,0,55.0,, +,54,48056.45718986,11.5,0,49.5,, +,55,47988.17926024,14.2,0,44.5,, +,56,47902.24487106,17.9,0,40.1,, +,57,47911.8471578599,2.0,0,18.1,, +,58,47670.81054939,50.3,10.3,10.3083522694779,, +,59,47580.67384441,18.9,0,9.27751704253011,, +,60,47449.76309439,27.5,0,8.3497653382771,, +,61,47382.88726893,14.1,0,7.51478880444939,, +,62,47222.35138239,33.9,0,6.76330992400445,, +,63,46948.76815888,57.9,17.9,24.0,, +,64,46675.18493538,58.3,18.3,39.9,, +,65,46463.74676537,45.3,5.3,41.2,, +,66,46217.7336139799,52.9,12.9,50.0,, +,67,46183.17440371,7.5,0,45.0,, +,68,45919.00562933,57.2,17.2,54.1,, +,69,45586.5085919099,72.4,32.4,81.1,, +,70,45483.8961602099,22.5,0,73.0,, +,71,44672.6561639399,178.4,138.4,204.1,, +,72,44661.70868763,2.5,0,183.7,, +,73,44650.76121132,2.5,0,159.0,, +,74,44668.7953606,4.0,0,136.8,, +,75,44774.49388229,23.7,0,121.2,, +,76,44853.05,17.5,0,104.6,, +,77,45204.4549999999,78.3,38.3,132.5,, +,78,45231.276,5.9,0,113.2,, +,79,45477.5075,54.4,14.4,105.1,, +,80,45568.3515,20.0,0,94.6,, +,81,45468.09,22.0,0,36.8537523828239,, +,82,45757.7,63.7,23.7,56.9,, +,83,45846.1785,19.3,0,51.2,, +,84,46060.93771094,46.8,6.8,52.9,, +,85,46081.215,4.4,0,47.6,, +,86,46167.82,18.8,0,42.9,, +,87,45860.8944999999,66.5,26.5,51.7,, +,88,45844.31854774,3.6,0,46.5,, +,89,46040.66042188,42.8,2.8,39.7,, +,90,45975.5952109399,14.1,0,35.7,, +,91,45942.48948481,7.2,0,32.1,, +,92,46040.66042188,21.4,0,20.6,, +,93,46090.3302109399,10.8,0,18.6,, +,94,46132.86,9.2,0,14.3,, +,95,45874.42216374,56.0,16.0,28.9,, +,96,45830.84933011,9.5,0,26.0,, +,97,45888.1962499999,12.5,0,14.2,, +,98,46038.42444459,32.7,0,12.8,, +,99,46039.54243323,0.2,0,10.5109430861616,, +,100,46218.23018745,38.8,0,9.5,, +,101,46213.94,0.9,0,8.5,, +,102,46437.773,48.4,8.4,16.1,, +,103,46506.479,14.8,0,14.5,, +,104,46519.9745,2.9,0,13.0,, +,105,46533.47,2.9,0,6.14844957170929,, +,106,46622.632,19.2,0,5.53360461453836,, +,107,46619.216,0.7,0,5.0,, +,108,46615.8,0.7,0,4.5,, +,109,46707.1025,19.6,0,4.0,, +,110,46826.18,25.5,0,3.6,, +,111,46819.33062006,1.5,0,3.3,, +,112,46826.18,1.5,0,0,, +,113,46961.35210329,28.9,0,0,, +,114,47001.22734027,8.5,0,0,, +,115,46829.45375,36.5,0,0,, +,116,46796.05875,7.1,0,0,, +,117,46903.6069999999,23.0,0,0,, +,118,46789.378,24.4,0,0,, +,119,46752.593,7.9,0,0,, +,120,46766.34475,2.9,0,0,, +,121,46780.0965,2.9,0,0,, +,122,46946.0995,35.5,0,0,, \ No newline at end of file From e905aca7d90103564b48abb50d61194bb9ffb8cf Mon Sep 17 00:00:00 2001 From: Lecky Date: Fri, 10 Dec 2021 00:33:36 +1100 Subject: [PATCH 087/133] Fix(ExchangerWithFeeRecAlternatives): - Adding default values for getUIntValues in flexibleStorage --- .../ExchangerWithFeeRecAlternatives.behaviors.js | 8 ++++++++ test/contracts/helpers.js | 1 + 2 files changed, 9 insertions(+) diff --git a/test/contracts/ExchangerWithFeeRecAlternatives.behaviors.js b/test/contracts/ExchangerWithFeeRecAlternatives.behaviors.js index c8c6407149..a952f77fea 100644 --- a/test/contracts/ExchangerWithFeeRecAlternatives.behaviors.js +++ b/test/contracts/ExchangerWithFeeRecAlternatives.behaviors.js @@ -138,6 +138,14 @@ module.exports = function({ accounts }) { cb(); }); }, + whenMockedWithUintsSystemSetting: ({ setting, value }, cb) => { + describe(`when SystemSetting.${setting} is mocked to ${value}`, () => { + beforeEach(async () => { + this.flexibleStorageMock.mockSystemSetting({ setting, value, type: 'uints' }); + }); + cb(); + }); + }, whenMockedWithSynthUintSystemSetting: ({ setting, synth, value }, cb) => { const settingForSynth = web3.utils.soliditySha3( { type: 'bytes32', value: toBytes32(setting) }, diff --git a/test/contracts/helpers.js b/test/contracts/helpers.js index e08c4d7846..8dac99414d 100644 --- a/test/contracts/helpers.js +++ b/test/contracts/helpers.js @@ -303,6 +303,7 @@ module.exports = { const flexibleStorageTypes = [ ['uint', 'getUIntValue', '0'], + ['uints', 'getUIntValues', ['0', '0', '0']], ['int', 'getIntValue', '0'], ['address', 'getAddressValue', ZERO_ADDRESS], ['bool', 'getBoolValue', false], From fe81a82ea0279c70187584ef59fe853a2f2503a9 Mon Sep 17 00:00:00 2001 From: Lecky Date: Sat, 11 Dec 2021 22:04:27 +1100 Subject: [PATCH 088/133] Adding comment for setExchangeDynamicFeeRounds with minimum 2 rounds --- contracts/SystemSettings.sol | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/contracts/SystemSettings.sol b/contracts/SystemSettings.sol index 268e38ef64..2c97b77ac0 100644 --- a/contracts/SystemSettings.sol +++ b/contracts/SystemSettings.sol @@ -408,7 +408,7 @@ contract SystemSettings is Owned, MixinSystemSettings, ISystemSettings { emit ExchangeDynamicFeeWeightDecayUpdated(weightDecay); } - /// @notice Set exchange dynamic fee last N rounds + /// @notice Set exchange dynamic fee last N rounds with minimum 2 rounds /// @param rounds The exchange dynamic fee last N rounds /// @return uint dynamic fee last N rounds function setExchangeDynamicFeeRounds(uint rounds) external onlyOwner { From 5b0cda888575ebd75b081eb157a000029d018f99 Mon Sep 17 00:00:00 2001 From: Lecky Date: Sat, 25 Dec 2021 22:12:00 +1100 Subject: [PATCH 089/133] Merge branch 'develop' into feat/sip-184-dynamic-fees --- contracts/ExchangeRates.sol | 230 +-- contracts/ExchangeRatesWithDexPricing.sol | 8 +- contracts/interfaces/IExchangeRates.sol | 4 - hardhat.config.js | 1 + index.js | 4 +- package-lock.json | 4 +- package.json | 2 +- publish/assets.json | 6 + publish/deployed/kovan-ovm/config.json | 3 + publish/deployed/kovan-ovm/deployment.json | 1335 +++++++++++++++- publish/deployed/kovan-ovm/params.json | 11 +- publish/deployed/kovan-ovm/versions.json | 41 +- publish/deployed/local-ovm/params.json | 11 +- publish/deployed/mainnet-ovm/config.json | 3 + publish/deployed/mainnet-ovm/deployment.json | 1337 +++++++++++++++- publish/deployed/mainnet-ovm/params.json | 11 +- publish/deployed/mainnet-ovm/versions.json | 25 +- publish/deployed/mainnet/config.json | 9 + publish/deployed/mainnet/deployment.json | 149 +- publish/deployed/mainnet/feeds.json | 4 + publish/deployed/mainnet/synths.json | 5 + publish/deployed/mainnet/versions.json | 25 + publish/releases.json | 28 +- .../src/commands/deploy/configure-loans.js | 10 +- .../deploy/configure-system-settings.js | 6 +- publish/src/commands/deploy/deploy-core.js | 3 +- publish/src/commands/deploy/index.js | 4 - .../deploy/rebuild-resolver-caches.js | 4 +- .../deploy/system-and-parameter-check.js | 18 +- publish/src/commands/owner.js | 156 +- test/contracts/BaseSynthetix.js | 74 +- test/contracts/CollateralErc20.js | 54 +- test/contracts/CollateralEth.js | 60 +- test/contracts/CollateralManager.js | 25 +- test/contracts/CollateralShort.js | 57 +- test/contracts/CollateralUtil.js | 57 +- test/contracts/DebtCache.js | 92 +- test/contracts/Depot.js | 29 +- test/contracts/EtherWrapper.js | 15 +- test/contracts/ExchangeRates.js | 1387 +++-------------- test/contracts/Exchanger.spec.js | 223 +-- test/contracts/FeePool.js | 69 +- test/contracts/FeePoolState.js | 16 +- test/contracts/Issuer.js | 121 +- test/contracts/Liquidations.js | 13 +- test/contracts/MultiCollateralSynth.js | 39 +- test/contracts/NativeEtherWrapper.js | 15 +- test/contracts/PurgeableSynth.js | 26 +- test/contracts/RewardsIntegrationTests.js | 57 +- test/contracts/ShortingRewards.js | 49 +- test/contracts/StakingRewards.js | 23 +- test/contracts/Synth.js | 26 +- test/contracts/SynthUtil.js | 25 +- test/contracts/Synthetix.js | 25 +- test/contracts/TradingRewards.spec.js | 25 +- test/contracts/Wrapper.js | 17 +- test/contracts/WrapperFactory.js | 15 +- test/contracts/helpers.js | 76 +- test/contracts/setup.js | 27 +- .../behaviors/exchange.behavior.js | 6 +- test/integration/behaviors/redeem.behavior.js | 4 +- test/integration/behaviors/stake.behavior.js | 9 +- test/integration/utils/bootstrap.js | 10 +- test/integration/utils/rates.js | 66 +- test/integration/utils/skip.js | 4 +- test/publish/index.js | 16 +- test/utils/index.js | 13 + 67 files changed, 3699 insertions(+), 2623 deletions(-) diff --git a/contracts/ExchangeRates.sol b/contracts/ExchangeRates.sol index ac0902db53..c3bae1220c 100644 --- a/contracts/ExchangeRates.sol +++ b/contracts/ExchangeRates.sol @@ -22,12 +22,8 @@ contract ExchangeRates is Owned, MixinSystemSettings, IExchangeRates { using SafeDecimalMath for uint; bytes32 public constant CONTRACT_NAME = "ExchangeRates"; - - // Exchange rates and update times stored by currency code, e.g. 'SNX', or 'sUSD' - mapping(bytes32 => mapping(uint => RateAndUpdatedTime)) private _rates; - - // The address of the oracle which pushes rate updates to this contract - address public oracle; + //slither-disable-next-line naming-convention + bytes32 internal constant sUSD = "sUSD"; // Decentralized oracle networks that feed into pricing aggregators mapping(bytes32 => AggregatorV2V3Interface) public aggregators; @@ -37,61 +33,12 @@ contract ExchangeRates is Owned, MixinSystemSettings, IExchangeRates { // List of aggregator keys for convenient iteration bytes32[] public aggregatorKeys; - // Do not allow the oracle to submit times any further forward into the future than this constant. - uint private constant ORACLE_FUTURE_LIMIT = 10 minutes; - - /// @notice ID for the current round - /// @param currencyKey is the currency key of the synth to be exchanged - /// @return the current exchange round ID - mapping(bytes32 => uint) public currentRoundForRate; - - // // ========== CONSTRUCTOR ========== - constructor( - address _owner, - address _oracle, - address _resolver, - bytes32[] memory _currencyKeys, - uint[] memory _newRates - ) public Owned(_owner) MixinSystemSettings(_resolver) { - require(_currencyKeys.length == _newRates.length, "Currency key length and rate length must match."); - - oracle = _oracle; - - // The sUSD rate is always 1 and is never stale. - _setRate("sUSD", 0, SafeDecimalMath.unit(), now); - - internalUpdateRates(_currencyKeys, _newRates, now); - } - - /* ========== SETTERS ========== */ - - function setOracle(address _oracle) external onlyOwner { - oracle = _oracle; - emit OracleUpdated(oracle); - } + constructor(address _owner, address _resolver) public Owned(_owner) MixinSystemSettings(_resolver) {} /* ========== MUTATIVE FUNCTIONS ========== */ - function updateRates( - bytes32[] calldata currencyKeys, - uint[] calldata newRates, - uint timeSent - ) external onlyOracle returns (bool) { - return internalUpdateRates(currencyKeys, newRates, timeSent); - } - - function deleteRate(bytes32 currencyKey) external onlyOracle { - require(_getRate(currencyKey) > 0, "Rate is zero"); - - delete _rates[currencyKey][currentRoundForRate[currencyKey]]; - - currentRoundForRate[currencyKey]--; - - emit RateDeleted(currencyKey); - } - function addAggregator(bytes32 currencyKey, address aggregatorAddress) external onlyOwner { AggregatorV2V3Interface aggregator = AggregatorV2V3Interface(aggregatorAddress); // This check tries to make sure that a valid aggregator is being added. @@ -341,7 +288,7 @@ contract ExchangeRates is Owned, MixinSystemSettings, IExchangeRates { function rateAndInvalid(bytes32 currencyKey) external view returns (uint rate, bool isInvalid) { RateAndUpdatedTime memory rateAndTime = _getRateAndUpdatedTime(currencyKey); - if (currencyKey == "sUSD") { + if (currencyKey == sUSD) { return (rateAndTime.rate, false); } return ( @@ -367,7 +314,7 @@ contract ExchangeRates is Owned, MixinSystemSettings, IExchangeRates { // do one lookup of the rate & time to minimize gas RateAndUpdatedTime memory rateEntry = _getRateAndUpdatedTime(currencyKeys[i]); rates[i] = rateEntry.rate; - if (!anyRateInvalid && currencyKeys[i] != "sUSD") { + if (!anyRateInvalid && currencyKeys[i] != sUSD) { anyRateInvalid = flagList[i] || _rateIsStaleWithTime(_rateStalePeriod, rateEntry.time); } } @@ -425,64 +372,6 @@ contract ExchangeRates is Owned, MixinSystemSettings, IExchangeRates { } } - /// @notice setting the rate for a currency - /// @param currencyKey the currency key - /// @param roundId the round id - /// @param rate the rate to set - /// @param time the time of the rate - function _setRate( - bytes32 currencyKey, - uint256 roundId, - uint256 rate, - uint256 time - ) internal { - if (roundId > 0) { - currentRoundForRate[currencyKey] = roundId; - } else { - // Note: this will effectively start the rounds at 1, which matches Chainlink's Agggregators - currentRoundForRate[currencyKey]++; - } - - // skip writing if the rate is the same - if (rate == _rates[currencyKey][currentRoundForRate[currencyKey]].rate) return; - - _rates[currencyKey][currentRoundForRate[currencyKey]] = RateAndUpdatedTime({ - rate: uint216(rate), - time: uint40(time) - }); - } - - function internalUpdateRates( - bytes32[] memory currencyKeys, - uint[] memory newRates, - uint timeSent - ) internal returns (bool) { - require(currencyKeys.length == newRates.length, "Currency key array length must match rates array length."); - require(timeSent < (now + ORACLE_FUTURE_LIMIT), "Time is too far into the future"); - - // Loop through each key and perform update. - for (uint i = 0; i < currencyKeys.length; i++) { - bytes32 currencyKey = currencyKeys[i]; - - // Should not set any rate to zero ever, as no asset will ever be - // truely worthless and still valid. In this scenario, we should - // delete the rate and remove it from the system. - require(newRates[i] != 0, "Zero is not a valid rate, please call deleteRate instead."); - require(currencyKey != "sUSD", "Rate of sUSD cannot be updated, it's always UNIT."); - - // We should only update the rate if it's at least the same age as the last rate we've got. - if (timeSent < _getUpdatedTime(currencyKey)) { - continue; - } - - _setRate(currencyKey, 0, newRates[i], timeSent); - } - - emit RatesUpdated(currencyKeys, newRates); - - return true; - } - function removeFromArray(bytes32 entry, bytes32[] storage array) internal returns (bool) { for (uint i = 0; i < array.length; i++) { if (array[i] == entry) { @@ -511,59 +400,65 @@ contract ExchangeRates is Owned, MixinSystemSettings, IExchangeRates { return uint(rate); } - function _getRateAndUpdatedTime(bytes32 currencyKey) internal view returns (RateAndUpdatedTime memory entry) { - AggregatorV2V3Interface aggregator = aggregators[currencyKey]; - - if (aggregator != AggregatorV2V3Interface(0)) { - // this view from the aggregator is the most gas efficient but it can throw when there's no data, - // so let's call it low-level to suppress any reverts - bytes memory payload = abi.encodeWithSignature("latestRoundData()"); - // solhint-disable avoid-low-level-calls - (bool success, bytes memory returnData) = address(aggregator).staticcall(payload); - - if (success) { - (, int256 answer, , uint256 updatedAt, ) = - abi.decode(returnData, (uint80, int256, uint256, uint256, uint80)); - entry.rate = uint216(_formatAggregatorAnswer(currencyKey, answer)); - entry.time = uint40(updatedAt); - } + function _getRateAndUpdatedTime(bytes32 currencyKey) internal view returns (RateAndUpdatedTime memory) { + // sUSD rate is 1.0 + if (currencyKey == sUSD) { + return RateAndUpdatedTime({rate: uint216(SafeDecimalMath.unit()), time: 0}); } else { - uint roundId = currentRoundForRate[currencyKey]; - entry = _rates[currencyKey][roundId]; + AggregatorV2V3Interface aggregator = aggregators[currencyKey]; + if (aggregator != AggregatorV2V3Interface(0)) { + // this view from the aggregator is the most gas efficient but it can throw when there's no data, + // so let's call it low-level to suppress any reverts + bytes memory payload = abi.encodeWithSignature("latestRoundData()"); + // solhint-disable avoid-low-level-calls + // slither-disable-next-line low-level-calls + (bool success, bytes memory returnData) = address(aggregator).staticcall(payload); + + if (success) { + (, int256 answer, , uint256 updatedAt, ) = + abi.decode(returnData, (uint80, int256, uint256, uint256, uint80)); + return + RateAndUpdatedTime({ + rate: uint216(_formatAggregatorAnswer(currencyKey, answer)), + time: uint40(updatedAt) + }); + } // else return defaults, to avoid reverting in views + } // else return defaults, to avoid reverting in views } } function _getCurrentRoundId(bytes32 currencyKey) internal view returns (uint) { + if (currencyKey == sUSD) { + return 0; // no roundIds for sUSD + } AggregatorV2V3Interface aggregator = aggregators[currencyKey]; - if (aggregator != AggregatorV2V3Interface(0)) { return aggregator.latestRound(); - } else { - return currentRoundForRate[currencyKey]; - } + } // else return defaults, to avoid reverting in views } function _getRateAndTimestampAtRound(bytes32 currencyKey, uint roundId) internal view returns (uint rate, uint time) { - AggregatorV2V3Interface aggregator = aggregators[currencyKey]; - RateAndUpdatedTime memory update = _rates[currencyKey][roundId]; - - // Try to get rate from cache if possible - if (update.rate > 0) { - return (update.rate, update.time); - } - - if (aggregator != AggregatorV2V3Interface(0)) { - // this view from the aggregator is the most gas efficient but it can throw when there's no data, - // so let's call it low-level to suppress any reverts - bytes memory payload = abi.encodeWithSignature("getRoundData(uint80)", roundId); - // solhint-disable avoid-low-level-calls - (bool success, bytes memory returnData) = address(aggregator).staticcall(payload); - - if (success) { - (, int256 answer, , uint256 updatedAt, ) = - abi.decode(returnData, (uint80, int256, uint256, uint256, uint80)); - return (_formatAggregatorAnswer(currencyKey, answer), updatedAt); - } + // short circuit sUSD + if (currencyKey == sUSD) { + // sUSD has no rounds, and 0 time is preferrable for "volatility" heuristics + // which are used in atomic swaps and fee reclamation + return (SafeDecimalMath.unit(), 0); + } else { + AggregatorV2V3Interface aggregator = aggregators[currencyKey]; + + if (aggregator != AggregatorV2V3Interface(0)) { + // this view from the aggregator is the most gas efficient but it can throw when there's no data, + // so let's call it low-level to suppress any reverts + bytes memory payload = abi.encodeWithSignature("getRoundData(uint80)", roundId); + // solhint-disable avoid-low-level-calls + (bool success, bytes memory returnData) = address(aggregator).staticcall(payload); + + if (success) { + (, int256 answer, , uint256 updatedAt, ) = + abi.decode(returnData, (uint80, int256, uint256, uint256, uint80)); + return (_formatAggregatorAnswer(currencyKey, answer), updatedAt); + } // else return defaults, to avoid reverting in views + } // else return defaults, to avoid reverting in views } } @@ -605,7 +500,7 @@ contract ExchangeRates is Owned, MixinSystemSettings, IExchangeRates { function _rateIsStale(bytes32 currencyKey, uint _rateStalePeriod) internal view returns (bool) { // sUSD is a special case and is never stale (check before an SLOAD of getRateAndUpdatedTime) - if (currencyKey == "sUSD") return false; + if (currencyKey == sUSD) return false; return _rateIsStaleWithTime(_rateStalePeriod, _getUpdatedTime(currencyKey)); } @@ -616,7 +511,7 @@ contract ExchangeRates is Owned, MixinSystemSettings, IExchangeRates { function _rateIsFlagged(bytes32 currencyKey, FlagsInterface flags) internal view returns (bool) { // sUSD is a special case and is never invalid - if (currencyKey == "sUSD") return false; + if (currencyKey == sUSD) return false; address aggregator = address(aggregators[currencyKey]); // when no aggregator or when the flags haven't been setup if (aggregator == address(0) || flags == FlagsInterface(0)) { @@ -626,25 +521,12 @@ contract ExchangeRates is Owned, MixinSystemSettings, IExchangeRates { } function _notImplemented() internal pure { + // slither-disable-next-line dead-code revert("Cannot be run on this layer"); } - /* ========== MODIFIERS ========== */ - - modifier onlyOracle { - _onlyOracle(); - _; - } - - function _onlyOracle() internal view { - require(msg.sender == oracle, "Only the oracle can perform this action"); - } - /* ========== EVENTS ========== */ - event OracleUpdated(address newOracle); - event RatesUpdated(bytes32[] currencyKeys, uint[] newRates); - event RateDeleted(bytes32 currencyKey); event AggregatorAdded(bytes32 currencyKey, address aggregator); event AggregatorRemoved(bytes32 currencyKey, address aggregator); } diff --git a/contracts/ExchangeRatesWithDexPricing.sol b/contracts/ExchangeRatesWithDexPricing.sol index 1548a61030..b6081da974 100644 --- a/contracts/ExchangeRatesWithDexPricing.sol +++ b/contracts/ExchangeRatesWithDexPricing.sol @@ -10,13 +10,7 @@ contract ExchangeRatesWithDexPricing is ExchangeRates { bytes32 internal constant SETTING_DEX_PRICE_AGGREGATOR = "dexPriceAggregator"; - constructor( - address _owner, - address _oracle, - address _resolver, - bytes32[] memory _currencyKeys, - uint[] memory _newRates - ) public ExchangeRates(_owner, _oracle, _resolver, _currencyKeys, _newRates) {} + constructor(address _owner, address _resolver) public ExchangeRates(_owner, _resolver) {} /* ========== SETTERS ========== */ diff --git a/contracts/interfaces/IExchangeRates.sol b/contracts/interfaces/IExchangeRates.sol index 5ed0ffdf2c..f1110010c8 100644 --- a/contracts/interfaces/IExchangeRates.sol +++ b/contracts/interfaces/IExchangeRates.sol @@ -15,8 +15,6 @@ interface IExchangeRates { function anyRateIsInvalid(bytes32[] calldata currencyKeys) external view returns (bool); - function currentRoundForRate(bytes32 currencyKey) external view returns (uint); - function currenciesUsingAggregator(address aggregator) external view returns (bytes32[] memory); function effectiveValue( @@ -85,8 +83,6 @@ interface IExchangeRates { function lastRateUpdateTimes(bytes32 currencyKey) external view returns (uint256); - function oracle() external view returns (address); - function rateAndTimestampAtRound(bytes32 currencyKey, uint roundId) external view returns (uint rate, uint time); function rateAndUpdatedTime(bytes32 currencyKey) external view returns (uint rate, uint time); diff --git a/hardhat.config.js b/hardhat.config.js index 8b769f2504..d349c6031f 100644 --- a/hardhat.config.js +++ b/hardhat.config.js @@ -65,5 +65,6 @@ module.exports = { }, mocha: { timeout: 120e3, // 120s + retries: 3, }, }; diff --git a/index.js b/index.js index 5b66697ea9..b0db24ef56 100644 --- a/index.js +++ b/index.js @@ -193,8 +193,8 @@ const defaults = { }, ETHER_WRAPPER_MAX_ETH: w3utils.toWei('5000'), - ETHER_WRAPPER_MINT_FEE_RATE: w3utils.toWei('0.02'), // 200 bps - ETHER_WRAPPER_BURN_FEE_RATE: w3utils.toWei('0.0005'), // 5 bps + ETHER_WRAPPER_MINT_FEE_RATE: w3utils.toWei('0.005'), // 5 bps + ETHER_WRAPPER_BURN_FEE_RATE: '0', // SIP-120 ATOMIC_MAX_VOLUME_PER_BLOCK: w3utils.toWei(`${2e5}`), // 200k diff --git a/package-lock.json b/package-lock.json index a3bffd203a..29f81add21 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,11 +1,11 @@ { "name": "synthetix", - "version": "2.55.0", + "version": "2.56.1", "lockfileVersion": 2, "requires": true, "packages": { "": { - "version": "2.55.0", + "version": "2.56.1", "license": "MIT", "dependencies": { "abi-decoder": "2.3.0", diff --git a/package.json b/package.json index 2536d81a40..aa01c63629 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "synthetix", - "version": "2.55.0", + "version": "2.56.1", "license": "MIT", "author": "Synthetix", "description": "The smart contracts which make up the Synthetix system. (synthetix.io)", diff --git a/publish/assets.json b/publish/assets.json index 35b05e6ae1..4dfcafc58a 100644 --- a/publish/assets.json +++ b/publish/assets.json @@ -239,6 +239,12 @@ "sign": "", "description": "DeFi Index" }, + "ETHBTC": { + "asset": "ETHBTC", + "category": "crypto", + "sign": "", + "description": "ETH / BTC" + }, "EUR": { "asset": "EUR", "category": "forex", diff --git a/publish/deployed/kovan-ovm/config.json b/publish/deployed/kovan-ovm/config.json index 41cad666e2..e273a64a5f 100644 --- a/publish/deployed/kovan-ovm/config.json +++ b/publish/deployed/kovan-ovm/config.json @@ -181,5 +181,8 @@ }, "OwnerRelayOnOptimism": { "deploy": false + }, + "CollateralEth": { + "deploy": false } } diff --git a/publish/deployed/kovan-ovm/deployment.json b/publish/deployed/kovan-ovm/deployment.json index 544b60e9bc..bf603e0ecf 100644 --- a/publish/deployed/kovan-ovm/deployment.json +++ b/publish/deployed/kovan-ovm/deployment.json @@ -209,10 +209,10 @@ }, "Synthetix": { "name": "Synthetix", - "address": "0xCAA5c8e9E67BBa010D2D7F589F02d588Fb49f93D", + "address": "0x099B3881d63d3Eef0ec32783Aa64B726672213E2", "source": "MintableSynthetix", - "link": "https://kovan-explorer.optimism.io/address/0xCAA5c8e9E67BBa010D2D7F589F02d588Fb49f93D", - "timestamp": "2021-10-11T16:31:00.378Z", + "link": "https://kovan-explorer.optimism.io/address/0x099B3881d63d3Eef0ec32783Aa64B726672213E2", + "timestamp": "2021-12-15T07:19:24.524Z", "txn": "", "network": "kovan" }, @@ -566,6 +566,15 @@ "timestamp": "2021-11-22T21:36:24.000Z", "txn": "https://kovan-explorer.optimism.io/tx/0x7977a8dbfbdf8a0154b1e38500420b6a8b5fc6ff8937d7ed4ea66d33fbbbccc3", "network": "kovan" + }, + "CollateralEth": { + "name": "CollateralEth", + "address": "0xc7960401a5Ca5A201d41Cf6532C7d2803f8D5Ce4", + "source": "CollateralEth", + "link": "https://kovan-explorer.optimism.io/address/0xc7960401a5Ca5A201d41Cf6532C7d2803f8D5Ce4", + "timestamp": "2021-12-15T07:19:30.068Z", + "txn": "", + "network": "kovan" } }, "sources": { @@ -14826,7 +14835,7 @@ } }, "MintableSynthetix": { - "bytecode": "60806040523480156200001c57600080620000196200039d565b50505b506040516200579538038062005795833981810160405260a08110156200004d576000806200004a6200039d565b50505b8101908080519291906020018051929190602001805192919060200180519291906020018051925086915085905084848480858560405160408082018152601782527f53796e746865746978204e6574776f726b20546f6b656e0000000000000000006020830152516040808201905260038152620a69cb60eb1b60208201528660128986816001600160a01b038116620001395760405162461bcd60e51b815260206004820152601960248201527f4f776e657220616464726573732063616e6e6f74206265203000000000000000604482015260640160405180910390620001366200039d565b50505b806000600181620001496200040a565b816001600160a01b0302191690836001600160a01b03160217906200016d6200046c565b5050507fb532073b38c83145e3e5135377a08bf9aab55bc0fd7c1179cd4fb995d2a5159c6000826040516001600160a01b039283168152911660208201526040908101905180910390a15060008080620001c66200040a565b906101000a90046001600160a01b03166001600160a01b03161415620002315760405162461bcd60e51b815260206004820152601160248201527013dddb995c881b5d5cdd081899481cd95d607a1b6044820152606401604051809103906200022e6200039d565b50505b806002600181620002416200040a565b816001600160a01b0302191690836001600160a01b0316021790620002656200046c565b5050507ffc80377ca9c49cc11ae6982f390a42db976d5530af7c43889264b13fbbd7c57e816040516001600160a01b03909116815260200160405180910390a150856005600181620002b66200040a565b816001600160a01b0302191690836001600160a01b0316021790620002da6200046c565b505050846006908051620002f3929160200190620004bb565b50600784805162000309929160200190620004bb565b5082806008620003186200046c565b50505081600960006101000a816200032f6200040a565b8160ff021916908360ff16021790620003476200046c565b5050505050505050505080600960016101000a81620003656200040a565b816001600160a01b0302191690836001600160a01b0316021790620003896200046c565b505050505050505050505050505062000597565b632a2a7adb598160e01b8152600481016020815285602082015260005b86811015620003d7578086015182820160400152602001620003ba565b506020828760640184336000905af158600e01573d6000803e3d6000fd5b3d6001141558600a015760016000f35b505050565b6303daa959598160e01b8152836004820152602081602483336000905af158600e01573d6000803e3d6000fd5b3d6001141558600a015760016000f35b8051935060005b604081101562000467576000828201526020016200044e565b505050565b6322bd64c0598160e01b8152836004820152846024820152600081604483336000905af158600e01573d6000803e3d6000fd5b3d6001141558600a015760016000f35b6000815260206200044e565b8280620004c76200040a565b600181600116156101000203166002900490600052602060002090601f016020900481019282601f106200051257805160ff191683800117856200050a6200046c565b505062000558565b82800160010185620005236200046c565b5050821562000558579182015b828111156200055857825182620005466200046c565b50509160200191906001019062000530565b50620005669291506200056a565b5090565b6200059491905b80821115620005665760008082620005886200046c565b50505060010162000571565b90565b6151ee80620005a76000396000f3fe608060405234801561001957600080610016614d0c565b50505b50600436106104055760003560e01c8063741853601161021e578063a9059cbb1161012e578063d8a1f76f116100c1578063e8e09b8b11610090578063e8e09b8b14610e23578063e90dd9e214610e58578063ec55688914610e60578063edef719a14610e68578063ee52a2f314610e9d57610405565b8063d8a1f76f14610d89578063dbf6334014610daf578063dd62ed3e14610db7578063e6203ed114610dee57610405565b8063c2bf3880116100fd578063c2bf388014610cd6578063c836fa0a14610d0b578063d37c4d8b14610d4c578063d67bdd2514610d8157610405565b8063a9059cbb14610c2f578063ace88afd14610c64578063af086c7e14610c9f578063bc67f83214610ca757610405565b80639324cac7116101b1578063987757dd11610180578063987757dd14610b745780639cbdaeb614610b9a5780639f76980714610ba2578063a311c7c214610bd1578063a5fdc5de14610c0057610405565b80639324cac714610b2d57806395d89b4114610b3557806397107d6d14610b3d5780639741fb2214610b6c57610405565b8063899ffef4116101ed578063899ffef414610aa85780638a29001414610ab05780638da5cb5b14610ad657806391e56b6814610ade57610405565b80637418536014610a4c57806379ba509714610a54578063835e119c14610a5c57806383d625d414610a8257610405565b80632c955fa7116103195780634e99bda9116102ac5780636ac0bf9c1161027b5780636ac0bf9c146109095780636c00f310146109385780636f01a9861461098757806370a08231146109c257806372cb051f146109f157610405565b80634e99bda91461087d57806353a47bb7146108855780635af090ef1461088d578063666ed4f1146108d457610405565b8063313ce567116102e8578063313ce567146107fa578063320223db1461080257806332608039146108315780633e89b9e51461085757610405565b80632c955fa71461072e5780632d3169eb1461075d5780632e0f26251461079557806330ead760146107b357610405565b80631627540c1161039c5780631fce304d1161036b5780631fce304d1461069357806323b872dd146106b9578063295da87d146106f85780632a9053181461071e5780632af64bd31461072657610405565b80631627540c1461062557806316b2213f1461065457806318160ddd14610683578063188214001461068b57610405565b80630e30963c116103d85780630e30963c146105405780631137aedf146105995780631249c58b146105ec578063131b0ae7146105f457610405565b806304f3bcec1461041357806305b3c1c91461043757806306fdde0314610478578063095ea7b3146104f7575b600080610410614d0c565b50505b61041b610ecf565b6040516001600160a01b03909116815260200160405180910390f35b6104666004803603602081101561045657600080610453614d0c565b50505b50356001600160a01b0316610eee565b60405190815260200160405180910390f35b610480610fad565b60405160208082528190810183818151815260200191508051906020019080838360005b838110156104bc5780820151838201526020016104a4565b50505050905090810190601f1680156104e95780820380516001836020036101000a031916815260200191505b509250505060405180910390f35b61052c6004803603604081101561051657600080610513614d0c565b50505b506001600160a01b038135169060200135611066565b604051901515815260200160405180910390f35b6105786004803603608081101561055f5760008061055c614d0c565b50505b5080359060208101359060408101359060600135611164565b6040519182526001600160a01b031660208201526040908101905180910390f35b6105c8600480360360208110156105b8576000806105b5614d0c565b50505b50356001600160a01b0316611178565b60405180848152602001838152602001828152602001935050505060405180910390f35b61052c611252565b6106236004803603602081101561061357600080610610614d0c565b50505b50356001600160a01b031661125f565b005b6106236004803603602081101561064457600080610641614d0c565b50505b50356001600160a01b031661129d565b6104666004803603602081101561067357600080610670614d0c565b50505b50356001600160a01b0316611317565b61046661136a565b610480611377565b61052c600480360360208110156106b2576000806106af614d0c565b50505b50356113ae565b61052c600480360360608110156106d8576000806106d5614d0c565b50505b506001600160a01b03813581169160208101359091169060400135611493565b6106236004803603602081101561071757600080610714614d0c565b50505b50356114df565b6104806115aa565b61052c6115c7565b6106236004803603602081101561074d5760008061074a614d0c565b50505b50356001600160a01b0316611756565b6106236004803603608081101561077c57600080610779614d0c565b50505b50803590602081013590604081013590606001356117dc565b61079d611961565b60405160ff909116815260200160405180910390f35b610466600480360360a08110156107d2576000806107cf614d0c565b50505b508035906020810135906040810135906001600160a01b036060820135169060800135611966565b61079d611ae7565b610623600480360360208110156108215760008061081e614d0c565b50505b50356001600160a01b0316611b00565b61041b600480360360208110156108505760008061084d614d0c565b50505b5035611b33565b6104666004803603602081101561087657600080610873614d0c565b50505b5035611b7e565b61052c611bd2565b61041b611c7a565b610466600480360360a08110156108ac576000806108a9614d0c565b50505b508035906020810135906040810135906001600160a01b036060820135169060800135611c86565b610623600480360360408110156108f3576000806108f0614d0c565b50505b506001600160a01b038135169060200135611c99565b6104666004803603602081101561092857600080610925614d0c565b50505b50356001600160a01b0316611caf565b610623600480360360c081101561095757600080610954614d0c565b50505b506001600160a01b03813581169160208101359160408201359160608101359160808201359160a0013516611e49565b610623600480360360608110156109a6576000806109a3614d0c565b50505b506001600160a01b038135169060208101359060400135611fee565b610466600480360360208110156109e1576000806109de614d0c565b50505b50356001600160a01b0316612171565b6109f96121d7565b60405160208082528190810183818151815260200191508051906020019060200280838360005b83811015610a38578082015183820152602001610a20565b505050509050019250505060405180910390f35b610623612332565b610623612562565b61041b60048036036020811015610a7b57600080610a78614d0c565b50505b50356126e5565b61046660048036036020811015610aa157600080610a9e614d0c565b50505b5035612730565b6109f9612784565b61062360048036036020811015610acf57600080610acc614d0c565b50505b5035612804565b61041b612836565b610466600480360360c0811015610afd57600080610afa614d0c565b50505b506001600160a01b0381358116916020810135916040820135916060810135916080820135169060a00135612841565b61046661298b565b610480612996565b61062360048036036020811015610b5c57600080610b59614d0c565b50505b50356001600160a01b0316612a38565b610623612ab3565b6105c860048036036020811015610b9357600080610b90614d0c565b50505b5035612b76565b61041b612c33565b61062360048036036020811015610bc157600080610bbe614d0c565b50505b50356001600160a01b0316612c3f565b61046660048036036020811015610bf057600080610bed614d0c565b50505b50356001600160a01b0316612c83565b61046660048036036020811015610c1f57600080610c1c614d0c565b50505b50356001600160a01b0316612cd6565b61052c60048036036040811015610c4e57600080610c4b614d0c565b50505b506001600160a01b038135169060200135612d29565b61062360048036036060811015610c8357600080610c80614d0c565b50505b506001600160a01b038135169060208101359060400135612d91565b610623612e0e565b61062360048036036020811015610cc657600080610cc3614d0c565b50505b50356001600160a01b0316612e40565b61062360048036036040811015610cf557600080610cf2614d0c565b50505b506001600160a01b038135169060200135612e56565b61046660048036036080811015610d2a57600080610d27614d0c565b50505b506001600160a01b038135169060208101359060408101359060600135612f23565b61046660048036036040811015610d6b57600080610d68614d0c565b50505b506001600160a01b03813516906020013561306e565b61041b613134565b61062360048036036020811015610da857600080610da5614d0c565b50505b5035613140565b61046661320e565b61046660048036036040811015610dd657600080610dd3614d0c565b50505b506001600160a01b038135811691602001351661324b565b61052c60048036036040811015610e0d57600080610e0a614d0c565b50505b506001600160a01b0381351690602001356132b9565b61062360048036036040811015610e4257600080610e3f614d0c565b50505b506001600160a01b0381351690602001356132c3565b61041b6132f6565b61041b613302565b61062360048036036040811015610e8757600080610e84614d0c565b50505b506001600160a01b03813516906020013561330e565b61046660048036036060811015610ebc57600080610eb9614d0c565b50505b50803590602081013590604001356134d4565b60016009610edb614d77565b906101000a90046001600160a01b031681565b6000610ef8613672565b6001600160a01b03166305b3c1c9836040516001600160e01b031960e084901b1681526001600160a01b0390911660048201526024016020604051808303818680610f41614dd2565b158015610f5657600080610f53614d0c565b50505b505a610f60614e1e565b5050505050158015610f7f573d6000803e3d6000610f7c614d0c565b50505b505050506040513d6020811015610f9e57600080610f9b614d0c565b50505b81019080805195945050505050565b600680610fb8614d77565b600181600116156101000203166002900480601f016020809104026020016040519081016040528181529190602083018280610ff2614d77565b6001816001161561010002031660029004801561105e5780601f1061102c57610100808361101e614d77565b04028352916020019161105e565b820191906000526020600020905b81611043614d77565b8152906001019060200180831161103a57829003601f168201915b505050505081565b600061107061368b565b600080600461107d614d77565b906101000a90046001600160a01b03169050600560009061109c614d77565b906101000a90046001600160a01b03166001600160a01b031663da46098c8286866040516001600160e01b031960e086901b1681526001600160a01b03938416600482015291909216602482015260448101919091526064016000604051808303816000878061110a614dd2565b15801561111f5760008061111c614d0c565b50505b505a611129614f09565b505050505050158015611149573d6000803e3d6000611146614d0c565b50505b50505050611158818585613761565b60019150505b92915050565b60008061116f613844565b94509492505050565b6000806000611185613672565b6001600160a01b0316631137aedf856040516001600160e01b031960e084901b1681526001600160a01b03909116600482015260240160606040518083038186806111ce614dd2565b1580156111e3576000806111e0614d0c565b50505b505a6111ed614e1e565b505050505015801561120c573d6000803e3d6000611209614d0c565b50505b505050506040513d606081101561122b57600080611228614d0c565b50505b81019080805192919060200180519291906020018051949993985093965091945050505050565b600061125c613844565b90565b611267613896565b806003600181611275614d77565b816001600160a01b0302191690836001600160a01b0316021790611297614fcf565b50505050565b6112a5613896565b80600180806112b2614d77565b816001600160a01b0302191690836001600160a01b03160217906112d4614fcf565b5050507f906a1c6bd7e3091ea86693dd029a831c19049ce77f1dce2ce0bab1cacbabce22816040516001600160a01b03909116815260200160405180910390a150565b6000611321613672565b6001600160a01b03166316b2213f836040516001600160e01b031960e084901b1681526001600160a01b0390911660048201526024016020604051808303818680610f41614dd2565b6008611374614d77565b81565b60405160408082019052601781527f53796e746865746978204e6574776f726b20546f6b656e000000000000000000602082015281565b6000806113b961390e565b6001600160a01b031663059c29ec600060046113d3614d77565b906101000a90046001600160a01b0316856040516001600160e01b031960e085901b1681526001600160a01b03909216600483015260248201526044016020604051808303818680611423614dd2565b15801561143857600080611435614d0c565b50505b505a611442614e1e565b5050505050158015611461573d6000803e3d600061145e614d0c565b50505b505050506040513d60208110156114805760008061147d614d0c565b50505b8101908080519390931195945050505050565b600061149d61368b565b6114a5613925565b6114af848361399b565b506114d7600060046114bf614d77565b906101000a90046001600160a01b0316858585613cb0565b949350505050565b6114e7613de2565b6114ef61368b565b6114f7613672565b6001600160a01b031663b06e8c6560006004611511614d77565b906101000a90046001600160a01b0316836040516001600160e01b031960e085901b1681526001600160a01b039092166004830152602482015260440160006040518083038160008780611563614dd2565b15801561157857600080611575614d0c565b50505b505a611582614f09565b5050505050501580156115a2573d6000803e3d600061159f614d0c565b50505b505050505b50565b6040516040808201905260038152620a69cb60eb1b602082015281565b600060606115d3612784565b905060005b815181101561174d5760008282815181106115ef57fe5b60200260200101516000818152600a60205290915060409020600090611613614d77565b6001600160a01b036101009290920a90041660016009611631614d77565b906101000a90046001600160a01b03166001600160a01b03166321f8a721836040516001600160e01b031960e084901b16815260048101919091526024016020604051808303818680611682614dd2565b15801561169757600080611694614d0c565b50505b505a6116a1614e1e565b50505050501580156116c0573d6000803e3d60006116bd614d0c565b50505b505050506040513d60208110156116df576000806116dc614d0c565b50505b8101908080516001600160a01b031693909314159250829150611733905057506000818152600a60205260408120600090611718614d77565b906101000a90046001600160a01b03166001600160a01b0316145b15611744576000935050505061125c565b506001016115d8565b50600191505090565b61175e613de2565b61176661368b565b61176e613672565b6001600160a01b0316632b3f41aa8260006004611789614d77565b906101000a90046001600160a01b03166040516001600160e01b031960e085901b1681526001600160a01b0392831660048201529116602482015260440160006040518083038160008780611563614dd2565b6117e4613e1d565b600060026117f0614d77565b906101000a90046001600160a01b03166001600160a01b031663907dff9784848460405160200180848152602001838152602001828152602001935050505060405160208183030381529060405260026040518060316150c1823960310190506040518091039020886000806040518763ffffffff1660e01b815260040180806020018781526020018681526020018581526020018460001b81526020018360001b8152602001828103825288818151815260200191508051906020019080838360005b838110156118cc5780820151838201526020016118b4565b50505050905090810190601f1680156118f95780820380516001836020036101000a031916815260200191505b5097505050505050505060006040518083038160008780611918614dd2565b15801561192d5760008061192a614d0c565b50505b505a611937614f09565b505050505050158015611957573d6000803e3d6000611954614d0c565b50505b5050505050505050565b601281565b600085846119748282613e97565b61197c61368b565b61198461390e565b6001600160a01b0316634f8633d26000600461199e614d77565b906101000a90046001600160a01b031660046000906119bb614d77565b906101000a90046001600160a01b03168b8b8b60046000906119db614d77565b906101000a90046001600160a01b031660008d8d6040516001600160e01b031960e08c901b1681526001600160a01b03998a1660048201529789166024890152604488019690965260648701949094526084860192909252851660a4850152151560c484015290921660e482015261010481019190915261012401604080518083038160008780611a6a614dd2565b158015611a7f57600080611a7c614d0c565b50505b505a611a89614f09565b505050505050158015611aa9573d6000803e3d6000611aa6614d0c565b50505b505050506040513d6040811015611ac857600080611ac5614d0c565b50505b810190808051929190602001805150929b9a5050505050505050505050565b60006009611af3614d77565b906101000a900460ff1681565b611b08613de2565b611b1061368b565b611b18613672565b6001600160a01b031663fd864ccf8260006004611789614d77565b6000611b3d613672565b6001600160a01b03166332608039836040516001600160e01b031960e084901b16815260048101919091526024016020604051808303818680610f41614dd2565b6000611b88613672565b6001600160a01b0316637b1001b78360016040516001600160e01b031960e085901b1681526004810192909252151560248201526044016020604051808303818680610f41614dd2565b6000611bdc613672565b6001600160a01b0316634e99bda96040518163ffffffff1660e01b81526004016020604051808303818680611c0f614dd2565b158015611c2457600080611c21614d0c565b50505b505a611c2e614e1e565b5050505050158015611c4d573d6000803e3d6000611c4a614d0c565b50505b505050506040513d6020811015611c6c57600080611c69614d0c565b50505b810190808051935050505090565b60006001610edb614d77565b6000611c90613844565b95945050505050565b611ca1613f21565b611cab8282613f9b565b5050565b6000611cb9613672565b6001600160a01b0316636bed04158360006005611cd4614d77565b906101000a90046001600160a01b03166001600160a01b03166370a08231866040516001600160e01b031960e084901b1681526001600160a01b0390911660048201526024016020604051808303818680611d2d614dd2565b158015611d4257600080611d3f614d0c565b50505b505a611d4c614e1e565b5050505050158015611d6b573d6000803e3d6000611d68614d0c565b50505b505050506040513d6020811015611d8a57600080611d87614d0c565b50505b81019080805192506040915050516001600160e01b031960e085901b1681526001600160a01b039092166004830152602482015260440160408051808303818680611dd3614dd2565b158015611de857600080611de5614d0c565b50505b505a611df2614e1e565b5050505050158015611e11573d6000803e3d6000611e0e614d0c565b50505b505050506040513d6040811015611e3057600080611e2d614d0c565b50505b8101908080519291906020018051509295945050505050565b611e51613e1d565b60006002611e5d614d77565b906101000a90046001600160a01b03166001600160a01b031663907dff9786868686866040516020810195909552604080860194909452606085019290925260808401526001600160a01b031660a083015260c09091019051602081830303815290604052600260405180603e6150f28239603e0190506040518091039020611ee58b61419d565b6000806040518763ffffffff1660e01b815260040180806020018781526020018681526020018581526020018460001b81526020018360001b8152602001828103825288818151815260200191508051906020019080838360005b83811015611f58578082015183820152602001611f40565b50505050905090810190601f168015611f855780820380516001836020036101000a031916815260200191505b5097505050505050505060006040518083038160008780611fa4614dd2565b158015611fb957600080611fb6614d0c565b50505b505a611fc3614f09565b505050505050158015611954573d6000803e3d6000611fe0614d0c565b505050505050505050505050565b611ff6613e1d565b60006002612002614d77565b906101000a90046001600160a01b03166001600160a01b031663907dff978383604051602001808381526020018281526020019250505060405160208183030381529060405260026040518060276151a682396027019050604051809103902061206b8861419d565b6000806040518763ffffffff1660e01b815260040180806020018781526020018681526020018581526020018460001b81526020018360001b8152602001828103825288818151815260200191508051906020019080838360005b838110156120de5780820151838201526020016120c6565b50505050905090810190601f16801561210b5780820380516001836020036101000a031916815260200191505b509750505050505050506000604051808303816000878061212a614dd2565b15801561213f5760008061213c614d0c565b50505b505a612149614f09565b50505050505015801561159f573d6000803e3d6000612166614d0c565b505050505050505050565b600080600561217e614d77565b906101000a90046001600160a01b03166001600160a01b03166370a08231836040516001600160e01b031960e084901b1681526001600160a01b0390911660048201526024016020604051808303818680610f41614dd2565b60606121e1613672565b6001600160a01b03166372cb051f6040518163ffffffff1660e01b81526004016000604051808303818680612214614dd2565b15801561222957600080612226614d0c565b50505b505a612233614e1e565b5050505050158015612252573d6000803e3d600061224f614d0c565b50505b505050506040513d6000823e601f3d908101601f19168201604052602081101561228457600080612281614d0c565b50505b81019080805160405193929190846401000000008211156122ad576000806122aa614d0c565b50505b9083019060208201858111156122cb576000806122c8614d0c565b50505b82518660208202830111640100000000821117156122f1576000806122ee614d0c565b50505b825250602001908051906020019060200280838360005b83811015612320578082015183820152602001612308565b50505050905001604052505050905090565b606061233c612784565b905060005b8151811015611cab57600082828151811061235857fe5b60200260200101519050600060016009612370614d77565b906101000a90046001600160a01b03166001600160a01b031663dacb2d0183846040517f5265736f6c766572206d697373696e67207461726765743a2000000000000000602082015260398101919091526059016040516020818303038152906040526040518363ffffffff1660e01b81526004018083815260200180602001828103825283818151815260200191508051906020019080838360005b8381101561242557808201518382015260200161240d565b50505050905090810190601f1680156124525780820380516001836020036101000a031916815260200191505b509350505050602060405180830381868061246b614dd2565b1580156124805760008061247d614d0c565b50505b505a61248a614e1e565b50505050501580156124a9573d6000803e3d60006124a6614d0c565b50505b505050506040513d60208110156124c8576000806124c5614d0c565b50505b8101908080516000868152600a602052909450849350604092509050206001816124f0614d77565b816001600160a01b0302191690836001600160a01b0316021790612512614fcf565b5050507f88a93678a3692f6789d9546fc621bf7234b101ddb7d4fe479455112831b8aa6882826040519182526001600160a01b031660208201526040908101905180910390a15050600101612341565b6000600161256e614d77565b906101000a90046001600160a01b03166001600160a01b03165a61259061501d565b6001600160a01b0316146125de5760405162461bcd60e51b815260040180806020018281038252603581526020018061506460359139604001915050604051809103906125db614d0c565b50505b7fb532073b38c83145e3e5135377a08bf9aab55bc0fd7c1179cd4fb995d2a5159c60008061260a614d77565b906101000a90046001600160a01b03166001600090612627614d77565b906101000a90046001600160a01b03166040516001600160a01b039283168152911660208201526040908101905180910390a160006001612666614d77565b906101000a90046001600160a01b03166000806101000a81612686614d77565b816001600160a01b0302191690836001600160a01b03160217906126a8614fcf565b5050506000600160006101000a816126be614d77565b816001600160a01b0302191690836001600160a01b03160217906126e0614fcf565b505050565b60006126ef613672565b6001600160a01b031663835e119c836040516001600160e01b031960e084901b16815260048101919091526024016020604051808303818680610f41614dd2565b600061273a613672565b6001600160a01b0316637b1001b78360006040516001600160e01b031960e085901b1681526004810192909252151560248201526044016020604051808303818680610f41614dd2565b60608061278f6141a9565b9050606060016040519080825280602002602001820160405280156127be578160200160208202803883390190505b5090507453796e746865746978427269646765546f4261736560581b816000815181106127e757fe5b6020026020010181815250506127fd82826142a9565b9250505090565b61280c613de2565b61281461368b565b61281c613672565b6001600160a01b031663042e068860006004611511614d77565b600080610edb614d77565b6000858461284f8282613e97565b61285761368b565b61285f61390e565b6001600160a01b0316634f8633d28a6000600461287a614d77565b906101000a90046001600160a01b03168b8b8b8f60008d8d6040516001600160e01b031960e08c901b1681526001600160a01b03998a1660048201529789166024890152604488019690965260648701949094526084860192909252851660a4850152151560c484015290921660e48201526101048101919091526101240160408051808303816000878061290d614dd2565b1580156129225760008061291f614d0c565b50505b505a61292c614f09565b50505050505015801561294c573d6000803e3d6000612949614d0c565b50505b505050506040513d604081101561296b57600080612968614d0c565b50505b810190808051929190602001805150929c9b505050505050505050505050565b631cd554d160e21b81565b6007806129a1614d77565b600181600116156101000203166002900480601f0160208091040260200160405190810160405281815291906020830182806129db614d77565b6001816001161561010002031660029004801561105e5780601f10612a0757610100808361101e614d77565b820191906000526020600020905b81612a1e614d77565b81529060010190602001808311612a155750859350505050565b612a40613896565b806002600181612a4e614d77565b816001600160a01b0302191690836001600160a01b0316021790612a70614fcf565b5050507ffc80377ca9c49cc11ae6982f390a42db976d5530af7c43889264b13fbbd7c57e816040516001600160a01b03909116815260200160405180910390a150565b612abb613de2565b612ac361368b565b612acb613672565b6001600160a01b031663497d704a60006004612ae5614d77565b906101000a90046001600160a01b03166040516001600160e01b031960e084901b1681526001600160a01b03909116600482015260240160006040518083038160008780612b31614dd2565b158015612b4657600080612b43614d0c565b50505b505a612b50614f09565b505050505050158015611297573d6000803e3d6000612b6d614d0c565b5050505050505b565b6000806000612b8361368b565b612b8b61390e565b6001600160a01b0316631b16802c60006004612ba5614d77565b906101000a90046001600160a01b0316866040516001600160e01b031960e085901b1681526001600160a01b039092166004830152602482015260440160606040518083038160008780612bf7614dd2565b158015612c0c57600080612c09614d0c565b50505b505a612c16614f09565b50505050505015801561120c573d6000803e3d6000611209614d0c565b60006003610edb614d77565b612c47614365565b806005600181612c55614d77565b816001600160a01b0302191690836001600160a01b0316021790612c77614fcf565b5050506115a7816144f0565b6000612c8d613672565b6001600160a01b031663a311c7c2836040516001600160e01b031960e084901b1681526001600160a01b0390911660048201526024016020604051808303818680610f41614dd2565b6000612ce0613672565b6001600160a01b031663a5fdc5de836040516001600160e01b031960e084901b1681526001600160a01b0390911660048201526024016020604051808303818680610f41614dd2565b6000612d3361368b565b612d3b613925565b612d6060006004612d4a614d77565b906101000a90046001600160a01b03168361399b565b50612d8760006004612d70614d77565b906101000a90046001600160a01b03168484614638565b5060019392505050565b612d99613e1d565b60006002612da5614d77565b906101000a90046001600160a01b03166001600160a01b031663907dff9783836040516020018083815260200182815260200192505050604051602081830303815290604052600260405180602861509982396028019050604051809103902061206b8861419d565b612e16613de2565b612e1e61368b565b612e26613672565b6001600160a01b031663c897713260006004612ae5614d77565b612e48614645565b806004600181611275614d77565b612e5e613de2565b612e6661368b565b612e6e613672565b6001600160a01b0316639a5154b48360006004612e89614d77565b906101000a90046001600160a01b0316846040516001600160e01b031960e086901b1681526001600160a01b039384166004820152919092166024820152604481019190915260640160006040518083038160008780612ee7614dd2565b158015612efc57600080612ef9614d0c565b50505b505a612f06614f09565b505050505050158015612b6d573d6000803e3d6000611957614d0c565b60008382612f318282613e97565b612f3961368b565b612f4161390e565b6001600160a01b0316634f8633d28860006004612f5c614d77565b906101000a90046001600160a01b03168989898d60008f6000801b6040516001600160e01b031960e08c901b1681526001600160a01b03998a1660048201529789166024890152604488019690965260648701949094526084860192909252851660a4850152151560c484015290921660e482015261010481019190915261012401604080518083038160008780612ff2614dd2565b15801561300757600080613004614d0c565b50505b505a613011614f09565b505050505050158015613031573d6000803e3d600061302e614d0c565b50505b505050506040513d60408110156130505760008061304d614d0c565b50505b810190808051929190602001805150929a9950505050505050505050565b6000613078613672565b6001600160a01b031663d37c4d8b84846040516001600160e01b031960e085901b1681526001600160a01b039092166004830152602482015260440160206040518083038186806130c7614dd2565b1580156130dc576000806130d9614d0c565b50505b505a6130e6614e1e565b5050505050158015613105573d6000803e3d6000613102614d0c565b50505b505050506040513d602081101561312457600080613121614d0c565b50505b8101908080519695505050505050565b60006004610edb614d77565b613148613f21565b6000613152614712565b905061315e8183613f9b565b806001600160a01b03166359974e38836040516001600160e01b031960e084901b1681526004810191909152602401602060405180830381600087806131a2614dd2565b1580156131b7576000806131b4614d0c565b50505b505a6131c1614f09565b5050505050501580156131e1573d6000803e3d60006131de614d0c565b50505b505050506040513d6020811015613200576000806131fd614d0c565b50505b810190808051505050505050565b6000613218613672565b6001600160a01b031663dbf633406040518163ffffffff1660e01b81526004016020604051808303818680611c0f614dd2565b6000806005613258614d77565b906101000a90046001600160a01b03166001600160a01b031663dd62ed3e84846040516001600160e01b031960e085901b1681526001600160a01b0392831660048201529116602482015260440160206040518083038186806130c7614dd2565b600061115e613844565b6132cb613de2565b6132d361368b565b6132db613672565b6001600160a01b03166344ec6b628360006004612e89614d77565b60006005610edb614d77565b60006002610edb614d77565b613316613f21565b61331e613925565b6000600561332a614d77565b6001600160a01b036101009290920a90041663b46310f68361341f8460006005613352614d77565b906101000a90046001600160a01b03166001600160a01b03166370a08231886040516001600160e01b031960e084901b1681526001600160a01b03909116600482015260240160206040518083038186806133ab614dd2565b1580156133c0576000806133bd614d0c565b50505b505a6133ca614e1e565b50505050501580156133e9573d6000803e3d60006133e6614d0c565b50505b505050506040513d602081101561340857600080613405614d0c565b50505b8101908080519392505063ffffffff614733169050565b6040516001600160e01b031960e085901b1681526001600160a01b039092166004830152602482015260440160006040518083038160008780613460614dd2565b15801561347557600080613472614d0c565b50505b505a61347f614f09565b50505050505015801561349f573d6000803e3d600061349c614d0c565b50505b505050506134af82600083614798565b6134c98160086134bd614d77565b9063ffffffff61473316565b8060086115a2614fcf565b600083826134e28282613e97565b6134ea61368b565b6134f261390e565b6001600160a01b0316634f8633d26000600461350c614d77565b906101000a90046001600160a01b03166004600090613529614d77565b906101000a90046001600160a01b03168989896004600090613549614d77565b906101000a90046001600160a01b031660006004600090613568614d77565b906101000a90046001600160a01b03166000801b6040516001600160e01b031960e08c901b1681526001600160a01b03998a1660048201529789166024890152604488019690965260648701949094526084860192909252851660a4850152151560c484015290921660e4820152610104810191909152610124016040805180830381600087806135f7614dd2565b15801561360c57600080613609614d0c565b50505b505a613616614f09565b505050505050158015613636573d6000803e3d6000613633614d0c565b50505b505050506040513d604081101561365557600080613652614d0c565b50505b810190808051929190602001805150929998505050505050505050565b60006136866524b9b9bab2b960d11b614805565b905090565b60006002613697614d77565b906101000a90046001600160a01b03166001600160a01b03165a6136b961501d565b6001600160a01b0316141580156137055750600060036136d7614d77565b906101000a90046001600160a01b03166001600160a01b03165a6136f961501d565b6001600160a01b031614155b801561374657505a61371561501d565b6001600160a01b03166000600461372a614d77565b906101000a90046001600160a01b03166001600160a01b031614155b15612b74575a61375461501d565b60046001816126be614d77565b6000600261376d614d77565b906101000a90046001600160a01b03166001600160a01b031663907dff97826040516020018082815260200191505060405160208183030381529060405260036040518060216151858239602101905060405180910390206137ce8861419d565b6137d78861419d565b60006040518763ffffffff1660e01b815260040180806020018781526020018681526020018581526020018481526020018360001b8152602001828103825288818151815260200191508051602090910190808383600083156120de5780820151838201526020016120c6565b60405162461bcd60e51b815260206004820152601b60248201527f43616e6e6f742062652072756e206f6e2074686973206c617965720000000000604482015260640160405180910390611cab614d0c565b6000806138a1614d77565b906101000a90046001600160a01b03166001600160a01b03165a6138c361501d565b6001600160a01b031614612b745760405162461bcd60e51b815260040180806020018281038252602f815260200180615156602f913960400191505060405180910390611cab614d0c565b60006136866822bc31b430b733b2b960b91b614805565b61392d614906565b6001600160a01b031663086dabd16040518163ffffffff1660e01b81526004016000604051808303818680613960614dd2565b15801561397557600080613972614d0c565b50505b505a61397f614e1e565b5050505050158015611297573d6000803e3d6000612b6d614d0c565b6000806139a6614920565b6001600160a01b0316638b3f8088856040516001600160e01b031960e084901b1681526001600160a01b039091166004820152602401604080518083038186806139ee614dd2565b158015613a0357600080613a00614d0c565b50505b505a613a0d614e1e565b5050505050158015613a2c573d6000803e3d6000613a29614d0c565b50505b505050506040513d6040811015613a4b57600080613a48614d0c565b50505b8101908080519291906020018051509293505082159150611158905057600080613a73613672565b6001600160a01b0316636bed04158760006005613a8e614d77565b906101000a90046001600160a01b03166001600160a01b03166370a082318a6040516001600160e01b031960e084901b1681526001600160a01b0390911660048201526024016020604051808303818680613ae7614dd2565b158015613afc57600080613af9614d0c565b50505b505a613b06614e1e565b5050505050158015613b25573d6000803e3d6000613b22614d0c565b50505b505050506040513d6020811015613b4457600080613b41614d0c565b50505b81019080805192506040915050516001600160e01b031960e085901b1681526001600160a01b039092166004830152602482015260440160408051808303818680613b8d614dd2565b158015613ba257600080613b9f614d0c565b50505b505a613bac614e1e565b5050505050158015613bcb573d6000803e3d6000613bc8614d0c565b50505b505050506040513d6040811015613bea57600080613be7614d0c565b50505b810190808051929190602001805193955092935050505081851115613c495760405162461bcd60e51b81526004018080602001828103825260268152602001806151306026913960400191505060405180910390613c46614d0c565b50505b8015613ca45760405162461bcd60e51b815260206004820152601e60248201527f412073796e7468206f7220534e58207261746520697320696e76616c69640000604482015260640160405180910390613ca1614d0c565b50505b50600195945050505050565b6000806005613cbd614d77565b6001600160a01b036101009290920a90041663da46098c8587613d478660006005613ce6614d77565b906101000a90046001600160a01b03166001600160a01b031663dd62ed3e8b8d6040516001600160e01b031960e085901b1681526001600160a01b0392831660048201529116602482015260440160206040518083038186806133ab614dd2565b6040516001600160e01b031960e086901b1681526001600160a01b039384166004820152919092166024820152604481019190915260640160006040518083038160008780613d94614dd2565b158015613da957600080613da6614d0c565b50505b505a613db3614f09565b505050505050158015613dd3573d6000803e3d6000613dd0614d0c565b50505b50505050611c9084848461493c565b613dea614906565b6001600160a01b0316637c3125416040518163ffffffff1660e01b81526004016000604051808303818680613960614dd2565b613e2561390e565b6001600160a01b03165a613e3761501d565b6001600160a01b031614612b745760405162461bcd60e51b815260206004820152601e60248201527f4f6e6c792045786368616e6765722063616e20696e766f6b6520746869730000604482015260640160405180910390611cab614d0c565b613e9f614906565b6001600160a01b0316631ce00ba283836040516001600160e01b031960e085901b168152600481019290925260248201526044016000604051808303818680613ee6614dd2565b158015613efb57600080613ef8614d0c565b50505b505a613f05614e1e565b5050505050158015612b6d573d6000803e3d6000611957614d0c565b613f29614c80565b6001600160a01b03165a613f3b61501d565b6001600160a01b031614612b745760405162461bcd60e51b815260206004820152601d60248201527f43616e206f6e6c7920626520696e766f6b656420627920627269646765000000604482015260640160405180910390611cab614d0c565b60006005613fa7614d77565b6001600160a01b036101009290920a90041663b46310f68361409c8460006005613fcf614d77565b906101000a90046001600160a01b03166001600160a01b03166370a08231886040516001600160e01b031960e084901b1681526001600160a01b0390911660048201526024016020604051808303818680614028614dd2565b15801561403d5760008061403a614d0c565b50505b505a614047614e1e565b5050505050158015614066573d6000803e3d6000614063614d0c565b50505b505050506040513d602081101561408557600080614082614d0c565b50505b8101908080519392505063ffffffff614ca3169050565b6040516001600160e01b031960e085901b1681526001600160a01b0390921660048301526024820152604401600060405180830381600087806140dd614dd2565b1580156140f2576000806140ef614d0c565b50505b505a6140fc614f09565b50505050505015801561411c573d6000803e3d6000614119614d0c565b50505b505050506141835a63996d79a5598160e01b8152602081600483336000905af158600e01573d6000803e3d6000fd5b3d6001141558600a015760016000f35b8051925060005b604081101561417957600082820152602001614162565b5050508383614798565b6134c9816008614191614d77565b9063ffffffff614ca316565b6001600160a01b031690565b606060056040519080825280602002602001820160405280156141d6578160200160208202803883390190505b5090506d53796e746865746978537461746560901b816000815181106141f857fe5b6020026020010181815250506b53797374656d53746174757360a01b8160018151811061422157fe5b6020026020010181815250506822bc31b430b733b2b960b91b8160028151811061424757fe5b6020026020010181815250506524b9b9bab2b960d11b8160038151811061426a57fe5b602002602001018181525050722932bbb0b93239a234b9ba3934b13aba34b7b760691b8160048151811061429a57fe5b60200260200101818152505090565b606081518351016040519080825280602002602001820160405280156142d9578160200160208202803883390190505b50905060005b835181101561431b578381815181106142f457fe5b602002602001015182828151811061430857fe5b60209081029190910101526001016142df565b5060005b825181101561435e5782818151811061433457fe5b602002602001015182828651018151811061434b57fe5b602090810291909101015260010161431f565b5092915050565b60006002614371614d77565b906101000a90046001600160a01b03166001600160a01b03165a61439361501d565b6001600160a01b0316141580156143df5750600060036143b1614d77565b906101000a90046001600160a01b03166001600160a01b03165a6143d361501d565b6001600160a01b031614155b801561442057505a6143ef61501d565b6001600160a01b031660006004614404614d77565b906101000a90046001600160a01b03166001600160a01b031614155b15614461575a61442e61501d565b600460018161443b614d77565b816001600160a01b0302191690836001600160a01b031602179061445d614fcf565b5050505b60008061446c614d77565b6001600160a01b036101009290920a9004166000600461448a614d77565b906101000a90046001600160a01b03166001600160a01b031614612b745760405162461bcd60e51b815260206004820152601360248201527227bbb732b91037b7363c90333ab731ba34b7b760691b604482015260640160405180910390611cab614d0c565b600060026144fc614d77565b906101000a90046001600160a01b03166001600160a01b031663907dff97826040516001600160a01b0390911660208201526040908101905160208183030381529060405260016040517f546f6b656e5374617465557064617465642861646472657373290000000000008152601a01604051809103902060008060006040518763ffffffff1660e01b815260040180806020018781526020018681526020018560001b81526020018460001b81526020018360001b8152602001828103825288818151815260200191508051906020019080838360005b838110156145ec5780820151838201526020016145d4565b50505050905090810190601f1680156146195780820380516001836020036101000a031916815260200191505b5097505050505050505060006040518083038160008780611563614dd2565b60006114d784848461493c565b60006002614651614d77565b906101000a90046001600160a01b03166001600160a01b03165a61467361501d565b6001600160a01b031614806146bc57506000600361468f614d77565b906101000a90046001600160a01b03166001600160a01b03165a6146b161501d565b6001600160a01b0316145b612b745760405162461bcd60e51b815260206004820152601760248201527f4f6e6c79207468652070726f78792063616e2063616c6c000000000000000000604482015260640160405180910390611cab614d0c565b6000613686722932bbb0b93239a234b9ba3934b13aba34b7b760691b614805565b6000828211156147925760405162461bcd60e51b815260206004820152601e60248201527f536166654d6174683a207375627472616374696f6e206f766572666c6f77000060448201526064016040518091039061478f614d0c565b50505b50900390565b600060026147a4614d77565b906101000a90046001600160a01b03166001600160a01b031663907dff97826040516020018082815260200191505060405160208183030381529060405260036040518060216151cd8239602101905060405180910390206137ce8861419d565b6000818152600a602052806040812060009061481f614d77565b6001600160a01b036101009290920a90041690508015158360405170026b4b9b9b4b7339030b2323932b9b99d1607d1b602082015260318101919091526051016040516020818303038152906040529061435e5760405162461bcd60e51b81526004018080602001828103825283818151815260200191508051906020019080838360005b838110156148bc5780820151838201526020016148a4565b50505050905090810190601f1680156148e95780820380516001836020036101000a031916815260200191505b5092505050604051809103906148fd614d0c565b50505092915050565b60006136866b53797374656d53746174757360a01b614805565b60006136866d53796e746865746978537461746560901b614805565b60006001600160a01b038316158015906149c057505a63996d79a5598160e01b8152602081600483336000905af158600e01573d6000803e3d6000fd5b3d6001141558600a015760016000f35b8051925060005b60408110156149a757600082820152602001614990565b5050506001600160a01b0316836001600160a01b031614155b80156149f95750600060026149d3614d77565b906101000a90046001600160a01b03166001600160a01b0316836001600160a01b031614155b614a525760405162461bcd60e51b815260206004820152601f60248201527f43616e6e6f74207472616e7366657220746f2074686973206164647265737300604482015260640160405180910390614a4f614d0c565b50505b60006005614a5e614d77565b6001600160a01b036101009290920a90041663b46310f685614adf8560006005614a86614d77565b906101000a90046001600160a01b03166001600160a01b03166370a082318a6040516001600160e01b031960e084901b1681526001600160a01b03909116600482015260240160206040518083038186806133ab614dd2565b6040516001600160e01b031960e085901b1681526001600160a01b039092166004830152602482015260440160006040518083038160008780614b20614dd2565b158015614b3557600080614b32614d0c565b50505b505a614b3f614f09565b505050505050158015614b5f573d6000803e3d6000614b5c614d0c565b50505b505050506005600090614b70614d77565b6001600160a01b036101009290920a90041663b46310f684614bf18560006005614b98614d77565b906101000a90046001600160a01b03166001600160a01b03166370a08231896040516001600160e01b031960e084901b1681526001600160a01b0390911660048201526024016020604051808303818680614028614dd2565b6040516001600160e01b031960e085901b1681526001600160a01b039092166004830152602482015260440160006040518083038160008780614c32614dd2565b158015614c4757600080614c44614d0c565b50505b505a614c51614f09565b505050505050158015614c71573d6000803e3d6000614c6e614d0c565b50505b50505050612d87848484614798565b60006136867453796e746865746978427269646765546f4261736560581b614805565b600082820183811015614d055760405162461bcd60e51b815260206004820152601b60248201527f536166654d6174683a206164646974696f6e206f766572666c6f770000000000604482015260640160405180910390614d02614d0c565b50505b9392505050565b632a2a7adb598160e01b8152600481016020815285602082015260005b86811015614d44578086015182820160400152602001614d29565b506020828760640184336000905af158600e01573d6000803e3d6000fd5b3d6001141558600a015760016000f35b505050565b6303daa959598160e01b8152836004820152602081602483336000905af158600e01573d6000803e3d6000fd5b3d6001141558600a015760016000f35b8051935060005b60408110156126e057600082820152602001614dbb565b638435035b598160e01b8152836004820152602081602483336000905af158600e01573d6000803e3d6000fd5b3d6001141558600a015760016000f35b80516000825293506020614dbb565b638540661f598160e01b8152614e4f565b808083111561115e575090919050565b808083101561115e575090919050565b836004820152846024820152606060448201528660648201526084810160005b88811015614e87578088015182820152602001614e6f565b506060828960a40184336000905af158600e01573d6000803e3d6000fd5b3d6001141558600a015760016000f35b815160408301513d6000853e8b8b82606087013350600060045af15059614edc8d3d614e3f565b8c01614ee88187614e2f565b5b82811015614efd5760008152602001614ee9565b50929c50505050505050565b6385979f76598160e01b8152836004820152846024820152606060448201528760648201526084810160005b89811015614f4d578089015182820152602001614f35565b506060828a60a40184336000905af158600e01573d6000803e3d6000fd5b3d6001141558600a015760016000f35b815160408301513d6000853e8c8c82606087013350600060045af15059614fa28e3d614e3f565b8d01614fae8187614e2f565b5b82811015614fc35760008152602001614faf565b50929d50505050505050565b6322bd64c0598160e01b8152836004820152846024820152600081604483336000905af158600e01573d6000803e3d6000fd5b3d6001141558600a015760016000f35b600081526020614dbb565b6373509064598160e01b8152602081600483336000905af158600e01573d6000803e3d6000fd5b3d6001141558600a015760016000f35b80516000825293506020614dbb56fe596f75206d757374206265206e6f6d696e61746564206265666f726520796f752063616e20616363657074206f776e65727368697045786368616e67655265636c61696d28616464726573732c627974657333322c75696e743235362945786368616e6765547261636b696e6728627974657333322c627974657333322c75696e743235362c75696e743235362953796e746845786368616e676528616464726573732c627974657333322c75696e743235362c627974657333322c75696e743235362c616464726573732943616e6e6f74207472616e73666572207374616b6564206f7220657363726f77656420534e584f6e6c792074686520636f6e7472616374206f776e6572206d617920706572666f726d207468697320616374696f6e417070726f76616c28616464726573732c616464726573732c75696e743235362945786368616e676552656261746528616464726573732c627974657333322c75696e74323536295472616e7366657228616464726573732c616464726573732c75696e7432353629", + "bytecode": "60806040523480156200001157600080fd5b50604051620042f7380380620042f7833981810160405260a08110156200003757600080fd5b508051602080830151604080850151606086015160809096015182518084018452601781527f53796e746865746978204e6574776f726b20546f6b656e00000000000000000081870152835180850190945260038452620a69cb60eb1b958401959095529495929490939091869186918691869186918291879187918660128986816001600160a01b03811662000115576040805162461bcd60e51b815260206004820152601960248201527f4f776e657220616464726573732063616e6e6f74206265203000000000000000604482015290519081900360640190fd5b600080546001600160a01b0319166001600160a01b038316908117825560408051928352602083019190915280517fb532073b38c83145e3e5135377a08bf9aab55bc0fd7c1179cd4fb995d2a5159c9281900390910190a1506000546001600160a01b0316620001c0576040805162461bcd60e51b815260206004820152601160248201527013dddb995c881b5d5cdd081899481cd95d607a1b604482015290519081900360640190fd5b600280546001600160a01b0383166001600160a01b0319909116811790915560408051918252517ffc80377ca9c49cc11ae6982f390a42db976d5530af7c43889264b13fbbd7c57e9181900360200190a150600480546001600160a01b0319166001600160a01b038816179055845162000242906005906020880190620002ab565b50835162000258906006906020870190620002ab565b50506007919091556008805460ff191660ff90921691909117610100600160a81b0319166101006001600160a01b0397909716969096029590951790945550620003509c50505050505050505050505050565b828054600181600116156101000203166002900490600052602060002090601f016020900481019282601f10620002ee57805160ff19168380011785556200031e565b828001600101855582156200031e579182015b828111156200031e57825182559160200191906001019062000301565b506200032c92915062000330565b5090565b6200034d91905b808211156200032c576000815560010162000337565b90565b613f9780620003606000396000f3fe608060405234801561001057600080fd5b50600436106103f15760003560e01c80637418536011610215578063ace88afd11610125578063d8a1f76f116100b8578063e8e09b8b11610087578063e8e09b8b14610c94578063e90dd9e214610cc0578063ec55688914610cc8578063edef719a14610cd0578063ee52a2f314610cfc576103f1565b8063d8a1f76f14610c15578063dbf6334014610c32578063dd62ed3e14610c3a578063e6203ed114610c68576103f1565b8063c62e5df8116100f4578063c62e5df814610b7a578063c836fa0a14610ba9578063d37c4d8b14610be1578063d67bdd2514610c0d576103f1565b8063ace88afd14610aee578063af086c7e14610b20578063bc67f83214610b28578063c2bf388014610b4e576103f1565b80639324cac7116101a8578063987757dd11610177578063987757dd14610a335780639f76980714610a50578063a311c7c214610a76578063a5fdc5de14610a9c578063a9059cbb14610ac2576103f1565b80639324cac7146109f557806395d89b41146109fd57806397107d6d14610a055780639741fb2214610a2b576103f1565b8063899ffef4116101e4578063899ffef4146109825780638a2900141461098a5780638da5cb5b146109a757806391e56b68146109af576103f1565b8063741853601461093857806379ba509714610940578063835e119c1461094857806383d625d414610965576103f1565b80632c955fa7116103105780634e99bda9116102a35780636ac0bf9c116102725780636ac0bf9c1461081c5780636c00f310146108425780636f01a9861461088857806370a08231146108ba57806372cb051f146108e0576103f1565b80634e99bda9146107a257806353a47bb7146107aa5780635af090ef146107b2578063666ed4f1146107f0576103f1565b8063313ce567116102df578063313ce5671461073a578063320223db1461074257806332608039146107685780633e89b9e514610785576103f1565b80632c955fa7146106895780632d3169eb146106af5780632e0f2625146106de57806330ead760146106fc576103f1565b806316b2213f1161038857806323b872dd1161035757806323b872dd14610626578063295da87d1461065c5780632a905318146106795780632af64bd314610681576103f1565b806316b2213f146105d357806318160ddd146105f957806318821400146106015780631fce304d14610609576103f1565b80630e30963c116103c45780630e30963c1461050f5780631137aedf1461055f5780631249c58b146105a35780631627540c146105ab576103f1565b806304f3bcec146103f657806305b3c1c91461041a57806306fdde0314610452578063095ea7b3146104cf575b600080fd5b6103fe610d25565b604080516001600160a01b039092168252519081900360200190f35b6104406004803603602081101561043057600080fd5b50356001600160a01b0316610d39565b60408051918252519081900360200190f35b61045a610dca565b6040805160208082528351818301528351919283929083019185019080838360005b8381101561049457818101518382015260200161047c565b50505050905090810190601f1680156104c15780820380516001836020036101000a031916815260200191505b509250505060405180910390f35b6104fb600480360360408110156104e557600080fd5b506001600160a01b038135169060200135610e58565b604080519115158252519081900360200190f35b61053e6004803603608081101561052557600080fd5b5080359060208101359060408101359060600135610ef3565b604080519283526001600160a01b0390911660208301528051918290030190f35b6105856004803603602081101561057557600080fd5b50356001600160a01b0316610f07565b60408051938452602084019290925282820152519081900360600190f35b6104fb610fad565b6105d1600480360360208110156105c157600080fd5b50356001600160a01b0316610fba565b005b610440600480360360208110156105e957600080fd5b50356001600160a01b0316611016565b610440611075565b61045a61107b565b6104fb6004803603602081101561061f57600080fd5b50356110b4565b6104fb6004803603606081101561063c57600080fd5b506001600160a01b03813581169160208101359091169060400135611147565b6105d16004803603602081101561067257600080fd5b5035611184565b61045a61120e565b6104fb61122d565b6105d16004803603602081101561069f57600080fd5b50356001600160a01b031661133e565b6105d1600480360360808110156106c557600080fd5b50803590602081013590604081013590606001356113ad565b6106e66114f0565b6040805160ff9092168252519081900360200190f35b610440600480360360a081101561071257600080fd5b508035906020810135906040810135906001600160a01b0360608201351690608001356114f5565b6106e66115d3565b6105d16004803603602081101561075857600080fd5b50356001600160a01b03166115dc565b6103fe6004803603602081101561077e57600080fd5b503561164b565b6104406004803603602081101561079b57600080fd5b5035611698565b6104fb6116f2565b6103fe611765565b610440600480360360a08110156107c857600080fd5b508035906020810135906040810135906001600160a01b036060820135169060800135611774565b6105d16004803603604081101561080657600080fd5b506001600160a01b038135169060200135611787565b6104406004803603602081101561083257600080fd5b50356001600160a01b031661179d565b6105d1600480360360c081101561085857600080fd5b506001600160a01b03813581169160208101359160408201359160608101359160808201359160a001351661189f565b6105d16004803603606081101561089e57600080fd5b506001600160a01b0381351690602081013590604001356119fe565b610440600480360360208110156108d057600080fd5b50356001600160a01b0316611b3f565b6108e8611b92565b60408051602080825283518183015283519192839290830191858101910280838360005b8381101561092457818101518382015260200161090c565b505050509050019250505060405180910390f35b6105d1611ca2565b6105d1611e7b565b6103fe6004803603602081101561095e57600080fd5b5035611f37565b6104406004803603602081101561097b57600080fd5b5035611f84565b6108e8611fde565b6105d1600480360360208110156109a057600080fd5b5035612052565b6103fe6120bf565b610440600480360360c08110156109c557600080fd5b506001600160a01b0381358116916020810135916040820135916060810135916080820135169060a001356120ce565b6104406121b1565b61045a6121bc565b6105d160048036036020811015610a1b57600080fd5b50356001600160a01b0316612217565b6105d1612273565b61058560048036036020811015610a4957600080fd5b50356122f5565b6105d160048036036020811015610a6657600080fd5b50356001600160a01b0316612375565b61044060048036036020811015610a8c57600080fd5b50356001600160a01b03166123a1565b61044060048036036020811015610ab257600080fd5b50356001600160a01b0316612400565b6104fb60048036036040811015610ad857600080fd5b506001600160a01b03813516906020013561245f565b6105d160048036036060811015610b0457600080fd5b506001600160a01b03813516906020810135906040013561249f565b6105d16124ff565b6105d160048036036020811015610b3e57600080fd5b50356001600160a01b0316612566565b6105d160048036036040811015610b6457600080fd5b506001600160a01b038135169060200135612590565b61044060048036036080811015610b9057600080fd5b5080359060208101359060408101359060600135612622565b61044060048036036080811015610bbf57600080fd5b506001600160a01b03813516906020810135906040810135906060013561262c565b61044060048036036040811015610bf757600080fd5b506001600160a01b03813516906020013561270a565b6103fe6127a4565b6105d160048036036020811015610c2b57600080fd5b50356127b3565b610440612841565b61044060048036036040811015610c5057600080fd5b506001600160a01b0381358116916020013516612883565b6104fb60048036036040811015610c7e57600080fd5b506001600160a01b0381351690602001356128de565b6105d160048036036040811015610caa57600080fd5b506001600160a01b0381351690602001356129c1565b6103fe612a37565b6103fe612a46565b6105d160048036036040811015610ce657600080fd5b506001600160a01b038135169060200135612a55565b61044060048036036060811015610d1257600080fd5b5080359060208101359060400135612b8a565b60085461010090046001600160a01b031681565b6000610d43612c68565b6001600160a01b03166305b3c1c9836040518263ffffffff1660e01b815260040180826001600160a01b03166001600160a01b0316815260200191505060206040518083038186803b158015610d9857600080fd5b505afa158015610dac573d6000803e3d6000fd5b505050506040513d6020811015610dc257600080fd5b505192915050565b6005805460408051602060026001851615610100026000190190941693909304601f81018490048402820184019092528181529291830182828015610e505780601f10610e2557610100808354040283529160200191610e50565b820191906000526020600020905b815481529060010190602001808311610e3357829003601f168201915b505050505081565b6000610e62612c81565b6003546004805460408051633691826360e21b81526001600160a01b03948516938101849052878516602482015260448101879052905192939091169163da46098c9160648082019260009290919082900301818387803b158015610ec657600080fd5b505af1158015610eda573d6000803e3d6000fd5b50505050610ee9818585612cc0565b5060019392505050565b600080610efe612d8a565b94509492505050565b6000806000610f14612c68565b6001600160a01b0316631137aedf856040518263ffffffff1660e01b815260040180826001600160a01b03166001600160a01b0316815260200191505060606040518083038186803b158015610f6957600080fd5b505afa158015610f7d573d6000803e3d6000fd5b505050506040513d6060811015610f9357600080fd5b508051602082015160409092015190969195509350915050565b6000610fb7612d8a565b90565b610fc2612dd7565b600180546001600160a01b0383166001600160a01b0319909116811790915560408051918252517f906a1c6bd7e3091ea86693dd029a831c19049ce77f1dce2ce0bab1cacbabce229181900360200190a150565b6000611020612c68565b6001600160a01b03166316b2213f836040518263ffffffff1660e01b815260040180826001600160a01b03166001600160a01b0316815260200191505060206040518083038186803b158015610d9857600080fd5b60075481565b6040518060400160405280601781526020017f53796e746865746978204e6574776f726b20546f6b656e00000000000000000081525081565b6000806110bf612e20565b600354604080516301670a7b60e21b81526001600160a01b039283166004820152602481018790529051929091169163059c29ec91604480820192602092909190829003018186803b15801561111457600080fd5b505afa158015611128573d6000803e3d6000fd5b505050506040513d602081101561113e57600080fd5b50511192915050565b6000611151612c81565b611159612e37565b6111638483612e8b565b5060035461117c906001600160a01b03168585856130bf565b949350505050565b61118c6131b7565b611194612c81565b61119c612c68565b6003546040805163b06e8c6560e01b81526001600160a01b039283166004820152602481018590529051929091169163b06e8c659160448082019260009290919082900301818387803b1580156111f257600080fd5b505af1158015611206573d6000803e3d6000fd5b505050505b50565b604051806040016040528060038152602001620a69cb60eb1b81525081565b60006060611239611fde565b905060005b815181101561133557600082828151811061125557fe5b6020908102919091018101516000818152600983526040908190205460085482516321f8a72160e01b81526004810185905292519395506001600160a01b0391821694610100909104909116926321f8a721926024808201939291829003018186803b1580156112c457600080fd5b505afa1580156112d8573d6000803e3d6000fd5b505050506040513d60208110156112ee57600080fd5b50516001600160a01b031614158061131b57506000818152600960205260409020546001600160a01b0316155b1561132c5760009350505050610fb7565b5060010161123e565b50600191505090565b6113466131b7565b61134e612c81565b611356612c68565b6003546040805163159fa0d560e11b81526001600160a01b038581166004830152928316602482015290519290911691632b3f41aa9160448082019260009290919082900301818387803b1580156111f257600080fd5b6113b56131f7565b6002805460408051602081018790528082018690526060808201869052825180830390910181526080909101918290526001600160a01b039092169263907dff979291806031613e04823960310190506040518091039020886000806040518763ffffffff1660e01b815260040180806020018781526020018681526020018581526020018460001b81526020018360001b8152602001828103825288818151815260200191508051906020019080838360005b83811015611481578181015183820152602001611469565b50505050905090810190601f1680156114ae5780820380516001836020036101000a031916815260200191505b50975050505050505050600060405180830381600087803b1580156114d257600080fd5b505af11580156114e6573d6000803e3d6000fd5b5050505050505050565b601281565b600085846115038282613264565b61150b612c81565b611513612e20565b600354604080516327c319e960e11b81526001600160a01b039283166004820181905260248201819052604482018d9052606482018c9052608482018b905260a4820152600060c4820181905289841660e4830152610104820189905282519490931693634f8633d29361012480840194938390030190829087803b15801561159b57600080fd5b505af11580156115af573d6000803e3d6000fd5b505050506040513d60408110156115c557600080fd5b505198975050505050505050565b60085460ff1681565b6115e46131b7565b6115ec612c81565b6115f4612c68565b6003546040805163fd864ccf60e01b81526001600160a01b03858116600483015292831660248201529051929091169163fd864ccf9160448082019260009290919082900301818387803b1580156111f257600080fd5b6000611655612c68565b6001600160a01b03166332608039836040518263ffffffff1660e01b81526004018082815260200191505060206040518083038186803b158015610d9857600080fd5b60006116a2612c68565b6001600160a01b0316637b1001b78360016040518363ffffffff1660e01b815260040180838152602001821515151581526020019250505060206040518083038186803b158015610d9857600080fd5b60006116fc612c68565b6001600160a01b0316634e99bda96040518163ffffffff1660e01b815260040160206040518083038186803b15801561173457600080fd5b505afa158015611748573d6000803e3d6000fd5b505050506040513d602081101561175e57600080fd5b5051905090565b6001546001600160a01b031681565b600061177e612d8a565b95945050505050565b61178f6132cb565b6117998282613338565b5050565b60006117a7612c68565b60048054604080516370a0823160e01b81526001600160a01b0387811694820194909452905193831693636bed041593879316916370a08231916024808301926020929190829003018186803b15801561180057600080fd5b505afa158015611814573d6000803e3d6000fd5b505050506040513d602081101561182a57600080fd5b5051604080516001600160e01b031960e086901b1681526001600160a01b03909316600484015260248301919091528051604480840193829003018186803b15801561187557600080fd5b505afa158015611889573d6000803e3d6000fd5b505050506040513d6040811015610dc257600080fd5b6118a76131f7565b60028054604080516020810189905280820188905260608101879052608081018690526001600160a01b0385811660a0808401919091528351808403909101815260c0909201928390529092169263907dff97929180603e613e358239603e019050604051809103902061191a8b613455565b6000806040518763ffffffff1660e01b815260040180806020018781526020018681526020018581526020018460001b81526020018360001b8152602001828103825288818151815260200191508051906020019080838360005b8381101561198d578181015183820152602001611975565b50505050905090810190601f1680156119ba5780820380516001836020036101000a031916815260200191505b50975050505050505050600060405180830381600087803b1580156119de57600080fd5b505af11580156119f2573d6000803e3d6000fd5b50505050505050505050565b611a066131f7565b6002805460408051602081018690528082018590528151808203830181526060909101918290526001600160a01b039092169263907dff979291806027613ee9823960270190506040518091039020611a5e88613455565b6000806040518763ffffffff1660e01b815260040180806020018781526020018681526020018581526020018460001b81526020018360001b8152602001828103825288818151815260200191508051906020019080838360005b83811015611ad1578181015183820152602001611ab9565b50505050905090810190601f168015611afe5780820380516001836020036101000a031916815260200191505b50975050505050505050600060405180830381600087803b158015611b2257600080fd5b505af1158015611b36573d6000803e3d6000fd5b50505050505050565b60048054604080516370a0823160e01b81526001600160a01b03858116948201949094529051600093909216916370a0823191602480820192602092909190829003018186803b158015610d9857600080fd5b6060611b9c612c68565b6001600160a01b03166372cb051f6040518163ffffffff1660e01b815260040160006040518083038186803b158015611bd457600080fd5b505afa158015611be8573d6000803e3d6000fd5b505050506040513d6000823e601f3d908101601f191682016040526020811015611c1157600080fd5b8101908080516040519392919084640100000000821115611c3157600080fd5b908301906020820185811115611c4657600080fd5b8251866020820283011164010000000082111715611c6357600080fd5b82525081516020918201928201910280838360005b83811015611c90578181015183820152602001611c78565b50505050905001604052505050905090565b6060611cac611fde565b905060005b8151811015611799576000828281518110611cc857fe5b602002602001015190506000600860019054906101000a90046001600160a01b03166001600160a01b031663dacb2d01838460405160200180807f5265736f6c766572206d697373696e67207461726765743a20000000000000008152506019018281526020019150506040516020818303038152906040526040518363ffffffff1660e01b81526004018083815260200180602001828103825283818151815260200191508051906020019080838360005b83811015611d93578181015183820152602001611d7b565b50505050905090810190601f168015611dc05780820380516001836020036101000a031916815260200191505b50935050505060206040518083038186803b158015611dde57600080fd5b505afa158015611df2573d6000803e3d6000fd5b505050506040513d6020811015611e0857600080fd5b505160008381526009602090815260409182902080546001600160a01b0319166001600160a01b03851690811790915582518681529182015281519293507f88a93678a3692f6789d9546fc621bf7234b101ddb7d4fe479455112831b8aa68929081900390910190a15050600101611cb1565b6001546001600160a01b03163314611ec45760405162461bcd60e51b8152600401808060200182810382526035815260200180613da76035913960400191505060405180910390fd5b600054600154604080516001600160a01b03938416815292909116602083015280517fb532073b38c83145e3e5135377a08bf9aab55bc0fd7c1179cd4fb995d2a5159c9281900390910190a160018054600080546001600160a01b03199081166001600160a01b03841617909155169055565b6000611f41612c68565b6001600160a01b031663835e119c836040518263ffffffff1660e01b81526004018082815260200191505060206040518083038186803b158015610d9857600080fd5b6000611f8e612c68565b6001600160a01b0316637b1001b78360006040518363ffffffff1660e01b815260040180838152602001821515151581526020019250505060206040518083038186803b158015610d9857600080fd5b606080611fe9613461565b60408051600180825281830190925291925060609190602080830190803883390190505090507453796e746865746978427269646765546f4261736560581b8160008151811061203557fe5b60200260200101818152505061204b8282613554565b9250505090565b61205a6131b7565b612062612c81565b61206a612c68565b600354604080516285c0d160e31b81526001600160a01b039283166004820152602481018590529051929091169163042e06889160448082019260009290919082900301818387803b1580156111f257600080fd5b6000546001600160a01b031681565b600085846120dc8282613264565b6120e4612c81565b6120ec612e20565b600354604080516327c319e960e11b81526001600160a01b038d8116600483018190529381166024830152604482018d9052606482018c9052608482018b905260a4820193909352600060c4820181905289841660e4830152610104820189905282519490931693634f8633d29361012480840194938390030190829087803b15801561217857600080fd5b505af115801561218c573d6000803e3d6000fd5b505050506040513d60408110156121a257600080fd5b50519998505050505050505050565b631cd554d160e21b81565b6006805460408051602060026001851615610100026000190190941693909304601f81018490048402820184019092528181529291830182828015610e505780601f10610e2557610100808354040283529160200191610e50565b61221f612dd7565b600280546001600160a01b0383166001600160a01b0319909116811790915560408051918252517ffc80377ca9c49cc11ae6982f390a42db976d5530af7c43889264b13fbbd7c57e9181900360200190a150565b61227b6131b7565b612283612c81565b61228b612c68565b600354604080516324beb82560e11b81526001600160a01b0392831660048201529051929091169163497d704a9160248082019260009290919082900301818387803b1580156122da57600080fd5b505af11580156122ee573d6000803e3d6000fd5b505050505b565b6000806000612302612c81565b61230a612e20565b600354604080516306c5a00b60e21b81526001600160a01b0392831660048201526024810188905290519290911691631b16802c916044808201926060929091908290030181600087803b15801561236157600080fd5b505af1158015610f7d573d6000803e3d6000fd5b61237d613610565b600480546001600160a01b0319166001600160a01b03831617905561120b816136a9565b60006123ab612c68565b6001600160a01b031663a311c7c2836040518263ffffffff1660e01b815260040180826001600160a01b03166001600160a01b0316815260200191505060206040518083038186803b158015610d9857600080fd5b600061240a612c68565b6001600160a01b031663a5fdc5de836040518263ffffffff1660e01b815260040180826001600160a01b03166001600160a01b0316815260200191505060206040518083038186803b158015610d9857600080fd5b6000612469612c81565b612471612e37565b600354612487906001600160a01b031683612e8b565b50600354610ee9906001600160a01b031684846137d4565b6124a76131f7565b6002805460408051602081018690528082018590528151808203830181526060909101918290526001600160a01b039092169263907dff979291806028613ddc823960280190506040518091039020611a5e88613455565b6125076131b7565b61250f612c81565b612517612c68565b6003546040805163644bb89960e11b81526001600160a01b0392831660048201529051929091169163c89771329160248082019260009290919082900301818387803b1580156122da57600080fd5b61256e6137e1565b600380546001600160a01b0319166001600160a01b0392909216919091179055565b6125986131b7565b6125a0612c81565b6125a8612c68565b60035460408051632694552d60e21b81526001600160a01b03868116600483015292831660248201526044810185905290519290911691639a5154b49160648082019260009290919082900301818387803b15801561260657600080fd5b505af115801561261a573d6000803e3d6000fd5b505050505050565b600061117c612d8a565b6000838261263a8282613264565b612642612c81565b61264a612e20565b600354604080516327c319e960e11b81526001600160a01b038b8116600483018190529381166024830152604482018b9052606482018a90526084820189905260a48201849052600060c4830181905260e483019490945261010482018490528251941693634f8633d29361012480840194938390030190829087803b1580156126d357600080fd5b505af11580156126e7573d6000803e3d6000fd5b505050506040513d60408110156126fd57600080fd5b5051979650505050505050565b6000612714612c68565b6001600160a01b031663d37c4d8b84846040518363ffffffff1660e01b815260040180836001600160a01b03166001600160a01b031681526020018281526020019250505060206040518083038186803b15801561277157600080fd5b505afa158015612785573d6000803e3d6000fd5b505050506040513d602081101561279b57600080fd5b50519392505050565b6003546001600160a01b031681565b6127bb6132cb565b60006127c5613840565b90506127d18183613338565b806001600160a01b03166359974e38836040518263ffffffff1660e01b815260040180828152602001915050602060405180830381600087803b15801561281757600080fd5b505af115801561282b573d6000803e3d6000fd5b505050506040513d60208110156122ee57600080fd5b600061284b612c68565b6001600160a01b031663dbf633406040518163ffffffff1660e01b815260040160206040518083038186803b15801561173457600080fd5b6004805460408051636eb1769f60e11b81526001600160a01b0386811694820194909452848416602482015290516000939092169163dd62ed3e91604480820192602092909190829003018186803b15801561277157600080fd5b60006128e8612e37565b6128f0612c81565b6000806128fb612c68565b6003546040805163298f137d60e21b81526001600160a01b0389811660048301526024820189905292831660448201528151939092169263a63c4df49260648082019392918290030181600087803b15801561295657600080fd5b505af115801561296a573d6000803e3d6000fd5b505050506040513d604081101561298057600080fd5b50805160209091015160035491935091506129a9908690849084906001600160a01b0316613861565b60035461177e9086906001600160a01b0316846137d4565b6129c96131b7565b6129d1612c81565b6129d9612c68565b6003546040805163227635b160e11b81526001600160a01b038681166004830152928316602482015260448101859052905192909116916344ec6b629160648082019260009290919082900301818387803b15801561260657600080fd5b6004546001600160a01b031681565b6002546001600160a01b031681565b612a5d6132cb565b612a65612e37565b60048054604080516370a0823160e01b81526001600160a01b03868116948201949094529051929091169163b46310f6918591612afd91869186916370a08231916024808301926020929190829003018186803b158015612ac557600080fd5b505afa158015612ad9573d6000803e3d6000fd5b505050506040513d6020811015612aef57600080fd5b50519063ffffffff61393816565b6040518363ffffffff1660e01b815260040180836001600160a01b03166001600160a01b0316815260200182815260200192505050600060405180830381600087803b158015612b4c57600080fd5b505af1158015612b60573d6000803e3d6000fd5b50505050612b7082600083613995565b600754612b83908263ffffffff61393816565b6007555050565b60008382612b988282613264565b612ba0612c81565b612ba8612e20565b600354604080516327c319e960e11b81526001600160a01b039283166004820181905260248201819052604482018b9052606482018a90526084820189905260a48201819052600060c4830181905260e4830191909152610104820181905282519490931693634f8633d29361012480840194938390030190829087803b158015612c3257600080fd5b505af1158015612c46573d6000803e3d6000fd5b505050506040513d6040811015612c5c57600080fd5b50519695505050505050565b6000612c7c6524b9b9bab2b960d11b6139e8565b905090565b6002546001600160a01b03163314801590612ca757506003546001600160a01b03163314155b156122f357600380546001600160a01b03191633179055565b60025460408051602080820185905282518083039091018152908201918290526001600160a01b039092169163907dff9791600390806021613ec8823960210190506040518091039020612d1388613455565b612d1c88613455565b60006040518763ffffffff1660e01b815260040180806020018781526020018681526020018581526020018481526020018360001b81526020018281038252888181518152602001915080519060200190808383600083811015611ad1578181015183820152602001611ab9565b6040805162461bcd60e51b815260206004820152601b60248201527f43616e6e6f742062652072756e206f6e2074686973206c617965720000000000604482015290519081900360640190fd5b6000546001600160a01b031633146122f35760405162461bcd60e51b815260040180806020018281038252602f815260200180613e99602f913960400191505060405180910390fd5b6000612c7c6822bc31b430b733b2b960b91b6139e8565b612e3f613ac5565b6001600160a01b031663086dabd16040518163ffffffff1660e01b815260040160006040518083038186803b158015612e7757600080fd5b505afa1580156122ee573d6000803e3d6000fd5b600080612e96613adf565b60408051631167f01160e31b81526001600160a01b0387811660048301528251931692638b3f808892602480840193919291829003018186803b158015612edc57600080fd5b505afa158015612ef0573d6000803e3d6000fd5b505050506040513d6040811015612f0657600080fd5b505190508015610ee957600080612f1b612c68565b60048054604080516370a0823160e01b81526001600160a01b038b811694820194909452905193831693636bed0415938b9316916370a08231916024808301926020929190829003018186803b158015612f7457600080fd5b505afa158015612f88573d6000803e3d6000fd5b505050506040513d6020811015612f9e57600080fd5b5051604080516001600160e01b031960e086901b1681526001600160a01b03909316600484015260248301919091528051604480840193829003018186803b158015612fe957600080fd5b505afa158015612ffd573d6000803e3d6000fd5b505050506040513d604081101561301357600080fd5b5080516020909101519092509050818511156130605760405162461bcd60e51b8152600401808060200182810382526026815260200180613e736026913960400191505060405180910390fd5b80156130b3576040805162461bcd60e51b815260206004820152601e60248201527f412073796e7468206f7220534e58207261746520697320696e76616c69640000604482015290519081900360640190fd5b50600195945050505050565b6004805460408051636eb1769f60e11b81526001600160a01b0387811694820194909452878416602482015290516000939092169163da46098c918791899161312c918891879163dd62ed3e91604480820192602092909190829003018186803b158015612ac557600080fd5b6040518463ffffffff1660e01b815260040180846001600160a01b03166001600160a01b03168152602001836001600160a01b03166001600160a01b031681526020018281526020019350505050600060405180830381600087803b15801561319457600080fd5b505af11580156131a8573d6000803e3d6000fd5b5050505061177e848484613afb565b6131bf613ac5565b6001600160a01b0316637c3125416040518163ffffffff1660e01b815260040160006040518083038186803b158015612e7757600080fd5b6131ff612e20565b6001600160a01b0316336001600160a01b0316146122f3576040805162461bcd60e51b815260206004820152601e60248201527f4f6e6c792045786368616e6765722063616e20696e766f6b6520746869730000604482015290519081900360640190fd5b61326c613ac5565b6001600160a01b0316631ce00ba283836040518363ffffffff1660e01b8152600401808381526020018281526020019250505060006040518083038186803b1580156132b757600080fd5b505afa15801561261a573d6000803e3d6000fd5b6132d3613d22565b6001600160a01b0316336001600160a01b0316146122f3576040805162461bcd60e51b815260206004820152601d60248201527f43616e206f6e6c7920626520696e766f6b656420627920627269646765000000604482015290519081900360640190fd5b60048054604080516370a0823160e01b81526001600160a01b03868116948201949094529051929091169163b46310f69185916133d091869186916370a08231916024808301926020929190829003018186803b15801561339857600080fd5b505afa1580156133ac573d6000803e3d6000fd5b505050506040513d60208110156133c257600080fd5b50519063ffffffff613d4516565b6040518363ffffffff1660e01b815260040180836001600160a01b03166001600160a01b0316815260200182815260200192505050600060405180830381600087803b15801561341f57600080fd5b505af1158015613433573d6000803e3d6000fd5b50505050613442308383613995565b600754612b83908263ffffffff613d4516565b6001600160a01b031690565b60408051600580825260c082019092526060916020820160a0803883390190505090506d53796e746865746978537461746560901b816000815181106134a357fe5b6020026020010181815250506b53797374656d53746174757360a01b816001815181106134cc57fe5b6020026020010181815250506822bc31b430b733b2b960b91b816002815181106134f257fe5b6020026020010181815250506524b9b9bab2b960d11b8160038151811061351557fe5b602002602001018181525050722932bbb0b93239a234b9ba3934b13aba34b7b760691b8160048151811061354557fe5b60200260200101818152505090565b60608151835101604051908082528060200260200182016040528015613584578160200160208202803883390190505b50905060005b83518110156135c65783818151811061359f57fe5b60200260200101518282815181106135b357fe5b602090810291909101015260010161358a565b5060005b8251811015613609578281815181106135df57fe5b60200260200101518282865101815181106135f657fe5b60209081029190910101526001016135ca565b5092915050565b6002546001600160a01b0316331480159061363657506003546001600160a01b03163314155b1561364e57600380546001600160a01b031916331790555b6000546003546001600160a01b039081169116146122f3576040805162461bcd60e51b815260206004820152601360248201527227bbb732b91037b7363c90333ab731ba34b7b760691b604482015290519081900360640190fd5b600254604080516001600160a01b038481166020808401919091528351808403820181528385018086527f546f6b656e5374617465557064617465642861646472657373290000000000009052935192839003605a01832063907dff9760e01b8452600160248501819052604485018290526000606486018190526084860181905260a4860181905260c060048701908152875160c48801528751959098169763907dff97979692959394919384938493839260e490920191908a0190808383885b8381101561378357818101518382015260200161376b565b50505050905090810190601f1680156137b05780820380516001836020036101000a031916815260200191505b50975050505050505050600060405180830381600087803b1580156111f257600080fd5b600061117c848484613afb565b6002546001600160a01b031633146122f3576040805162461bcd60e51b815260206004820152601760248201527f4f6e6c79207468652070726f78792063616e2063616c6c000000000000000000604482015290519081900360640190fd5b6000612c7c722932bbb0b93239a234b9ba3934b13aba34b7b760691b6139e8565b6002805460408051602081018790528082018690526001600160a01b03858116606080840191909152835180840390910181526080909201928390529092169263907dff979291806032613f108239603201905060405180910390206138c689613455565b6000806040518763ffffffff1660e01b815260040180806020018781526020018681526020018581526020018460001b81526020018360001b81526020018281038252888181518152602001915080519060200190808383600083811015611481578181015183820152602001611469565b60008282111561398f576040805162461bcd60e51b815260206004820152601e60248201527f536166654d6174683a207375627472616374696f6e206f766572666c6f770000604482015290519081900360640190fd5b50900390565b60025460408051602080820185905282518083039091018152908201918290526001600160a01b039092169163907dff9791600390806021613f42823960210190506040518091039020612d1388613455565b600081815260096020908152604080832054815170026b4b9b9b4b7339030b2323932b9b99d1607d1b9381019390935260318084018690528251808503909101815260519093019091526001600160a01b031690816136095760405162461bcd60e51b81526004018080602001828103825283818151815260200191508051906020019080838360005b83811015613a8a578181015183820152602001613a72565b50505050905090810190601f168015613ab75780820380516001836020036101000a031916815260200191505b509250505060405180910390fd5b6000612c7c6b53797374656d53746174757360a01b6139e8565b6000612c7c6d53796e746865746978537461746560901b6139e8565b60006001600160a01b03831615801590613b1e57506001600160a01b0383163014155b8015613b3857506002546001600160a01b03848116911614155b613b89576040805162461bcd60e51b815260206004820152601f60248201527f43616e6e6f74207472616e7366657220746f2074686973206164647265737300604482015290519081900360640190fd5b60048054604080516370a0823160e01b81526001600160a01b03888116948201949094529051929091169163b46310f6918791613be991879186916370a08231916024808301926020929190829003018186803b158015612ac557600080fd5b6040518363ffffffff1660e01b815260040180836001600160a01b03166001600160a01b0316815260200182815260200192505050600060405180830381600087803b158015613c3857600080fd5b505af1158015613c4c573d6000803e3d6000fd5b505060048054604080516370a0823160e01b81526001600160a01b0389811694820194909452905192909116935063b46310f692508691613cb091879186916370a08231916024808301926020929190829003018186803b15801561339857600080fd5b6040518363ffffffff1660e01b815260040180836001600160a01b03166001600160a01b0316815260200182815260200192505050600060405180830381600087803b158015613cff57600080fd5b505af1158015613d13573d6000803e3d6000fd5b50505050610ee9848484613995565b6000612c7c7453796e746865746978427269646765546f4261736560581b6139e8565b600082820183811015613d9f576040805162461bcd60e51b815260206004820152601b60248201527f536166654d6174683a206164646974696f6e206f766572666c6f770000000000604482015290519081900360640190fd5b939250505056fe596f75206d757374206265206e6f6d696e61746564206265666f726520796f752063616e20616363657074206f776e65727368697045786368616e67655265636c61696d28616464726573732c627974657333322c75696e743235362945786368616e6765547261636b696e6728627974657333322c627974657333322c75696e743235362c75696e743235362953796e746845786368616e676528616464726573732c627974657333322c75696e743235362c627974657333322c75696e743235362c616464726573732943616e6e6f74207472616e73666572207374616b6564206f7220657363726f77656420534e584f6e6c792074686520636f6e7472616374206f776e6572206d617920706572666f726d207468697320616374696f6e417070726f76616c28616464726573732c616464726573732c75696e743235362945786368616e676552656261746528616464726573732c627974657333322c75696e74323536294163636f756e744c69717569646174656428616464726573732c75696e743235362c75696e743235362c61646472657373295472616e7366657228616464726573732c616464726573732c75696e7432353629a265627a7a7231582046b95ecf0aafa1112c0aa66ee1e9d26dc5fd4e44c0171b7a85c17e188b20e23964736f6c63430005100032", "abi": [ { "inputs": [ @@ -14860,6 +14869,37 @@ "stateMutability": "nonpayable", "type": "constructor" }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "account", + "type": "address" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "snxRedeemed", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "amountLiquidated", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "address", + "name": "liquidator", + "type": "address" + } + ], + "name": "AccountLiquidated", + "type": "event" + }, { "anonymous": false, "inputs": [ @@ -15617,6 +15657,42 @@ "stateMutability": "nonpayable", "type": "function" }, + { + "constant": false, + "inputs": [ + { + "internalType": "bytes32", + "name": "", + "type": "bytes32" + }, + { + "internalType": "uint256", + "name": "", + "type": "uint256" + }, + { + "internalType": "bytes32", + "name": "", + "type": "bytes32" + }, + { + "internalType": "bytes32", + "name": "", + "type": "bytes32" + } + ], + "name": "exchangeAtomically", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "payable": false, + "stateMutability": "nonpayable", + "type": "function" + }, { "constant": false, "inputs": [ @@ -15773,7 +15849,7 @@ "outputs": [ { "internalType": "uint256", - "name": "amountReceived", + "name": "", "type": "uint256" } ], @@ -15822,21 +15898,6 @@ "stateMutability": "nonpayable", "type": "function" }, - { - "constant": true, - "inputs": [], - "name": "integrationProxy", - "outputs": [ - { - "internalType": "contract Proxy", - "name": "", - "type": "address" - } - ], - "payable": false, - "stateMutability": "view", - "type": "function" - }, { "constant": true, "inputs": [], @@ -15937,12 +15998,12 @@ "inputs": [ { "internalType": "address", - "name": "", + "name": "account", "type": "address" }, { "internalType": "uint256", - "name": "", + "name": "susdAmount", "type": "uint256" } ], @@ -16204,21 +16265,6 @@ "stateMutability": "view", "type": "function" }, - { - "constant": false, - "inputs": [ - { - "internalType": "address payable", - "name": "_integrationProxy", - "type": "address" - } - ], - "name": "setIntegrationProxy", - "outputs": [], - "payable": false, - "stateMutability": "nonpayable", - "type": "function" - }, { "constant": false, "inputs": [ @@ -16504,15 +16550,15 @@ } ], "source": { - "keccak256": "0xbbe79669c2586e5f0a7f35a0577d21b881e0e083b9fc297fa16d6c1de064c749", + "keccak256": "0x87c92d55af4f4b01932ec25770439770836ed60b9652a3f4ab1ec6e0d82d302a", "urls": [ - "bzz-raw://45f229865baabb02bd1d2fc70e2689e8eddb335483c56fbc8a40aa839725831c", - "dweb:/ipfs/QmZ7hK15fu8JkDQRbHzVCHgyKLL6P7VJeuGEaUEY7ZHpom" + "bzz-raw://8eeb6e102aeb9e33d5bc3feafa27ced0cbf46166b8642b55711ae8088cbef77d", + "dweb:/ipfs/QmWnq7ZBagWFnHz4o82gyM193MZqeD5bqNiZxtd8R23N5D" ] }, "metadata": { "compiler": { - "version": "0.5.16-develop.2020.12.10+ovm+commit.25adf37d" + "version": "0.5.16+commit.9c3226ce" }, "language": "Solidity", "settings": { @@ -16529,10 +16575,10 @@ }, "sources": { "MintableSynthetix.sol": { - "keccak256": "0xbbe79669c2586e5f0a7f35a0577d21b881e0e083b9fc297fa16d6c1de064c749", + "keccak256": "0x87c92d55af4f4b01932ec25770439770836ed60b9652a3f4ab1ec6e0d82d302a", "urls": [ - "bzz-raw://45f229865baabb02bd1d2fc70e2689e8eddb335483c56fbc8a40aa839725831c", - "dweb:/ipfs/QmZ7hK15fu8JkDQRbHzVCHgyKLL6P7VJeuGEaUEY7ZHpom" + "bzz-raw://8eeb6e102aeb9e33d5bc3feafa27ced0cbf46166b8642b55711ae8088cbef77d", + "dweb:/ipfs/QmWnq7ZBagWFnHz4o82gyM193MZqeD5bqNiZxtd8R23N5D" ] } }, @@ -30460,6 +30506,1207 @@ }, "version": 1 } + }, + "CollateralEth": { + "bytecode": "6080604052600d805460ff191660011790553480156200001e57600080fd5b506040516200476938038062004769833981016040819052620000419162000150565b8585858585858380876001600160a01b0381166200007c5760405162461bcd60e51b8152600401620000739062000261565b60405180910390fd5b600080546001600160a01b0319166001600160a01b0383161781556040517fb532073b38c83145e3e5135377a08bf9aab55bc0fd7c1179cd4fb995d2a5159c91620000c99184906200023b565b60405180910390a150600280546001600160a01b039283166001600160a01b0319918216179091556006805498909216971696909617909555600492909255600a55600b5550506001600e5550620002e295505050505050565b80516200013081620002b2565b92915050565b80516200013081620002cc565b80516200013081620002d7565b60008060008060008060c087890312156200016a57600080fd5b600062000178898962000123565b96505060206200018b89828a0162000143565b95505060406200019e89828a0162000123565b9450506060620001b189828a0162000136565b9350506080620001c489828a0162000136565b92505060a0620001d789828a0162000136565b9150509295509295509295565b620001ef81620002a5565b82525050565b620001ef816200027c565b60006200020f60198362000273565b7f4f776e657220616464726573732063616e6e6f74206265203000000000000000815260200192915050565b604081016200024b8285620001e4565b6200025a6020830184620001f5565b9392505050565b60208082528101620001308162000200565b90815260200190565b6000620001308262000299565b90565b600062000130826200027c565b6001600160a01b031690565b600062000130826200028c565b620002bd816200027c565b8114620002c957600080fd5b50565b620002bd8162000289565b620002bd816200028c565b61447780620002f26000396000f3fe60806040526004361061020f5760003560e01c806372e18b6a11610118578063925ead11116100a0578063ba2de9bc1161006f578063ba2de9bc146105b0578063d2b8035a146105c5578063de81eda9146105e5578063e1ec3c6814610605578063f3f437031461063a5761020f565b8063925ead1114610546578063a76cdfa51461055b578063aa2d8ce31461057b578063b562a1ab1461059b5761020f565b8063846321a4116100e7578063846321a4146104af578063899ffef4146104cf5780638cd2e0c7146104f15780638da5cb5b1461051157806390abb4d9146105265761020f565b806372e18b6a14610445578063741853601461046557806379ba50971461047a5780637e1323551461048f5761020f565b8063379607f51161019b578063441a3e701161016a578063441a3e70146103bb57806347e7ef24146103db578063481c6a75146103ee57806353a47bb7146104035780635eb2ad01146104255761020f565b8063379607f51461034657806338245377146103665780634065b81b1461038657806341c738011461039b5761020f565b80631627540c116101e25780631627540c146102af57806323d60e2e146102cf5780632af64bd3146102ef57806330edd96114610311578063361e2086146103315761020f565b806304f3bcec1461021457806306c19e3f1461023f5780630710285c1461025f5780630aebeb4e14610281575b600080fd5b34801561022057600080fd5b5061022961065a565b6040516102369190614009565b60405180910390f35b61025261024d3660046136c0565b610669565b6040516102369190613f97565b34801561026b57600080fd5b5061027f61027a36600461355b565b61067f565b005b34801561028d57600080fd5b506102a161029c366004613684565b6106c5565b604051610236929190613fb3565b3480156102bb57600080fd5b5061027f6102ca3660046134e5565b61070e565b3480156102db57600080fd5b5061027f6102ea3660046135a8565b61076c565b3480156102fb57600080fd5b5061030461083b565b6040516102369190613f89565b34801561031d57600080fd5b5061025261032c366004613684565b610953565b34801561033d57600080fd5b50610252610971565b34801561035257600080fd5b5061027f610361366004613684565b610977565b34801561037257600080fd5b50610252610381366004613684565b610a4c565b34801561039257600080fd5b50610304610a5e565b3480156103a757600080fd5b506102526103b6366004613684565b610a67565b3480156103c757600080fd5b506102a16103d63660046136c0565b610b7f565b6102a16103e9366004613521565b610bca565b3480156103fa57600080fd5b50610229610be3565b34801561040f57600080fd5b50610418610bf2565b6040516102369190613f26565b34801561043157600080fd5b5061027f610440366004613521565b610c01565b34801561045157600080fd5b506103046104603660046135a8565b610c37565b34801561047157600080fd5b5061027f610cfb565b34801561048657600080fd5b5061027f610e4d565b34801561049b57600080fd5b506102526104aa3660046136c0565b610ee9565b3480156104bb57600080fd5b5061027f6104ca366004613684565b610f2e565b3480156104db57600080fd5b506104e4610f6b565b6040516102369190613f78565b3480156104fd57600080fd5b506102a161050c36600461355b565b6110fb565b34801561051d57600080fd5b50610418611116565b34801561053257600080fd5b5061027f610541366004613618565b611125565b34801561055257600080fd5b50610252611171565b34801561056757600080fd5b5061027f610576366004613684565b611177565b34801561058757600080fd5b50610252610596366004613684565b6111b4565b3480156105a757600080fd5b50610252611278565b3480156105bc57600080fd5b5061025261127e565b3480156105d157600080fd5b506102a16105e03660046136c0565b611284565b3480156105f157600080fd5b50610418610600366004613684565b611291565b34801561061157600080fd5b50610625610620366004613684565b6112ac565b604051610236999897969594939291906141ee565b34801561064657600080fd5b506102526106553660046134e5565b611306565b6002546001600160a01b031681565b60006106783484846000611318565b9392505050565b600061068c8484846119c0565b336000908152600f60205260409020549091506106af908263ffffffff611d2716565b336000908152600f602052604090205550505050565b6000806106d23384611d4c565b336000908152600f602052604090205491935091506106f7908263ffffffff611d2716565b336000908152600f60205260409020559092909150565b610716611e42565b600180546001600160a01b0319166001600160a01b0383161790556040517f906a1c6bd7e3091ea86693dd029a831c19049ce77f1dce2ce0bab1cacbabce2290610761908390613f26565b60405180910390a150565b610774611e42565b82811461079c5760405162461bcd60e51b815260040161079390614048565b60405180910390fd5b60005b8381101561082c5760008585838181106107b557fe5b600780546001810182556000918252602090920293909301357fa66cc928b5edb82af9bd49922954155ab7b0942694bea4ce44661d9a8736c688909101819055925082916008915086868681811061080957fe5b60209081029290920135835250810191909152604001600020555060010161079f565b50610835610cfb565b50505050565b60006060610847610f6b565b905060005b815181101561094957600082828151811061086357fe5b602090810291909101810151600081815260039092526040918290205460025492516321f8a72160e01b81529193506001600160a01b039081169216906321f8a721906108b4908590600401613f97565b60206040518083038186803b1580156108cc57600080fd5b505afa1580156108e0573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052506109049190810190613503565b6001600160a01b031614158061092f57506000818152600360205260409020546001600160a01b0316155b156109405760009350505050610950565b5060010161084c565b5060019150505b90565b6007818154811061096057fe5b600091825260209091200154905081565b600c5481565b600e805460010190819055336000908152600f60205260409020546109a2908363ffffffff611e6e16565b336000818152600f602052604080822093909355915184906109c390613f1b565b60006040518083038185875af1925050503d8060008114610a00576040519150601f19603f3d011682016040523d82523d6000602084013e610a05565b606091505b5050905080610a265760405162461bcd60e51b815260040161079390614058565b50600e548114610a485760405162461bcd60e51b815260040161079390614168565b5050565b60086020526000908152604090205481565b600d5460ff1681565b6000610a71613407565b506000828152600560208181526040928390208351610120810185528154815260018201546001600160a01b03169281019290925260028101549382019390935260038301546060820152600483015460808201529082015460ff16151560a0820152600682015460c0820152600782015460e0820152600890910154610100820152610afc611e96565b6001600160a01b031663fbfeca4082600a546004546040518463ffffffff1660e01b8152600401610b2f939291906141a5565b60206040518083038186803b158015610b4757600080fd5b505afa158015610b5b573d6000803e3d6000fd5b505050506040513d601f19601f8201168201806040525061067891908101906136a2565b600080610b8c8484611eb7565b336000908152600f60205260409020549193509150610bb1908463ffffffff611d2716565b336000908152600f602052604090205590939092509050565b600080610bd8848434611f5c565b909590945092505050565b6006546001600160a01b031681565b6001546001600160a01b031681565b610c09611e42565b600090815260096020526040902080546001600160a01b0319166001600160a01b0392909216919091179055565b6007546000908414610c4b57506000610cf3565b60005b84811015610ced576000868683818110610c6457fe5b9050602002013590508060078381548110610c7b57fe5b906000526020600020015414610c9657600092505050610cf3565b60078281548110610ca357fe5b906000526020600020015460086000878786818110610cbe57fe5b9050602002013581526020019081526020016000205414610ce457600092505050610cf3565b50600101610c4e565b50600190505b949350505050565b6060610d05610f6b565b905060005b8151811015610a48576000828281518110610d2157fe5b602002602001015190506000600260009054906101000a90046001600160a01b03166001600160a01b031663dacb2d018384604051602001610d639190613f10565b6040516020818303038152906040526040518363ffffffff1660e01b8152600401610d8f929190613fc1565b60206040518083038186803b158015610da757600080fd5b505afa158015610dbb573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250610ddf9190810190613503565b6000838152600360205260409081902080546001600160a01b0319166001600160a01b038416179055519091507f88a93678a3692f6789d9546fc621bf7234b101ddb7d4fe479455112831b8aa6890610e3b9084908490613fa5565b60405180910390a15050600101610d0a565b6001546001600160a01b03163314610e775760405162461bcd60e51b815260040161079390614038565b6000546001546040517fb532073b38c83145e3e5135377a08bf9aab55bc0fd7c1179cd4fb995d2a5159c92610eba926001600160a01b0391821692911690613f4f565b60405180910390a160018054600080546001600160a01b03199081166001600160a01b03841617909155169055565b6000610ef3611e96565b6001600160a01b0316638a7399758484600a546004546040518563ffffffff1660e01b8152600401610b2f94939291906142da565b92915050565b610f36611e42565b600b8190556040517fd19fe8ad9152af12b174a60210fb798db0767d63973ebb97298dc44d67a5c82d90610761908390613f97565b606080610f7661202d565b60408051600680825260e08201909252919250606091906020820160c08038833901905050905066119959541bdbdb60ca1b81600081518110610fb557fe5b6020026020010181815250506c45786368616e6765526174657360981b81600181518110610fdf57fe5b6020026020010181815250506822bc31b430b733b2b960b91b8160028151811061100557fe5b6020026020010181815250506b53797374656d53746174757360a01b8160038151811061102e57fe5b6020026020010181815250506814de5b9d1a1cd554d160ba1b8160048151811061105457fe5b6020026020010181815250506d10dbdb1b185d195c985b155d1a5b60921b8160058151811061107f57fe5b6020026020010181815250506060611097838361207e565b90506110f38160078054806020026020016040519081016040528092919081815260200182805480156110e957602002820191906000526020600020905b8154815260200190600101908083116110d5575b505050505061207e565b935050505090565b60008061110a8533868661213a565b90969095509350505050565b6000546001600160a01b031681565b61112d611e42565b600d805460ff191682151517908190556040517f261991749e1b2436706a31bde8bf184bb37fe21e303709b78d3b881afacadaa2916107619160ff90911690613f89565b600a5481565b61117f611e42565b600c8190556040517fe7bd72551c54d568cd97b00dc52d2787b5c5d4f0070d3582c1e8ba25141f799c90610761908390613f97565b60006111be613407565b506000828152600560208181526040928390208351610120810185528154815260018201546001600160a01b03169281019290925260028101549382019390935260038301546060820152600483015460808201529082015460ff16151560a0820152600682015460c0820152600782015460e0820152600890910154610100820152611249611e96565b6001600160a01b031663e99f9647826004546040518363ffffffff1660e01b8152600401610b2f929190614188565b60045481565b600b5481565b600080610bd884846122e9565b6009602052600090815260409020546001600160a01b031681565b600560208190526000918252604090912080546001820154600283015460038401546004850154958501546006860154600787015460089097015495976001600160a01b0390951696939592949360ff9092169290919089565b600f6020526000908152604090205481565b600061132261272b565b61132a6127cd565b600d5460ff1661134c5760405162461bcd60e51b815260040161079390614118565b6000838152600860205260409020546113775760405162461bcd60e51b815260040161079390614068565b61137f612821565b6001600160a01b0316632528f0fe846040518263ffffffff1660e01b81526004016113aa9190613f97565b60206040518083038186803b1580156113c257600080fd5b505afa1580156113d6573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052506113fa9190810190613636565b156114175760405162461bcd60e51b8152600401610793906140f8565b600b548510156114395760405162461bcd60e51b815260040161079390614148565b6006546040516302d35b2d60e61b815260009182916001600160a01b039091169063b4d6cb40906114709089908990600401613fb3565b604080518083038186803b15801561148757600080fd5b505afa15801561149b573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052506114bf9190810190613654565b915091508180156114ce575080155b6114ea5760405162461bcd60e51b8152600401610793906140d8565b6114f48786610ee9565b8611156115135760405162461bcd60e51b815260040161079390614138565b600061152a600c548861283c90919063ffffffff16565b9050600061153e888363ffffffff611e6e16565b9050600660009054906101000a90046001600160a01b03166001600160a01b031663b3b467326040518163ffffffff1660e01b8152600401602060405180830381600087803b15801561159057600080fd5b505af11580156115a4573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052506115c891908101906136a2565b60408051610120810182528281523360208083019182528284018e8152606084018d8152608085018f81528d151560a08701908152600060c0880181815260e08901828152426101008b019081528c84526005988990529a9092209851895596516001890180546001600160a01b0319166001600160a01b03909216919091179055935160028801559151600387015551600486015551918401805460ff191692151592909217909155905160068301555160078201559051600882015590955061169290612851565b61169c8288612943565b851561188f576116aa612b2f565b6001600160a01b031663867904b4336116c1612821565b6001600160a01b031663654a60ac8b86631cd554d160e21b6040518463ffffffff1660e01b81526004016116f793929190613fe1565b60206040518083038186803b15801561170f57600080fd5b505afa158015611723573d6000803e3d6000fd5b505050506040513d601f19601f8201168201806040525061174791908101906136a2565b6040518363ffffffff1660e01b8152600401611764929190613f34565b600060405180830381600087803b15801561177e57600080fd5b505af1158015611792573d6000803e3d6000fd5b505060065460405163e31f27c160e01b81526001600160a01b03909116925063e31f27c191506117c8908a908c90600401613fb3565b600060405180830381600087803b1580156117e257600080fd5b505af11580156117f6573d6000803e3d6000fd5b5050506000888152600960205260409020546001600160a01b031615905061188a576000878152600960205260409081902054905163db454a5160e01b81526001600160a01b039091169063db454a51906118579033908c90600401613f34565b600060405180830381600087803b15801561187157600080fd5b505af1158015611885573d6000803e3d6000fd5b505050505b61196b565b6000878152600860205260409020546118a790612b46565b6001600160a01b031663867904b433836040518363ffffffff1660e01b81526004016118d4929190613f34565b600060405180830381600087803b1580156118ee57600080fd5b505af1158015611902573d6000803e3d6000fd5b50506006546040516375ca5def60e11b81526001600160a01b03909116925063eb94bbde9150611938908a908c90600401613fb3565b600060405180830381600087803b15801561195257600080fd5b505af1158015611966573d6000803e3d6000fd5b505050505b336001600160a01b03167f604952b18be5fed608cbdd28101dc57bd667055c9678ec6d44fb1d8e4c7c172a868a8c8b876040516119ac9594939291906142f5565b60405180910390a250505050949350505050565b60006119ca61272b565b6119d26127cd565b600082116119f25760405162461bcd60e51b815260040161079390614178565b60006119fe8486612b51565b9050611a0f33826003015485612ba5565b600a54611a1a611e96565b6001600160a01b031663e99f9647836004546040518363ffffffff1660e01b8152600401611a499291906141d0565b60206040518083038186803b158015611a6157600080fd5b505afa158015611a75573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250611a9991908101906136a2565b10611ab65760405162461bcd60e51b815260040161079390614158565b6000611ac0611e96565b6001600160a01b031663fbfeca4083600a546004546040518463ffffffff1660e01b8152600401611af3939291906141df565b60206040518083038186803b158015611b0b57600080fd5b505afa158015611b1f573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250611b4391908101906136a2565b90506000848210611b545784611b56565b815b90506000611b7584600601548560040154611d2790919063ffffffff16565b9050808210611b9657611b89883386612c5d565b9550610678945050505050565b611ba533856003015484612ba5565b611baf8483612cce565b611bb7611e96565b6001600160a01b0316633c4aa0f38560030154846004546040518463ffffffff1660e01b8152600401611bec93929190613fe1565b60206040518083038186803b158015611c0457600080fd5b505afa158015611c18573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250611c3c91908101906136a2565b6002850154909550611c54908663ffffffff611e6e16565b60028501556003840154600090815260086020526040902054611c7690612b46565b6001600160a01b0316639dc29fac33846040518363ffffffff1660e01b8152600401611ca3929190613f34565b600060405180830381600087803b158015611cbd57600080fd5b505af1158015611cd1573d6000803e3d6000fd5b50505050876001600160a01b03167fb6e43890aeea54fbe6c0ed628e78172a0ff30bbcb1d70d8b130b12c366bac4c588338589604051611d149493929190614274565b60405180910390a2505050509392505050565b6000828201838110156106785760405162461bcd60e51b815260040161079390614078565b600080611d5761272b565b611d5f6127cd565b6000611d6b8486612b51565b60408051610120810182528254815260018301546001600160a01b031660208201526002830154918101919091526003820154606082015260048201546080820152600582015460ff16151560a0820152600682015460c0820152600782015460e08201526008820154610100820152909150611de790612ebd565b611df2858683612f0a565b60405191945092506001600160a01b038616907fcab22a4e95d29d40da2ace3f6ec72b49954a9bc7b2584f8fd47bf7f357a3ed6f90611e32908790613f97565b60405180910390a2509250929050565b6000546001600160a01b03163314611e6c5760405162461bcd60e51b8152600401610793906140b8565b565b600082821115611e905760405162461bcd60e51b815260040161079390614098565b50900390565b6000611eb26d10dbdb1b185d195c985b155d1a5b60921b613170565b905090565b600080611ec261272b565b611eca6127cd565b6000611ed68533612b51565b6002810154909150611eee908563ffffffff611e6e16565b6002820155611efc816131cd565b336001600160a01b03167ffae26280bca25d80f1501a9e363c73d3845e651c9aaae54f1fc09a9dcd5f330386868460020154604051611f3d93929190613fe1565b60405180910390a28060040154816002015492509250505b9250929050565b600080611f6761272b565b611f6f6127cd565b60008311611f8f5760405162461bcd60e51b815260040161079390614128565b60008481526005602052604090206007810154611fab90613206565b611fb481612851565b6002810154611fc9908563ffffffff611d2716565b600282018190556040516001600160a01b038816917f0b1992dffc262be88559dcaf96464e9d661d8bfca7e82f2bb73e31932a82187c9161200e918991899190613fe1565b60405180910390a2806004015481600201549250925050935093915050565b604080516001808252818301909252606091602080830190803883390190505090506e466c657869626c6553746f7261676560881b8160008151811061206f57fe5b60200260200101818152505090565b606081518351016040519080825280602002602001820160405280156120ae578160200160208202803883390190505b50905060005b83518110156120f0578381815181106120c957fe5b60200260200101518282815181106120dd57fe5b60209081029190910101526001016120b4565b5060005b82518110156121335782818151811061210957fe5b602002602001015182828651018151811061212057fe5b60209081029190910101526001016120f4565b5092915050565b60008061214561272b565b61214d6127cd565b6000848152600560208181526040928390208351610120810185528154815260018201546001600160a01b03169281019290925260028101549382019390935260038301546060820152600483015460808201529082015460ff16151560a0820152600682015460c0820152600782015460e082015260088201546101008201526121d790612ebd565b6121e686826003015486612ba5565b6121ef81612851565b6121f98185612cce565b600381015460009081526008602052604090205461221690612b46565b6001600160a01b0316639dc29fac87866040518363ffffffff1660e01b8152600401612243929190613f6a565b600060405180830381600087803b15801561225d57600080fd5b505af1158015612271573d6000803e3d6000fd5b50505050428160080181905550856001600160a01b0316876001600160a01b03167fdf10512219e869922340b1b24b21d7d79bf71f411a6391cc7c3ef5dd2fe89e7f878785600401546040516122c993929190613fe1565b60405180910390a380600401548160020154925092505094509492505050565b6000806122f461272b565b6122fc6127cd565b60006123088533612b51565b60408051610120810182528254815260018301546001600160a01b031660208201526002830154918101919091526003820154606082015260048201546080820152600582015460ff16151560a0820152600682015460c0820152600782015460e0820152600882015461010082015290915061238490612ebd565b6004810154612399908563ffffffff611d2716565b60048201556123a7816131cd565b60006123be600c548661283c90919063ffffffff16565b905060006123d2868363ffffffff611e6e16565b600584015490915060ff16156125df57600654600384015460405163e31f27c160e01b81526001600160a01b039092169163e31f27c191612417918a90600401613fb3565b600060405180830381600087803b15801561243157600080fd5b505af1158015612445573d6000803e3d6000fd5b50505050612451612b2f565b6001600160a01b031663867904b433612468612821565b6001600160a01b031663654a60ac876003015486631cd554d160e21b6040518463ffffffff1660e01b81526004016124a293929190613fe1565b60206040518083038186803b1580156124ba57600080fd5b505afa1580156124ce573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052506124f291908101906136a2565b6040518363ffffffff1660e01b815260040161250f929190613f34565b600060405180830381600087803b15801561252957600080fd5b505af115801561253d573d6000803e3d6000fd5b5050505060038301546000908152600960205260409020546001600160a01b0316156125da5760038301546000908152600960205260409081902054905163db454a5160e01b81526001600160a01b039091169063db454a51906125a79033908a90600401613f34565b600060405180830381600087803b1580156125c157600080fd5b505af11580156125d5573d6000803e3d6000fd5b505050505b6126c3565b60065460038401546040516375ca5def60e11b81526001600160a01b039092169163eb94bbde91612614918a90600401613fb3565b600060405180830381600087803b15801561262e57600080fd5b505af1158015612642573d6000803e3d6000fd5b50505060038401546000908152600860205260409020546126639150612b46565b6001600160a01b031663867904b433836040518363ffffffff1660e01b8152600401612690929190613f34565b600060405180830381600087803b1580156126aa57600080fd5b505af11580156126be573d6000803e3d6000fd5b505050505b6126d1828460030154612943565b42600884015560405133907f5754fe57f36ac0f121901d7555aba517e6608590429d86a81c662cf3583106549061270b908a908a90613fb3565b60405180910390a282600401548360020154945094505050509250929050565b612733612821565b6001600160a01b0316632528f0fe6004546040518263ffffffff1660e01b81526004016127609190613f97565b60206040518083038186803b15801561277857600080fd5b505afa15801561278c573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052506127b09190810190613636565b15611e6c5760405162461bcd60e51b8152600401610793906140f8565b6127d5613223565b6001600160a01b0316637c3125416040518163ffffffff1660e01b815260040160006040518083038186803b15801561280d57600080fd5b505afa158015610835573d6000803e3d6000fd5b6000611eb26c45786368616e6765526174657360981b613170565b60006106788383670de0b6b3a764000061323d565b600654600782015460038301546005840154604051634002a33360e11b815260009485946001600160a01b03909116936380054666936128999360ff909116906004016142b2565b6040805180830381600087803b1580156128b257600080fd5b505af11580156128c6573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052506128ea91908101906136df565b9150915060008360070154600014612916576004840154612911908463ffffffff61327916565b612919565b60005b6006850154909150612931908263ffffffff611d2716565b60068501555060079092019190915550565b8115610a4857631cd554d160e21b81146129e85761295f612821565b6001600160a01b031663654a60ac8284631cd554d160e21b6040518463ffffffff1660e01b815260040161299593929190613fe1565b60206040518083038186803b1580156129ad57600080fd5b505afa1580156129c1573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052506129e591908101906136a2565b91505b6129f0612b2f565b6001600160a01b031663867904b4612a066132a3565b6001600160a01b031663eb1edd616040518163ffffffff1660e01b815260040160206040518083038186803b158015612a3e57600080fd5b505afa158015612a52573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250612a769190810190613503565b846040518363ffffffff1660e01b8152600401612a94929190613f6a565b600060405180830381600087803b158015612aae57600080fd5b505af1158015612ac2573d6000803e3d6000fd5b50505050612ace6132a3565b6001600160a01b03166322bf55ef836040518263ffffffff1660e01b8152600401612af99190613f97565b600060405180830381600087803b158015612b1357600080fd5b505af1158015612b27573d6000803e3d6000fd5b505050505050565b6000611eb26814de5b9d1a1cd554d160ba1b613170565b6000610f2882613170565b60008281526005602052604090206007810154612b6d90613206565b60018101546001600160a01b03838116911614612b9c5760405162461bcd60e51b815260040161079390614108565b610f2881612851565b6000828152600860205260409020548190612bbf90612b46565b6001600160a01b03166370a08231856040518263ffffffff1660e01b8152600401612bea9190613f26565b60206040518083038186803b158015612c0257600080fd5b505afa158015612c16573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250612c3a91908101906136a2565b1015612c585760405162461bcd60e51b8152600401610793906140a8565b505050565b600080612c6b858585612f0a565b8092508193505050836001600160a01b0316856001600160a01b03167f697721ed1b9d4866cb1aaa0692f62bb3abc1b01c2dafeaad053ffd4532aa7dbb85600001548585604051612cbe93929190613fe1565b60405180910390a3935093915050565b60008111612cee5760405162461bcd60e51b815260040161079390614178565b600682015415612d5057600082600601548211612d0b5781612d11565b82600601545b6006840154909150612d29908263ffffffff611e6e16565b6006840155612d3e828263ffffffff611e6e16565b9150612d4e818460030154612943565b505b8015610a48576004820154612d6b908263ffffffff611e6e16565b6004830155600582015460ff1615612e88576006546003830154604051635246f2b960e01b81526001600160a01b0390921691635246f2b991612db2918590600401613fb3565b600060405180830381600087803b158015612dcc57600080fd5b505af1158015612de0573d6000803e3d6000fd5b5050505060038201546000908152600960205260409020546001600160a01b031615612e8357600382015460009081526009602052604090819020546001840154915163f3fef3a360e01b81526001600160a01b039182169263f3fef3a392612e50929116908590600401613f34565b600060405180830381600087803b158015612e6a57600080fd5b505af1158015612e7e573d6000803e3d6000fd5b505050505b610a48565b600654600383015460405163e50a31b360e01b81526001600160a01b039092169163e50a31b391612af9918590600401613fb3565b612eca8160e00151613206565b42612ee9612ed7306132b8565b6101008401519063ffffffff611d2716565b1115612f075760405162461bcd60e51b8152600401610793906140e8565b50565b6000806000612f2a84600601548560040154611d2790919063ffffffff16565b90508360040154925083600201549150612f4985856003015483612ba5565b6003840154600090815260086020526040902054612f6690612b46565b6001600160a01b0316639dc29fac86836040518363ffffffff1660e01b8152600401612f93929190613f6a565b600060405180830381600087803b158015612fad57600080fd5b505af1158015612fc1573d6000803e3d6000fd5b50505050600584015460ff16156130df576006546003850154600480870154604051635246f2b960e01b81526001600160a01b0390941693635246f2b99361300c9390929101613fb3565b600060405180830381600087803b15801561302657600080fd5b505af115801561303a573d6000803e3d6000fd5b5050505060038401546000908152600960205260409020546001600160a01b0316156130da5760038401546000908152600960205260409081902054600480870154925163f3fef3a360e01b81526001600160a01b039092169263f3fef3a3926130a7928b929101613f6a565b600060405180830381600087803b1580156130c157600080fd5b505af11580156130d5573d6000803e3d6000fd5b505050505b61314c565b600654600385015460048087015460405163e50a31b360e01b81526001600160a01b039094169363e50a31b3936131199390929101613fb3565b600060405180830381600087803b15801561313357600080fd5b505af1158015613147573d6000803e3d6000fd5b505050505b61315e84600601548560030154612943565b6131678461338b565b50935093915050565b60008181526003602090815260408083205490516001600160a01b0390911691821515916131a091869101613ef0565b604051602081830303815290604052906121335760405162461bcd60e51b81526004016107939190614017565b60048101546131db57612f07565b600a5481546131e9906111b4565b11612f075760405162461bcd60e51b815260040161079390614028565b80612f075760405162461bcd60e51b815260040161079390614088565b6000611eb26b53797374656d53746174757360a01b613170565b600080600a8304613254868663ffffffff6133b016565b8161325b57fe5b0490506005600a82061061326d57600a015b600a9004949350505050565b6000670de0b6b3a7640000613294848463ffffffff6133b016565b8161329b57fe5b049392505050565b6000611eb266119959541bdbdb60ca1b613170565b60006132c26133ea565b6001600160a01b03166323257c2b6d53797374656d53657474696e677360901b6f696e746572616374696f6e44656c617960801b85604051602001613308929190613eca565b604051602081830303815290604052805190602001206040518363ffffffff1660e01b815260040161333b929190613fb3565b60206040518083038186803b15801561335357600080fd5b505afa158015613367573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250610f2891908101906136a2565b6000600482018190556002820181905560068201819055600782015542600890910155565b6000826133bf57506000610f28565b828202828482816133cc57fe5b04146106785760405162461bcd60e51b8152600401610793906140c8565b6000611eb26e466c657869626c6553746f7261676560881b613170565b6040518061012001604052806000815260200160006001600160a01b031681526020016000815260200160008019168152602001600081526020016000151581526020016000815260200160008152602001600081525090565b8035610f288161440e565b8051610f288161440e565b60008083601f84011261348957600080fd5b50813567ffffffffffffffff8111156134a157600080fd5b602083019150836020820283011115611f5557600080fd5b8035610f2881614422565b8051610f2881614422565b8035610f288161442b565b8051610f288161442b565b6000602082840312156134f757600080fd5b6000610cf38484613461565b60006020828403121561351557600080fd5b6000610cf3848461346c565b6000806040838503121561353457600080fd5b60006135408585613461565b9250506020613551858286016134cf565b9150509250929050565b60008060006060848603121561357057600080fd5b600061357c8686613461565b935050602061358d868287016134cf565b925050604061359e868287016134cf565b9150509250925092565b600080600080604085870312156135be57600080fd5b843567ffffffffffffffff8111156135d557600080fd5b6135e187828801613477565b9450945050602085013567ffffffffffffffff81111561360057600080fd5b61360c87828801613477565b95989497509550505050565b60006020828403121561362a57600080fd5b6000610cf384846134b9565b60006020828403121561364857600080fd5b6000610cf384846134c4565b6000806040838503121561366757600080fd5b600061367385856134c4565b9250506020613551858286016134c4565b60006020828403121561369657600080fd5b6000610cf384846134cf565b6000602082840312156136b457600080fd5b6000610cf384846134da565b600080604083850312156136d357600080fd5b600061354085856134cf565b600080604083850312156136f257600080fd5b60006136fe85856134da565b9250506020613551858286016134da565b600061371b83836137ae565b505060200190565b61372c8161437b565b82525050565b61372c8161436b565b61372c6137478261436b565b6143ed565b600061375782614347565b613761818561434b565b935061376c83614341565b8060005b8381101561379a578151613784888261370f565b975061378f83614341565b925050600101613770565b509495945050505050565b61372c81614376565b61372c81610950565b61372c6137c382610950565b610950565b61372c81614382565b60006137dc82614347565b6137e6818561434b565b93506137f681856020860161438d565b6137ff816143fe565b9093019392505050565b6000613816600e8361434b565b6d43726174696f20746f6f206c6f7760901b815260200192915050565b600061384060358361434b565b7f596f75206d757374206265206e6f6d696e61746564206265666f726520796f7581527402063616e20616363657074206f776e65727368697605c1b602082015260400192915050565b600061389760158361434b565b74082e4e4c2f240d8cadccee8d040dad2e6dac2e8c6d605b1b815260200192915050565b60006138c8600f8361434b565b6e151c985b9cd9995c8819985a5b1959608a1b815260200192915050565b60006138f360148361434b565b734e6f7420616c6c6f77656420746f20697373756560601b815260200192915050565b6000613923601b8361434b565b7f536166654d6174683a206164646974696f6e206f766572666c6f770000000000815260200192915050565b600061395c600e8361434b565b6d131bd85b881a5cc818db1bdcd95960921b815260200192915050565b6000613986601e8361434b565b7f536166654d6174683a207375627472616374696f6e206f766572666c6f770000815260200192915050565b60006139bf601183614354565b70026b4b9b9b4b7339030b2323932b9b99d1607d1b815260110192915050565b60006139ec60128361434b565b714e6f7420656e6f7567682062616c616e636560701b815260200192915050565b6000613a1a602f8361434b565b7f4f6e6c792074686520636f6e7472616374206f776e6572206d6179207065726681526e37b936903a3434b99030b1ba34b7b760891b602082015260400192915050565b6000613a6b60218361434b565b7f536166654d6174683a206d756c7469706c69636174696f6e206f766572666c6f8152607760f81b602082015260400192915050565b6000613aae601a8361434b565b7f44656274206c696d6974206f7220696e76616c69642072617465000000000000815260200192915050565b6000613ae760138361434b565b72149958d95b9d1b1e481a5b9d195c9858dd1959606a1b815260200192915050565b6000613b16601983614354565b7f5265736f6c766572206d697373696e67207461726765743a2000000000000000815260190192915050565b6000613b4f600c8361434b565b6b496e76616c6964207261746560a01b815260200192915050565b6000613b7760108361434b565b6f26bab9ba103132903137b93937bbb2b960811b815260200192915050565b6000610f28600083614354565b6000613bb0600d8361434b565b6c13dc195b88191a5cd8589b1959609a1b815260200192915050565b6000613bd960178361434b565b7f4465706f736974206d7573742062652061626f76652030000000000000000000815260200192915050565b6000613c1260178361434b565b7f457863656564206d617820626f72726f7720706f776572000000000000000000815260200192915050565b6000613c4b60158361434b565b74139bdd08195b9bdd59da0818dbdb1b185d195c985b605a1b815260200192915050565b6000613c7c60168361434b565b7543726174696f2061626f7665206c697120726174696f60501b815260200192915050565b6000613cae601f8361434b565b7f5265656e7472616e637947756172643a207265656e7472616e742063616c6c00815260200192915050565b6000613ce760178361434b565b7f5061796d656e74206d7573742062652061626f76652030000000000000000000815260200192915050565b8051610120830190613d2584826137ae565b506020820151613d386020850182613732565b506040820151613d4b60408501826137ae565b506060820151613d5e60608501826137ae565b506080820151613d7160808501826137ae565b5060a0820151613d8460a08501826137a5565b5060c0820151613d9760c08501826137ae565b5060e0820151613daa60e08501826137ae565b506101008201516108356101008501826137ae565b8054610120830190613dd0816143df565b613dda85826137ae565b50506001820154613dea816143b9565b613df76020860182613732565b50506002820154613e07816143df565b613e1460408601826137ae565b50506003820154613e24816143df565b613e3160608601826137ae565b50506004820154613e41816143df565b613e4e60808601826137ae565b50506005820154613e5e816143cc565b613e6b60a08601826137a5565b50506006820154613e7b816143df565b613e8860c08601826137ae565b50506007820154613e98816143df565b613ea560e08601826137ae565b50506008820154613eb5816143df565b613ec36101008601826137ae565b5050505050565b6000613ed682856137b7565b602082019150613ee6828461373b565b5060140192915050565b6000613efb826139b2565b9150613f0782846137b7565b50602001919050565b6000613efb82613b09565b6000610f2882613b96565b60208101610f288284613732565b60408101613f428285613723565b61067860208301846137ae565b60408101613f5d8285613732565b6106786020830184613732565b60408101613f428285613732565b60208082528101610678818461374c565b60208101610f2882846137a5565b60208101610f2882846137ae565b60408101613f5d82856137ae565b60408101613f4282856137ae565b60408101613fcf82856137ae565b8181036020830152610cf381846137d1565b60608101613fef82866137ae565b613ffc60208301856137ae565b610cf360408301846137ae565b60208101610f2882846137c8565b6020808252810161067881846137d1565b60208082528101610f2881613809565b60208082528101610f2881613833565b60208082528101610f288161388a565b60208082528101610f28816138bb565b60208082528101610f28816138e6565b60208082528101610f2881613916565b60208082528101610f288161394f565b60208082528101610f2881613979565b60208082528101610f28816139df565b60208082528101610f2881613a0d565b60208082528101610f2881613a5e565b60208082528101610f2881613aa1565b60208082528101610f2881613ada565b60208082528101610f2881613b42565b60208082528101610f2881613b6a565b60208082528101610f2881613ba3565b60208082528101610f2881613bcc565b60208082528101610f2881613c05565b60208082528101610f2881613c3e565b60208082528101610f2881613c6f565b60208082528101610f2881613ca1565b60208082528101610f2881613cda565b61014081016141978285613d13565b6106786101208301846137ae565b61016081016141b48286613d13565b6141c26101208301856137ae565b610cf36101408301846137ae565b61014081016141978285613dbf565b61016081016141b48286613dbf565b61012081016141fd828c6137ae565b61420a602083018b613732565b614217604083018a6137ae565b61422460608301896137ae565b61423160808301886137ae565b61423e60a08301876137a5565b61424b60c08301866137ae565b61425860e08301856137ae565b6142666101008301846137ae565b9a9950505050505050505050565b6080810161428282876137ae565b61428f6020830186613723565b61429c60408301856137ae565b6142a960608301846137ae565b95945050505050565b606081016142c082866137ae565b6142cd60208301856137ae565b610cf360408301846137a5565b608081016142e882876137ae565b61428f60208301866137ae565b60a0810161430382886137ae565b61431060208301876137ae565b61431d60408301866137ae565b61432a60608301856137ae565b61433760808301846137ae565b9695505050505050565b60200190565b5190565b90815260200190565b919050565b6001600160a01b031690565b60ff1690565b6000610f2882614359565b151590565b6000610f28825b6000610f288261436b565b60005b838110156143a8578181015183820152602001614390565b838111156108355750506000910152565b6000610f286143c783610950565b614359565b6000610f286143da83610950565b614365565b6000610f286137c383610950565b6000610f28826000610f2882614408565b601f01601f191690565b60601b90565b6144178161436b565b8114612f0757600080fd5b61441781614376565b6144178161095056fea365627a7a723158207ea733850eab679e1a0115a60d2318d891ec530c9063b5a96fa092a6360815b46c6578706572696d656e74616cf564736f6c63430005100040", + "abi": [ + { + "inputs": [ + { + "internalType": "address", + "name": "_owner", + "type": "address" + }, + { + "internalType": "contract ICollateralManager", + "name": "_manager", + "type": "address" + }, + { + "internalType": "address", + "name": "_resolver", + "type": "address" + }, + { + "internalType": "bytes32", + "name": "_collateralKey", + "type": "bytes32" + }, + { + "internalType": "uint256", + "name": "_minCratio", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "_minCollateral", + "type": "uint256" + } + ], + "payable": false, + "stateMutability": "nonpayable", + "type": "constructor" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "bytes32", + "name": "name", + "type": "bytes32" + }, + { + "indexed": false, + "internalType": "address", + "name": "destination", + "type": "address" + } + ], + "name": "CacheUpdated", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "bool", + "name": "canOpenLoans", + "type": "bool" + } + ], + "name": "CanOpenLoansUpdated", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "account", + "type": "address" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "id", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "amountDeposited", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "collateralAfter", + "type": "uint256" + } + ], + "name": "CollateralDeposited", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "account", + "type": "address" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "id", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "amountWithdrawn", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "collateralAfter", + "type": "uint256" + } + ], + "name": "CollateralWithdrawn", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "uint256", + "name": "issueFeeRate", + "type": "uint256" + } + ], + "name": "IssueFeeRateUpdated", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "account", + "type": "address" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "id", + "type": "uint256" + } + ], + "name": "LoanClosed", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "account", + "type": "address" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "id", + "type": "uint256" + }, + { + "indexed": true, + "internalType": "address", + "name": "liquidator", + "type": "address" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "amountLiquidated", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "collateralLiquidated", + "type": "uint256" + } + ], + "name": "LoanClosedByLiquidation", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "account", + "type": "address" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "id", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "amountRepaid", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "collateralAfter", + "type": "uint256" + } + ], + "name": "LoanClosedByRepayment", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "account", + "type": "address" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "id", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "amount", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "collateral", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "bytes32", + "name": "currency", + "type": "bytes32" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "issuanceFee", + "type": "uint256" + } + ], + "name": "LoanCreated", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "account", + "type": "address" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "id", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "amount", + "type": "uint256" + } + ], + "name": "LoanDrawnDown", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "account", + "type": "address" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "id", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "address", + "name": "liquidator", + "type": "address" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "amountLiquidated", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "collateralLiquidated", + "type": "uint256" + } + ], + "name": "LoanPartiallyLiquidated", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "account", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "repayer", + "type": "address" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "id", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "amountRepaid", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "amountAfter", + "type": "uint256" + } + ], + "name": "LoanRepaymentMade", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "uint256", + "name": "minCollateral", + "type": "uint256" + } + ], + "name": "MinCollateralUpdated", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "address", + "name": "oldOwner", + "type": "address" + }, + { + "indexed": false, + "internalType": "address", + "name": "newOwner", + "type": "address" + } + ], + "name": "OwnerChanged", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "address", + "name": "newOwner", + "type": "address" + } + ], + "name": "OwnerNominated", + "type": "event" + }, + { + "constant": false, + "inputs": [], + "name": "acceptOwnership", + "outputs": [], + "payable": false, + "stateMutability": "nonpayable", + "type": "function" + }, + { + "constant": false, + "inputs": [ + { + "internalType": "address", + "name": "rewardsContract", + "type": "address" + }, + { + "internalType": "bytes32", + "name": "synth", + "type": "bytes32" + } + ], + "name": "addRewardsContracts", + "outputs": [], + "payable": false, + "stateMutability": "nonpayable", + "type": "function" + }, + { + "constant": false, + "inputs": [ + { + "internalType": "bytes32[]", + "name": "_synthNamesInResolver", + "type": "bytes32[]" + }, + { + "internalType": "bytes32[]", + "name": "_synthKeys", + "type": "bytes32[]" + } + ], + "name": "addSynths", + "outputs": [], + "payable": false, + "stateMutability": "nonpayable", + "type": "function" + }, + { + "constant": true, + "inputs": [ + { + "internalType": "bytes32[]", + "name": "_synthNamesInResolver", + "type": "bytes32[]" + }, + { + "internalType": "bytes32[]", + "name": "_synthKeys", + "type": "bytes32[]" + } + ], + "name": "areSynthsAndCurrenciesSet", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "payable": false, + "stateMutability": "view", + "type": "function" + }, + { + "constant": true, + "inputs": [], + "name": "canOpenLoans", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "payable": false, + "stateMutability": "view", + "type": "function" + }, + { + "constant": false, + "inputs": [ + { + "internalType": "uint256", + "name": "amount", + "type": "uint256" + } + ], + "name": "claim", + "outputs": [], + "payable": false, + "stateMutability": "nonpayable", + "type": "function" + }, + { + "constant": false, + "inputs": [ + { + "internalType": "uint256", + "name": "id", + "type": "uint256" + } + ], + "name": "close", + "outputs": [ + { + "internalType": "uint256", + "name": "amount", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "collateral", + "type": "uint256" + } + ], + "payable": false, + "stateMutability": "nonpayable", + "type": "function" + }, + { + "constant": true, + "inputs": [], + "name": "collateralKey", + "outputs": [ + { + "internalType": "bytes32", + "name": "", + "type": "bytes32" + } + ], + "payable": false, + "stateMutability": "view", + "type": "function" + }, + { + "constant": true, + "inputs": [ + { + "internalType": "uint256", + "name": "id", + "type": "uint256" + } + ], + "name": "collateralRatio", + "outputs": [ + { + "internalType": "uint256", + "name": "cratio", + "type": "uint256" + } + ], + "payable": false, + "stateMutability": "view", + "type": "function" + }, + { + "constant": false, + "inputs": [ + { + "internalType": "address", + "name": "borrower", + "type": "address" + }, + { + "internalType": "uint256", + "name": "id", + "type": "uint256" + } + ], + "name": "deposit", + "outputs": [ + { + "internalType": "uint256", + "name": "principal", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "collateral", + "type": "uint256" + } + ], + "payable": true, + "stateMutability": "payable", + "type": "function" + }, + { + "constant": false, + "inputs": [ + { + "internalType": "uint256", + "name": "id", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "amount", + "type": "uint256" + } + ], + "name": "draw", + "outputs": [ + { + "internalType": "uint256", + "name": "principal", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "collateral", + "type": "uint256" + } + ], + "payable": false, + "stateMutability": "nonpayable", + "type": "function" + }, + { + "constant": true, + "inputs": [], + "name": "isResolverCached", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "payable": false, + "stateMutability": "view", + "type": "function" + }, + { + "constant": true, + "inputs": [], + "name": "issueFeeRate", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "payable": false, + "stateMutability": "view", + "type": "function" + }, + { + "constant": false, + "inputs": [ + { + "internalType": "address", + "name": "borrower", + "type": "address" + }, + { + "internalType": "uint256", + "name": "id", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "amount", + "type": "uint256" + } + ], + "name": "liquidate", + "outputs": [], + "payable": false, + "stateMutability": "nonpayable", + "type": "function" + }, + { + "constant": true, + "inputs": [ + { + "internalType": "uint256", + "name": "id", + "type": "uint256" + } + ], + "name": "liquidationAmount", + "outputs": [ + { + "internalType": "uint256", + "name": "liqAmount", + "type": "uint256" + } + ], + "payable": false, + "stateMutability": "view", + "type": "function" + }, + { + "constant": true, + "inputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "name": "loans", + "outputs": [ + { + "internalType": "uint256", + "name": "id", + "type": "uint256" + }, + { + "internalType": "address payable", + "name": "account", + "type": "address" + }, + { + "internalType": "uint256", + "name": "collateral", + "type": "uint256" + }, + { + "internalType": "bytes32", + "name": "currency", + "type": "bytes32" + }, + { + "internalType": "uint256", + "name": "amount", + "type": "uint256" + }, + { + "internalType": "bool", + "name": "short", + "type": "bool" + }, + { + "internalType": "uint256", + "name": "accruedInterest", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "interestIndex", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "lastInteraction", + "type": "uint256" + } + ], + "payable": false, + "stateMutability": "view", + "type": "function" + }, + { + "constant": true, + "inputs": [], + "name": "manager", + "outputs": [ + { + "internalType": "contract ICollateralManager", + "name": "", + "type": "address" + } + ], + "payable": false, + "stateMutability": "view", + "type": "function" + }, + { + "constant": true, + "inputs": [ + { + "internalType": "uint256", + "name": "amount", + "type": "uint256" + }, + { + "internalType": "bytes32", + "name": "currency", + "type": "bytes32" + } + ], + "name": "maxLoan", + "outputs": [ + { + "internalType": "uint256", + "name": "max", + "type": "uint256" + } + ], + "payable": false, + "stateMutability": "view", + "type": "function" + }, + { + "constant": true, + "inputs": [], + "name": "minCollateral", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "payable": false, + "stateMutability": "view", + "type": "function" + }, + { + "constant": true, + "inputs": [], + "name": "minCratio", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "payable": false, + "stateMutability": "view", + "type": "function" + }, + { + "constant": false, + "inputs": [ + { + "internalType": "address", + "name": "_owner", + "type": "address" + } + ], + "name": "nominateNewOwner", + "outputs": [], + "payable": false, + "stateMutability": "nonpayable", + "type": "function" + }, + { + "constant": true, + "inputs": [], + "name": "nominatedOwner", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "payable": false, + "stateMutability": "view", + "type": "function" + }, + { + "constant": false, + "inputs": [ + { + "internalType": "uint256", + "name": "amount", + "type": "uint256" + }, + { + "internalType": "bytes32", + "name": "currency", + "type": "bytes32" + } + ], + "name": "open", + "outputs": [ + { + "internalType": "uint256", + "name": "id", + "type": "uint256" + } + ], + "payable": true, + "stateMutability": "payable", + "type": "function" + }, + { + "constant": true, + "inputs": [], + "name": "owner", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "payable": false, + "stateMutability": "view", + "type": "function" + }, + { + "constant": true, + "inputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "name": "pendingWithdrawals", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "payable": false, + "stateMutability": "view", + "type": "function" + }, + { + "constant": false, + "inputs": [], + "name": "rebuildCache", + "outputs": [], + "payable": false, + "stateMutability": "nonpayable", + "type": "function" + }, + { + "constant": false, + "inputs": [ + { + "internalType": "address", + "name": "borrower", + "type": "address" + }, + { + "internalType": "uint256", + "name": "id", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "amount", + "type": "uint256" + } + ], + "name": "repay", + "outputs": [ + { + "internalType": "uint256", + "name": "principal", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "collateral", + "type": "uint256" + } + ], + "payable": false, + "stateMutability": "nonpayable", + "type": "function" + }, + { + "constant": true, + "inputs": [], + "name": "resolver", + "outputs": [ + { + "internalType": "contract AddressResolver", + "name": "", + "type": "address" + } + ], + "payable": false, + "stateMutability": "view", + "type": "function" + }, + { + "constant": true, + "inputs": [], + "name": "resolverAddressesRequired", + "outputs": [ + { + "internalType": "bytes32[]", + "name": "addresses", + "type": "bytes32[]" + } + ], + "payable": false, + "stateMutability": "view", + "type": "function" + }, + { + "constant": false, + "inputs": [ + { + "internalType": "bool", + "name": "_canOpenLoans", + "type": "bool" + } + ], + "name": "setCanOpenLoans", + "outputs": [], + "payable": false, + "stateMutability": "nonpayable", + "type": "function" + }, + { + "constant": false, + "inputs": [ + { + "internalType": "uint256", + "name": "_issueFeeRate", + "type": "uint256" + } + ], + "name": "setIssueFeeRate", + "outputs": [], + "payable": false, + "stateMutability": "nonpayable", + "type": "function" + }, + { + "constant": false, + "inputs": [ + { + "internalType": "uint256", + "name": "_minCollateral", + "type": "uint256" + } + ], + "name": "setMinCollateral", + "outputs": [], + "payable": false, + "stateMutability": "nonpayable", + "type": "function" + }, + { + "constant": true, + "inputs": [ + { + "internalType": "bytes32", + "name": "", + "type": "bytes32" + } + ], + "name": "shortingRewards", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "payable": false, + "stateMutability": "view", + "type": "function" + }, + { + "constant": true, + "inputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "name": "synths", + "outputs": [ + { + "internalType": "bytes32", + "name": "", + "type": "bytes32" + } + ], + "payable": false, + "stateMutability": "view", + "type": "function" + }, + { + "constant": true, + "inputs": [ + { + "internalType": "bytes32", + "name": "", + "type": "bytes32" + } + ], + "name": "synthsByKey", + "outputs": [ + { + "internalType": "bytes32", + "name": "", + "type": "bytes32" + } + ], + "payable": false, + "stateMutability": "view", + "type": "function" + }, + { + "constant": false, + "inputs": [ + { + "internalType": "uint256", + "name": "id", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "amount", + "type": "uint256" + } + ], + "name": "withdraw", + "outputs": [ + { + "internalType": "uint256", + "name": "principal", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "collateral", + "type": "uint256" + } + ], + "payable": false, + "stateMutability": "nonpayable", + "type": "function" + } + ], + "source": { + "keccak256": "0xd83c3667bcacd4d33cf0fc649cee03d85e7d3914abb7ce6c9898e6ee1d66da81", + "urls": [ + "bzz-raw://77bd631dcee01051f611ea2927feb37c87f5532ce90537b2dc9b8952b85c3576", + "dweb:/ipfs/QmWHiciYDoG7sNnyA7kUzeSmQy4EVV8ktrDNVHDuQhq7tx" + ] + }, + "metadata": { + "compiler": { + "version": "0.5.16+commit.9c3226ce" + }, + "language": "Solidity", + "settings": { + "compilationTarget": { + "CollateralEth.sol": "CollateralEth" + }, + "evmVersion": "istanbul", + "libraries": {}, + "optimizer": { + "enabled": true, + "runs": 200 + }, + "remappings": [] + }, + "sources": { + "CollateralEth.sol": { + "keccak256": "0xd83c3667bcacd4d33cf0fc649cee03d85e7d3914abb7ce6c9898e6ee1d66da81", + "urls": [ + "bzz-raw://77bd631dcee01051f611ea2927feb37c87f5532ce90537b2dc9b8952b85c3576", + "dweb:/ipfs/QmWHiciYDoG7sNnyA7kUzeSmQy4EVV8ktrDNVHDuQhq7tx" + ] + } + }, + "version": 1 + } } } } diff --git a/publish/deployed/kovan-ovm/params.json b/publish/deployed/kovan-ovm/params.json index 140bbd85c8..f6b7f5807e 100644 --- a/publish/deployed/kovan-ovm/params.json +++ b/publish/deployed/kovan-ovm/params.json @@ -18,7 +18,7 @@ "SHORTS": ["sBTC", "sETH", "sLINK", "sUNI", "sAAVE"], "MAX_DEBT": "75000000000000000000000000", "MAX_SKEW_RATE": "200000000000000000", - "BASE_BORROW_RATE": "158443823", + "BASE_BORROW_RATE": "950662938", "BASE_SHORT_RATE": "158443823" } }, @@ -32,5 +32,14 @@ "INTERACTION_DELAY": "0", "COLLAPSE_FEE_RATE": "0" } + }, + { + "name": "COLLATERAL_ETH", + "value": { + "SYNTHS": ["sUSD"], + "MIN_CRATIO": "1200000000000000000", + "MIN_COLLATERAL": "100000000000000000", + "ISSUE_FEE_RATE": "0" + } } ] diff --git a/publish/deployed/kovan-ovm/versions.json b/publish/deployed/kovan-ovm/versions.json index 1398c7e67e..32a1899fae 100644 --- a/publish/deployed/kovan-ovm/versions.json +++ b/publish/deployed/kovan-ovm/versions.json @@ -778,8 +778,9 @@ }, "Synthetix": { "address": "0xCAA5c8e9E67BBa010D2D7F589F02d588Fb49f93D", - "status": "current", - "keccak256": "0xbbe79669c2586e5f0a7f35a0577d21b881e0e083b9fc297fa16d6c1de064c749" + "status": "replaced", + "keccak256": "0xbbe79669c2586e5f0a7f35a0577d21b881e0e083b9fc297fa16d6c1de064c749", + "replaced_in": "v2.56.0-alpha" }, "DebtCache": { "address": "0xCF4a31F3C7E245F8de884907aEFF1841C042cF41", @@ -967,5 +968,41 @@ "keccak256": "0x582cac91f50cfefc154d67097081d0dd0d4877333df51c3cfe19485f80ef7e74" } } + }, + "v2.56.0-alpha": { + "tag": "v2.56.0-alpha", + "fulltag": "v2.56.0-alpha", + "release": "Alhena", + "network": "kovan", + "date": "2021-12-07T16:13:13-05:00", + "commit": "79b5acd4a4557d9892409dad70f139f54cf5e6d5", + "contracts": { + "Synthetix": { + "address": "0x752b2e77769a8832E657CB9f7318543e03c13627", + "status": "replaced", + "keccak256": "0x87c92d55af4f4b01932ec25770439770836ed60b9652a3f4ab1ec6e0d82d302a", + "replaced_in": "v2.56.1-alpha" + } + } + }, + "v2.56.1-alpha": { + "tag": "v2.56.1-alpha", + "fulltag": "v2.56.1-alpha", + "release": "Alhena", + "network": "kovan", + "date": "2021-12-15T05:19:57-05:00", + "commit": "64f28868700af3cbc1fae6522d7fd29d059d7577", + "contracts": { + "Synthetix": { + "address": "0x099B3881d63d3Eef0ec32783Aa64B726672213E2", + "status": "current", + "keccak256": "0x87c92d55af4f4b01932ec25770439770836ed60b9652a3f4ab1ec6e0d82d302a" + }, + "CollateralEth": { + "address": "0xc7960401a5Ca5A201d41Cf6532C7d2803f8D5Ce4", + "status": "current", + "keccak256": "0xd83c3667bcacd4d33cf0fc649cee03d85e7d3914abb7ce6c9898e6ee1d66da81" + } + } } } diff --git a/publish/deployed/local-ovm/params.json b/publish/deployed/local-ovm/params.json index d670223afe..d0978a2684 100644 --- a/publish/deployed/local-ovm/params.json +++ b/publish/deployed/local-ovm/params.json @@ -18,7 +18,7 @@ "SHORTS": ["sBTC", "sETH"], "MAX_DEBT": "75000000000000000000000000", "MAX_SKEW_RATE": "200000000000000000", - "BASE_BORROW_RATE": "158443823", + "BASE_BORROW_RATE": "950662938", "BASE_SHORT_RATE": "158443823" } }, @@ -32,5 +32,14 @@ "INTERACTION_DELAY": "0", "COLLAPSE_FEE_RATE": "0" } + }, + { + "name": "COLLATERAL_ETH", + "value": { + "SYNTHS": ["sUSD"], + "MIN_CRATIO": "1200000000000000000", + "MIN_COLLATERAL": "100000000000000000", + "ISSUE_FEE_RATE": "0" + } } ] diff --git a/publish/deployed/mainnet-ovm/config.json b/publish/deployed/mainnet-ovm/config.json index c9b42bcc8f..913ffbe2d9 100644 --- a/publish/deployed/mainnet-ovm/config.json +++ b/publish/deployed/mainnet-ovm/config.json @@ -160,5 +160,8 @@ }, "OwnerRelayOnOptimism": { "deploy": false + }, + "CollateralEth": { + "deploy": false } } diff --git a/publish/deployed/mainnet-ovm/deployment.json b/publish/deployed/mainnet-ovm/deployment.json index 4dc25e311c..38be0e7262 100644 --- a/publish/deployed/mainnet-ovm/deployment.json +++ b/publish/deployed/mainnet-ovm/deployment.json @@ -209,11 +209,11 @@ }, "Synthetix": { "name": "Synthetix", - "address": "0xff4287311138ad3BD051F84524B2eA3A682944a5", + "address": "0x20eBfbdD14c9D8093E9AC33e736Ac61bbaC90092", "source": "MintableSynthetix", - "link": "https://explorer.optimism.io/address/0xff4287311138ad3BD051F84524B2eA3A682944a5", - "timestamp": "2021-10-12T21:03:08.000Z", - "txn": "https://explorer.optimism.io/tx/0xb45b1bb22fc0a0abb33a4ff5d8c46c95379ed89a09d077918607d2de963ccc22", + "link": "https://explorer.optimism.io/address/0x20eBfbdD14c9D8093E9AC33e736Ac61bbaC90092", + "timestamp": "2021-12-15T21:06:22.000Z", + "txn": "https://explorer.optimism.io/tx/0x21e75db0194e9bdd96e9e37c701d72e5f973f9c8bfca82d474fd4e7c79314f50", "network": "mainnet" }, "ProxySynthetix": { @@ -512,6 +512,15 @@ "timestamp": "2021-11-23T23:07:05.000Z", "txn": "https://explorer.optimism.io/tx/0x8b3f33579f65c9d5f4a1eaf78fff47b6e42d481b9686575303be4f5bc6c30433", "network": "mainnet" + }, + "CollateralEth": { + "name": "CollateralEth", + "address": "0x308AD16ef90fe7caCb85B784A603CB6E71b1A41a", + "source": "CollateralEth", + "link": "https://explorer.optimism.io/address/0x308AD16ef90fe7caCb85B784A603CB6E71b1A41a", + "timestamp": "2021-12-15T21:06:22.000Z", + "txn": "https://explorer.optimism.io/tx/0x7849ba45a602863e4d20a0abd2d0e7d7d9ca6b5b369da56356b91f382a7e034a", + "network": "mainnet" } }, "sources": { @@ -14292,7 +14301,7 @@ } }, "MintableSynthetix": { - "bytecode": "60806040523480156200001c57600080620000196200039d565b50505b506040516200579538038062005795833981810160405260a08110156200004d576000806200004a6200039d565b50505b8101908080519291906020018051929190602001805192919060200180519291906020018051925086915085905084848480858560405160408082018152601782527f53796e746865746978204e6574776f726b20546f6b656e0000000000000000006020830152516040808201905260038152620a69cb60eb1b60208201528660128986816001600160a01b038116620001395760405162461bcd60e51b815260206004820152601960248201527f4f776e657220616464726573732063616e6e6f74206265203000000000000000604482015260640160405180910390620001366200039d565b50505b806000600181620001496200040a565b816001600160a01b0302191690836001600160a01b03160217906200016d6200046c565b5050507fb532073b38c83145e3e5135377a08bf9aab55bc0fd7c1179cd4fb995d2a5159c6000826040516001600160a01b039283168152911660208201526040908101905180910390a15060008080620001c66200040a565b906101000a90046001600160a01b03166001600160a01b03161415620002315760405162461bcd60e51b815260206004820152601160248201527013dddb995c881b5d5cdd081899481cd95d607a1b6044820152606401604051809103906200022e6200039d565b50505b806002600181620002416200040a565b816001600160a01b0302191690836001600160a01b0316021790620002656200046c565b5050507ffc80377ca9c49cc11ae6982f390a42db976d5530af7c43889264b13fbbd7c57e816040516001600160a01b03909116815260200160405180910390a150856005600181620002b66200040a565b816001600160a01b0302191690836001600160a01b0316021790620002da6200046c565b505050846006908051620002f3929160200190620004bb565b50600784805162000309929160200190620004bb565b5082806008620003186200046c565b50505081600960006101000a816200032f6200040a565b8160ff021916908360ff16021790620003476200046c565b5050505050505050505080600960016101000a81620003656200040a565b816001600160a01b0302191690836001600160a01b0316021790620003896200046c565b505050505050505050505050505062000597565b632a2a7adb598160e01b8152600481016020815285602082015260005b86811015620003d7578086015182820160400152602001620003ba565b506020828760640184336000905af158600e01573d6000803e3d6000fd5b3d6001141558600a015760016000f35b505050565b6303daa959598160e01b8152836004820152602081602483336000905af158600e01573d6000803e3d6000fd5b3d6001141558600a015760016000f35b8051935060005b604081101562000467576000828201526020016200044e565b505050565b6322bd64c0598160e01b8152836004820152846024820152600081604483336000905af158600e01573d6000803e3d6000fd5b3d6001141558600a015760016000f35b6000815260206200044e565b8280620004c76200040a565b600181600116156101000203166002900490600052602060002090601f016020900481019282601f106200051257805160ff191683800117856200050a6200046c565b505062000558565b82800160010185620005236200046c565b5050821562000558579182015b828111156200055857825182620005466200046c565b50509160200191906001019062000530565b50620005669291506200056a565b5090565b6200059491905b80821115620005665760008082620005886200046c565b50505060010162000571565b90565b6151ee80620005a76000396000f3fe608060405234801561001957600080610016614d0c565b50505b50600436106104055760003560e01c8063741853601161021e578063a9059cbb1161012e578063d8a1f76f116100c1578063e8e09b8b11610090578063e8e09b8b14610e23578063e90dd9e214610e58578063ec55688914610e60578063edef719a14610e68578063ee52a2f314610e9d57610405565b8063d8a1f76f14610d89578063dbf6334014610daf578063dd62ed3e14610db7578063e6203ed114610dee57610405565b8063c2bf3880116100fd578063c2bf388014610cd6578063c836fa0a14610d0b578063d37c4d8b14610d4c578063d67bdd2514610d8157610405565b8063a9059cbb14610c2f578063ace88afd14610c64578063af086c7e14610c9f578063bc67f83214610ca757610405565b80639324cac7116101b1578063987757dd11610180578063987757dd14610b745780639cbdaeb614610b9a5780639f76980714610ba2578063a311c7c214610bd1578063a5fdc5de14610c0057610405565b80639324cac714610b2d57806395d89b4114610b3557806397107d6d14610b3d5780639741fb2214610b6c57610405565b8063899ffef4116101ed578063899ffef414610aa85780638a29001414610ab05780638da5cb5b14610ad657806391e56b6814610ade57610405565b80637418536014610a4c57806379ba509714610a54578063835e119c14610a5c57806383d625d414610a8257610405565b80632c955fa7116103195780634e99bda9116102ac5780636ac0bf9c1161027b5780636ac0bf9c146109095780636c00f310146109385780636f01a9861461098757806370a08231146109c257806372cb051f146109f157610405565b80634e99bda91461087d57806353a47bb7146108855780635af090ef1461088d578063666ed4f1146108d457610405565b8063313ce567116102e8578063313ce567146107fa578063320223db1461080257806332608039146108315780633e89b9e51461085757610405565b80632c955fa71461072e5780632d3169eb1461075d5780632e0f26251461079557806330ead760146107b357610405565b80631627540c1161039c5780631fce304d1161036b5780631fce304d1461069357806323b872dd146106b9578063295da87d146106f85780632a9053181461071e5780632af64bd31461072657610405565b80631627540c1461062557806316b2213f1461065457806318160ddd14610683578063188214001461068b57610405565b80630e30963c116103d85780630e30963c146105405780631137aedf146105995780631249c58b146105ec578063131b0ae7146105f457610405565b806304f3bcec1461041357806305b3c1c91461043757806306fdde0314610478578063095ea7b3146104f7575b600080610410614d0c565b50505b61041b610ecf565b6040516001600160a01b03909116815260200160405180910390f35b6104666004803603602081101561045657600080610453614d0c565b50505b50356001600160a01b0316610eee565b60405190815260200160405180910390f35b610480610fad565b60405160208082528190810183818151815260200191508051906020019080838360005b838110156104bc5780820151838201526020016104a4565b50505050905090810190601f1680156104e95780820380516001836020036101000a031916815260200191505b509250505060405180910390f35b61052c6004803603604081101561051657600080610513614d0c565b50505b506001600160a01b038135169060200135611066565b604051901515815260200160405180910390f35b6105786004803603608081101561055f5760008061055c614d0c565b50505b5080359060208101359060408101359060600135611164565b6040519182526001600160a01b031660208201526040908101905180910390f35b6105c8600480360360208110156105b8576000806105b5614d0c565b50505b50356001600160a01b0316611178565b60405180848152602001838152602001828152602001935050505060405180910390f35b61052c611252565b6106236004803603602081101561061357600080610610614d0c565b50505b50356001600160a01b031661125f565b005b6106236004803603602081101561064457600080610641614d0c565b50505b50356001600160a01b031661129d565b6104666004803603602081101561067357600080610670614d0c565b50505b50356001600160a01b0316611317565b61046661136a565b610480611377565b61052c600480360360208110156106b2576000806106af614d0c565b50505b50356113ae565b61052c600480360360608110156106d8576000806106d5614d0c565b50505b506001600160a01b03813581169160208101359091169060400135611493565b6106236004803603602081101561071757600080610714614d0c565b50505b50356114df565b6104806115aa565b61052c6115c7565b6106236004803603602081101561074d5760008061074a614d0c565b50505b50356001600160a01b0316611756565b6106236004803603608081101561077c57600080610779614d0c565b50505b50803590602081013590604081013590606001356117dc565b61079d611961565b60405160ff909116815260200160405180910390f35b610466600480360360a08110156107d2576000806107cf614d0c565b50505b508035906020810135906040810135906001600160a01b036060820135169060800135611966565b61079d611ae7565b610623600480360360208110156108215760008061081e614d0c565b50505b50356001600160a01b0316611b00565b61041b600480360360208110156108505760008061084d614d0c565b50505b5035611b33565b6104666004803603602081101561087657600080610873614d0c565b50505b5035611b7e565b61052c611bd2565b61041b611c7a565b610466600480360360a08110156108ac576000806108a9614d0c565b50505b508035906020810135906040810135906001600160a01b036060820135169060800135611c86565b610623600480360360408110156108f3576000806108f0614d0c565b50505b506001600160a01b038135169060200135611c99565b6104666004803603602081101561092857600080610925614d0c565b50505b50356001600160a01b0316611caf565b610623600480360360c081101561095757600080610954614d0c565b50505b506001600160a01b03813581169160208101359160408201359160608101359160808201359160a0013516611e49565b610623600480360360608110156109a6576000806109a3614d0c565b50505b506001600160a01b038135169060208101359060400135611fee565b610466600480360360208110156109e1576000806109de614d0c565b50505b50356001600160a01b0316612171565b6109f96121d7565b60405160208082528190810183818151815260200191508051906020019060200280838360005b83811015610a38578082015183820152602001610a20565b505050509050019250505060405180910390f35b610623612332565b610623612562565b61041b60048036036020811015610a7b57600080610a78614d0c565b50505b50356126e5565b61046660048036036020811015610aa157600080610a9e614d0c565b50505b5035612730565b6109f9612784565b61062360048036036020811015610acf57600080610acc614d0c565b50505b5035612804565b61041b612836565b610466600480360360c0811015610afd57600080610afa614d0c565b50505b506001600160a01b0381358116916020810135916040820135916060810135916080820135169060a00135612841565b61046661298b565b610480612996565b61062360048036036020811015610b5c57600080610b59614d0c565b50505b50356001600160a01b0316612a38565b610623612ab3565b6105c860048036036020811015610b9357600080610b90614d0c565b50505b5035612b76565b61041b612c33565b61062360048036036020811015610bc157600080610bbe614d0c565b50505b50356001600160a01b0316612c3f565b61046660048036036020811015610bf057600080610bed614d0c565b50505b50356001600160a01b0316612c83565b61046660048036036020811015610c1f57600080610c1c614d0c565b50505b50356001600160a01b0316612cd6565b61052c60048036036040811015610c4e57600080610c4b614d0c565b50505b506001600160a01b038135169060200135612d29565b61062360048036036060811015610c8357600080610c80614d0c565b50505b506001600160a01b038135169060208101359060400135612d91565b610623612e0e565b61062360048036036020811015610cc657600080610cc3614d0c565b50505b50356001600160a01b0316612e40565b61062360048036036040811015610cf557600080610cf2614d0c565b50505b506001600160a01b038135169060200135612e56565b61046660048036036080811015610d2a57600080610d27614d0c565b50505b506001600160a01b038135169060208101359060408101359060600135612f23565b61046660048036036040811015610d6b57600080610d68614d0c565b50505b506001600160a01b03813516906020013561306e565b61041b613134565b61062360048036036020811015610da857600080610da5614d0c565b50505b5035613140565b61046661320e565b61046660048036036040811015610dd657600080610dd3614d0c565b50505b506001600160a01b038135811691602001351661324b565b61052c60048036036040811015610e0d57600080610e0a614d0c565b50505b506001600160a01b0381351690602001356132b9565b61062360048036036040811015610e4257600080610e3f614d0c565b50505b506001600160a01b0381351690602001356132c3565b61041b6132f6565b61041b613302565b61062360048036036040811015610e8757600080610e84614d0c565b50505b506001600160a01b03813516906020013561330e565b61046660048036036060811015610ebc57600080610eb9614d0c565b50505b50803590602081013590604001356134d4565b60016009610edb614d77565b906101000a90046001600160a01b031681565b6000610ef8613672565b6001600160a01b03166305b3c1c9836040516001600160e01b031960e084901b1681526001600160a01b0390911660048201526024016020604051808303818680610f41614dd2565b158015610f5657600080610f53614d0c565b50505b505a610f60614e1e565b5050505050158015610f7f573d6000803e3d6000610f7c614d0c565b50505b505050506040513d6020811015610f9e57600080610f9b614d0c565b50505b81019080805195945050505050565b600680610fb8614d77565b600181600116156101000203166002900480601f016020809104026020016040519081016040528181529190602083018280610ff2614d77565b6001816001161561010002031660029004801561105e5780601f1061102c57610100808361101e614d77565b04028352916020019161105e565b820191906000526020600020905b81611043614d77565b8152906001019060200180831161103a57829003601f168201915b505050505081565b600061107061368b565b600080600461107d614d77565b906101000a90046001600160a01b03169050600560009061109c614d77565b906101000a90046001600160a01b03166001600160a01b031663da46098c8286866040516001600160e01b031960e086901b1681526001600160a01b03938416600482015291909216602482015260448101919091526064016000604051808303816000878061110a614dd2565b15801561111f5760008061111c614d0c565b50505b505a611129614f09565b505050505050158015611149573d6000803e3d6000611146614d0c565b50505b50505050611158818585613761565b60019150505b92915050565b60008061116f613844565b94509492505050565b6000806000611185613672565b6001600160a01b0316631137aedf856040516001600160e01b031960e084901b1681526001600160a01b03909116600482015260240160606040518083038186806111ce614dd2565b1580156111e3576000806111e0614d0c565b50505b505a6111ed614e1e565b505050505015801561120c573d6000803e3d6000611209614d0c565b50505b505050506040513d606081101561122b57600080611228614d0c565b50505b81019080805192919060200180519291906020018051949993985093965091945050505050565b600061125c613844565b90565b611267613896565b806003600181611275614d77565b816001600160a01b0302191690836001600160a01b0316021790611297614fcf565b50505050565b6112a5613896565b80600180806112b2614d77565b816001600160a01b0302191690836001600160a01b03160217906112d4614fcf565b5050507f906a1c6bd7e3091ea86693dd029a831c19049ce77f1dce2ce0bab1cacbabce22816040516001600160a01b03909116815260200160405180910390a150565b6000611321613672565b6001600160a01b03166316b2213f836040516001600160e01b031960e084901b1681526001600160a01b0390911660048201526024016020604051808303818680610f41614dd2565b6008611374614d77565b81565b60405160408082019052601781527f53796e746865746978204e6574776f726b20546f6b656e000000000000000000602082015281565b6000806113b961390e565b6001600160a01b031663059c29ec600060046113d3614d77565b906101000a90046001600160a01b0316856040516001600160e01b031960e085901b1681526001600160a01b03909216600483015260248201526044016020604051808303818680611423614dd2565b15801561143857600080611435614d0c565b50505b505a611442614e1e565b5050505050158015611461573d6000803e3d600061145e614d0c565b50505b505050506040513d60208110156114805760008061147d614d0c565b50505b8101908080519390931195945050505050565b600061149d61368b565b6114a5613925565b6114af848361399b565b506114d7600060046114bf614d77565b906101000a90046001600160a01b0316858585613cb0565b949350505050565b6114e7613de2565b6114ef61368b565b6114f7613672565b6001600160a01b031663b06e8c6560006004611511614d77565b906101000a90046001600160a01b0316836040516001600160e01b031960e085901b1681526001600160a01b039092166004830152602482015260440160006040518083038160008780611563614dd2565b15801561157857600080611575614d0c565b50505b505a611582614f09565b5050505050501580156115a2573d6000803e3d600061159f614d0c565b50505b505050505b50565b6040516040808201905260038152620a69cb60eb1b602082015281565b600060606115d3612784565b905060005b815181101561174d5760008282815181106115ef57fe5b60200260200101516000818152600a60205290915060409020600090611613614d77565b6001600160a01b036101009290920a90041660016009611631614d77565b906101000a90046001600160a01b03166001600160a01b03166321f8a721836040516001600160e01b031960e084901b16815260048101919091526024016020604051808303818680611682614dd2565b15801561169757600080611694614d0c565b50505b505a6116a1614e1e565b50505050501580156116c0573d6000803e3d60006116bd614d0c565b50505b505050506040513d60208110156116df576000806116dc614d0c565b50505b8101908080516001600160a01b031693909314159250829150611733905057506000818152600a60205260408120600090611718614d77565b906101000a90046001600160a01b03166001600160a01b0316145b15611744576000935050505061125c565b506001016115d8565b50600191505090565b61175e613de2565b61176661368b565b61176e613672565b6001600160a01b0316632b3f41aa8260006004611789614d77565b906101000a90046001600160a01b03166040516001600160e01b031960e085901b1681526001600160a01b0392831660048201529116602482015260440160006040518083038160008780611563614dd2565b6117e4613e1d565b600060026117f0614d77565b906101000a90046001600160a01b03166001600160a01b031663907dff9784848460405160200180848152602001838152602001828152602001935050505060405160208183030381529060405260026040518060316150c1823960310190506040518091039020886000806040518763ffffffff1660e01b815260040180806020018781526020018681526020018581526020018460001b81526020018360001b8152602001828103825288818151815260200191508051906020019080838360005b838110156118cc5780820151838201526020016118b4565b50505050905090810190601f1680156118f95780820380516001836020036101000a031916815260200191505b5097505050505050505060006040518083038160008780611918614dd2565b15801561192d5760008061192a614d0c565b50505b505a611937614f09565b505050505050158015611957573d6000803e3d6000611954614d0c565b50505b5050505050505050565b601281565b600085846119748282613e97565b61197c61368b565b61198461390e565b6001600160a01b0316634f8633d26000600461199e614d77565b906101000a90046001600160a01b031660046000906119bb614d77565b906101000a90046001600160a01b03168b8b8b60046000906119db614d77565b906101000a90046001600160a01b031660008d8d6040516001600160e01b031960e08c901b1681526001600160a01b03998a1660048201529789166024890152604488019690965260648701949094526084860192909252851660a4850152151560c484015290921660e482015261010481019190915261012401604080518083038160008780611a6a614dd2565b158015611a7f57600080611a7c614d0c565b50505b505a611a89614f09565b505050505050158015611aa9573d6000803e3d6000611aa6614d0c565b50505b505050506040513d6040811015611ac857600080611ac5614d0c565b50505b810190808051929190602001805150929b9a5050505050505050505050565b60006009611af3614d77565b906101000a900460ff1681565b611b08613de2565b611b1061368b565b611b18613672565b6001600160a01b031663fd864ccf8260006004611789614d77565b6000611b3d613672565b6001600160a01b03166332608039836040516001600160e01b031960e084901b16815260048101919091526024016020604051808303818680610f41614dd2565b6000611b88613672565b6001600160a01b0316637b1001b78360016040516001600160e01b031960e085901b1681526004810192909252151560248201526044016020604051808303818680610f41614dd2565b6000611bdc613672565b6001600160a01b0316634e99bda96040518163ffffffff1660e01b81526004016020604051808303818680611c0f614dd2565b158015611c2457600080611c21614d0c565b50505b505a611c2e614e1e565b5050505050158015611c4d573d6000803e3d6000611c4a614d0c565b50505b505050506040513d6020811015611c6c57600080611c69614d0c565b50505b810190808051935050505090565b60006001610edb614d77565b6000611c90613844565b95945050505050565b611ca1613f21565b611cab8282613f9b565b5050565b6000611cb9613672565b6001600160a01b0316636bed04158360006005611cd4614d77565b906101000a90046001600160a01b03166001600160a01b03166370a08231866040516001600160e01b031960e084901b1681526001600160a01b0390911660048201526024016020604051808303818680611d2d614dd2565b158015611d4257600080611d3f614d0c565b50505b505a611d4c614e1e565b5050505050158015611d6b573d6000803e3d6000611d68614d0c565b50505b505050506040513d6020811015611d8a57600080611d87614d0c565b50505b81019080805192506040915050516001600160e01b031960e085901b1681526001600160a01b039092166004830152602482015260440160408051808303818680611dd3614dd2565b158015611de857600080611de5614d0c565b50505b505a611df2614e1e565b5050505050158015611e11573d6000803e3d6000611e0e614d0c565b50505b505050506040513d6040811015611e3057600080611e2d614d0c565b50505b8101908080519291906020018051509295945050505050565b611e51613e1d565b60006002611e5d614d77565b906101000a90046001600160a01b03166001600160a01b031663907dff9786868686866040516020810195909552604080860194909452606085019290925260808401526001600160a01b031660a083015260c09091019051602081830303815290604052600260405180603e6150f28239603e0190506040518091039020611ee58b61419d565b6000806040518763ffffffff1660e01b815260040180806020018781526020018681526020018581526020018460001b81526020018360001b8152602001828103825288818151815260200191508051906020019080838360005b83811015611f58578082015183820152602001611f40565b50505050905090810190601f168015611f855780820380516001836020036101000a031916815260200191505b5097505050505050505060006040518083038160008780611fa4614dd2565b158015611fb957600080611fb6614d0c565b50505b505a611fc3614f09565b505050505050158015611954573d6000803e3d6000611fe0614d0c565b505050505050505050505050565b611ff6613e1d565b60006002612002614d77565b906101000a90046001600160a01b03166001600160a01b031663907dff978383604051602001808381526020018281526020019250505060405160208183030381529060405260026040518060276151a682396027019050604051809103902061206b8861419d565b6000806040518763ffffffff1660e01b815260040180806020018781526020018681526020018581526020018460001b81526020018360001b8152602001828103825288818151815260200191508051906020019080838360005b838110156120de5780820151838201526020016120c6565b50505050905090810190601f16801561210b5780820380516001836020036101000a031916815260200191505b509750505050505050506000604051808303816000878061212a614dd2565b15801561213f5760008061213c614d0c565b50505b505a612149614f09565b50505050505015801561159f573d6000803e3d6000612166614d0c565b505050505050505050565b600080600561217e614d77565b906101000a90046001600160a01b03166001600160a01b03166370a08231836040516001600160e01b031960e084901b1681526001600160a01b0390911660048201526024016020604051808303818680610f41614dd2565b60606121e1613672565b6001600160a01b03166372cb051f6040518163ffffffff1660e01b81526004016000604051808303818680612214614dd2565b15801561222957600080612226614d0c565b50505b505a612233614e1e565b5050505050158015612252573d6000803e3d600061224f614d0c565b50505b505050506040513d6000823e601f3d908101601f19168201604052602081101561228457600080612281614d0c565b50505b81019080805160405193929190846401000000008211156122ad576000806122aa614d0c565b50505b9083019060208201858111156122cb576000806122c8614d0c565b50505b82518660208202830111640100000000821117156122f1576000806122ee614d0c565b50505b825250602001908051906020019060200280838360005b83811015612320578082015183820152602001612308565b50505050905001604052505050905090565b606061233c612784565b905060005b8151811015611cab57600082828151811061235857fe5b60200260200101519050600060016009612370614d77565b906101000a90046001600160a01b03166001600160a01b031663dacb2d0183846040517f5265736f6c766572206d697373696e67207461726765743a2000000000000000602082015260398101919091526059016040516020818303038152906040526040518363ffffffff1660e01b81526004018083815260200180602001828103825283818151815260200191508051906020019080838360005b8381101561242557808201518382015260200161240d565b50505050905090810190601f1680156124525780820380516001836020036101000a031916815260200191505b509350505050602060405180830381868061246b614dd2565b1580156124805760008061247d614d0c565b50505b505a61248a614e1e565b50505050501580156124a9573d6000803e3d60006124a6614d0c565b50505b505050506040513d60208110156124c8576000806124c5614d0c565b50505b8101908080516000868152600a602052909450849350604092509050206001816124f0614d77565b816001600160a01b0302191690836001600160a01b0316021790612512614fcf565b5050507f88a93678a3692f6789d9546fc621bf7234b101ddb7d4fe479455112831b8aa6882826040519182526001600160a01b031660208201526040908101905180910390a15050600101612341565b6000600161256e614d77565b906101000a90046001600160a01b03166001600160a01b03165a61259061501d565b6001600160a01b0316146125de5760405162461bcd60e51b815260040180806020018281038252603581526020018061506460359139604001915050604051809103906125db614d0c565b50505b7fb532073b38c83145e3e5135377a08bf9aab55bc0fd7c1179cd4fb995d2a5159c60008061260a614d77565b906101000a90046001600160a01b03166001600090612627614d77565b906101000a90046001600160a01b03166040516001600160a01b039283168152911660208201526040908101905180910390a160006001612666614d77565b906101000a90046001600160a01b03166000806101000a81612686614d77565b816001600160a01b0302191690836001600160a01b03160217906126a8614fcf565b5050506000600160006101000a816126be614d77565b816001600160a01b0302191690836001600160a01b03160217906126e0614fcf565b505050565b60006126ef613672565b6001600160a01b031663835e119c836040516001600160e01b031960e084901b16815260048101919091526024016020604051808303818680610f41614dd2565b600061273a613672565b6001600160a01b0316637b1001b78360006040516001600160e01b031960e085901b1681526004810192909252151560248201526044016020604051808303818680610f41614dd2565b60608061278f6141a9565b9050606060016040519080825280602002602001820160405280156127be578160200160208202803883390190505b5090507453796e746865746978427269646765546f4261736560581b816000815181106127e757fe5b6020026020010181815250506127fd82826142a9565b9250505090565b61280c613de2565b61281461368b565b61281c613672565b6001600160a01b031663042e068860006004611511614d77565b600080610edb614d77565b6000858461284f8282613e97565b61285761368b565b61285f61390e565b6001600160a01b0316634f8633d28a6000600461287a614d77565b906101000a90046001600160a01b03168b8b8b8f60008d8d6040516001600160e01b031960e08c901b1681526001600160a01b03998a1660048201529789166024890152604488019690965260648701949094526084860192909252851660a4850152151560c484015290921660e48201526101048101919091526101240160408051808303816000878061290d614dd2565b1580156129225760008061291f614d0c565b50505b505a61292c614f09565b50505050505015801561294c573d6000803e3d6000612949614d0c565b50505b505050506040513d604081101561296b57600080612968614d0c565b50505b810190808051929190602001805150929c9b505050505050505050505050565b631cd554d160e21b81565b6007806129a1614d77565b600181600116156101000203166002900480601f0160208091040260200160405190810160405281815291906020830182806129db614d77565b6001816001161561010002031660029004801561105e5780601f10612a0757610100808361101e614d77565b820191906000526020600020905b81612a1e614d77565b81529060010190602001808311612a155750859350505050565b612a40613896565b806002600181612a4e614d77565b816001600160a01b0302191690836001600160a01b0316021790612a70614fcf565b5050507ffc80377ca9c49cc11ae6982f390a42db976d5530af7c43889264b13fbbd7c57e816040516001600160a01b03909116815260200160405180910390a150565b612abb613de2565b612ac361368b565b612acb613672565b6001600160a01b031663497d704a60006004612ae5614d77565b906101000a90046001600160a01b03166040516001600160e01b031960e084901b1681526001600160a01b03909116600482015260240160006040518083038160008780612b31614dd2565b158015612b4657600080612b43614d0c565b50505b505a612b50614f09565b505050505050158015611297573d6000803e3d6000612b6d614d0c565b5050505050505b565b6000806000612b8361368b565b612b8b61390e565b6001600160a01b0316631b16802c60006004612ba5614d77565b906101000a90046001600160a01b0316866040516001600160e01b031960e085901b1681526001600160a01b039092166004830152602482015260440160606040518083038160008780612bf7614dd2565b158015612c0c57600080612c09614d0c565b50505b505a612c16614f09565b50505050505015801561120c573d6000803e3d6000611209614d0c565b60006003610edb614d77565b612c47614365565b806005600181612c55614d77565b816001600160a01b0302191690836001600160a01b0316021790612c77614fcf565b5050506115a7816144f0565b6000612c8d613672565b6001600160a01b031663a311c7c2836040516001600160e01b031960e084901b1681526001600160a01b0390911660048201526024016020604051808303818680610f41614dd2565b6000612ce0613672565b6001600160a01b031663a5fdc5de836040516001600160e01b031960e084901b1681526001600160a01b0390911660048201526024016020604051808303818680610f41614dd2565b6000612d3361368b565b612d3b613925565b612d6060006004612d4a614d77565b906101000a90046001600160a01b03168361399b565b50612d8760006004612d70614d77565b906101000a90046001600160a01b03168484614638565b5060019392505050565b612d99613e1d565b60006002612da5614d77565b906101000a90046001600160a01b03166001600160a01b031663907dff9783836040516020018083815260200182815260200192505050604051602081830303815290604052600260405180602861509982396028019050604051809103902061206b8861419d565b612e16613de2565b612e1e61368b565b612e26613672565b6001600160a01b031663c897713260006004612ae5614d77565b612e48614645565b806004600181611275614d77565b612e5e613de2565b612e6661368b565b612e6e613672565b6001600160a01b0316639a5154b48360006004612e89614d77565b906101000a90046001600160a01b0316846040516001600160e01b031960e086901b1681526001600160a01b039384166004820152919092166024820152604481019190915260640160006040518083038160008780612ee7614dd2565b158015612efc57600080612ef9614d0c565b50505b505a612f06614f09565b505050505050158015612b6d573d6000803e3d6000611957614d0c565b60008382612f318282613e97565b612f3961368b565b612f4161390e565b6001600160a01b0316634f8633d28860006004612f5c614d77565b906101000a90046001600160a01b03168989898d60008f6000801b6040516001600160e01b031960e08c901b1681526001600160a01b03998a1660048201529789166024890152604488019690965260648701949094526084860192909252851660a4850152151560c484015290921660e482015261010481019190915261012401604080518083038160008780612ff2614dd2565b15801561300757600080613004614d0c565b50505b505a613011614f09565b505050505050158015613031573d6000803e3d600061302e614d0c565b50505b505050506040513d60408110156130505760008061304d614d0c565b50505b810190808051929190602001805150929a9950505050505050505050565b6000613078613672565b6001600160a01b031663d37c4d8b84846040516001600160e01b031960e085901b1681526001600160a01b039092166004830152602482015260440160206040518083038186806130c7614dd2565b1580156130dc576000806130d9614d0c565b50505b505a6130e6614e1e565b5050505050158015613105573d6000803e3d6000613102614d0c565b50505b505050506040513d602081101561312457600080613121614d0c565b50505b8101908080519695505050505050565b60006004610edb614d77565b613148613f21565b6000613152614712565b905061315e8183613f9b565b806001600160a01b03166359974e38836040516001600160e01b031960e084901b1681526004810191909152602401602060405180830381600087806131a2614dd2565b1580156131b7576000806131b4614d0c565b50505b505a6131c1614f09565b5050505050501580156131e1573d6000803e3d60006131de614d0c565b50505b505050506040513d6020811015613200576000806131fd614d0c565b50505b810190808051505050505050565b6000613218613672565b6001600160a01b031663dbf633406040518163ffffffff1660e01b81526004016020604051808303818680611c0f614dd2565b6000806005613258614d77565b906101000a90046001600160a01b03166001600160a01b031663dd62ed3e84846040516001600160e01b031960e085901b1681526001600160a01b0392831660048201529116602482015260440160206040518083038186806130c7614dd2565b600061115e613844565b6132cb613de2565b6132d361368b565b6132db613672565b6001600160a01b03166344ec6b628360006004612e89614d77565b60006005610edb614d77565b60006002610edb614d77565b613316613f21565b61331e613925565b6000600561332a614d77565b6001600160a01b036101009290920a90041663b46310f68361341f8460006005613352614d77565b906101000a90046001600160a01b03166001600160a01b03166370a08231886040516001600160e01b031960e084901b1681526001600160a01b03909116600482015260240160206040518083038186806133ab614dd2565b1580156133c0576000806133bd614d0c565b50505b505a6133ca614e1e565b50505050501580156133e9573d6000803e3d60006133e6614d0c565b50505b505050506040513d602081101561340857600080613405614d0c565b50505b8101908080519392505063ffffffff614733169050565b6040516001600160e01b031960e085901b1681526001600160a01b039092166004830152602482015260440160006040518083038160008780613460614dd2565b15801561347557600080613472614d0c565b50505b505a61347f614f09565b50505050505015801561349f573d6000803e3d600061349c614d0c565b50505b505050506134af82600083614798565b6134c98160086134bd614d77565b9063ffffffff61473316565b8060086115a2614fcf565b600083826134e28282613e97565b6134ea61368b565b6134f261390e565b6001600160a01b0316634f8633d26000600461350c614d77565b906101000a90046001600160a01b03166004600090613529614d77565b906101000a90046001600160a01b03168989896004600090613549614d77565b906101000a90046001600160a01b031660006004600090613568614d77565b906101000a90046001600160a01b03166000801b6040516001600160e01b031960e08c901b1681526001600160a01b03998a1660048201529789166024890152604488019690965260648701949094526084860192909252851660a4850152151560c484015290921660e4820152610104810191909152610124016040805180830381600087806135f7614dd2565b15801561360c57600080613609614d0c565b50505b505a613616614f09565b505050505050158015613636573d6000803e3d6000613633614d0c565b50505b505050506040513d604081101561365557600080613652614d0c565b50505b810190808051929190602001805150929998505050505050505050565b60006136866524b9b9bab2b960d11b614805565b905090565b60006002613697614d77565b906101000a90046001600160a01b03166001600160a01b03165a6136b961501d565b6001600160a01b0316141580156137055750600060036136d7614d77565b906101000a90046001600160a01b03166001600160a01b03165a6136f961501d565b6001600160a01b031614155b801561374657505a61371561501d565b6001600160a01b03166000600461372a614d77565b906101000a90046001600160a01b03166001600160a01b031614155b15612b74575a61375461501d565b60046001816126be614d77565b6000600261376d614d77565b906101000a90046001600160a01b03166001600160a01b031663907dff97826040516020018082815260200191505060405160208183030381529060405260036040518060216151858239602101905060405180910390206137ce8861419d565b6137d78861419d565b60006040518763ffffffff1660e01b815260040180806020018781526020018681526020018581526020018481526020018360001b8152602001828103825288818151815260200191508051602090910190808383600083156120de5780820151838201526020016120c6565b60405162461bcd60e51b815260206004820152601b60248201527f43616e6e6f742062652072756e206f6e2074686973206c617965720000000000604482015260640160405180910390611cab614d0c565b6000806138a1614d77565b906101000a90046001600160a01b03166001600160a01b03165a6138c361501d565b6001600160a01b031614612b745760405162461bcd60e51b815260040180806020018281038252602f815260200180615156602f913960400191505060405180910390611cab614d0c565b60006136866822bc31b430b733b2b960b91b614805565b61392d614906565b6001600160a01b031663086dabd16040518163ffffffff1660e01b81526004016000604051808303818680613960614dd2565b15801561397557600080613972614d0c565b50505b505a61397f614e1e565b5050505050158015611297573d6000803e3d6000612b6d614d0c565b6000806139a6614920565b6001600160a01b0316638b3f8088856040516001600160e01b031960e084901b1681526001600160a01b039091166004820152602401604080518083038186806139ee614dd2565b158015613a0357600080613a00614d0c565b50505b505a613a0d614e1e565b5050505050158015613a2c573d6000803e3d6000613a29614d0c565b50505b505050506040513d6040811015613a4b57600080613a48614d0c565b50505b8101908080519291906020018051509293505082159150611158905057600080613a73613672565b6001600160a01b0316636bed04158760006005613a8e614d77565b906101000a90046001600160a01b03166001600160a01b03166370a082318a6040516001600160e01b031960e084901b1681526001600160a01b0390911660048201526024016020604051808303818680613ae7614dd2565b158015613afc57600080613af9614d0c565b50505b505a613b06614e1e565b5050505050158015613b25573d6000803e3d6000613b22614d0c565b50505b505050506040513d6020811015613b4457600080613b41614d0c565b50505b81019080805192506040915050516001600160e01b031960e085901b1681526001600160a01b039092166004830152602482015260440160408051808303818680613b8d614dd2565b158015613ba257600080613b9f614d0c565b50505b505a613bac614e1e565b5050505050158015613bcb573d6000803e3d6000613bc8614d0c565b50505b505050506040513d6040811015613bea57600080613be7614d0c565b50505b810190808051929190602001805193955092935050505081851115613c495760405162461bcd60e51b81526004018080602001828103825260268152602001806151306026913960400191505060405180910390613c46614d0c565b50505b8015613ca45760405162461bcd60e51b815260206004820152601e60248201527f412073796e7468206f7220534e58207261746520697320696e76616c69640000604482015260640160405180910390613ca1614d0c565b50505b50600195945050505050565b6000806005613cbd614d77565b6001600160a01b036101009290920a90041663da46098c8587613d478660006005613ce6614d77565b906101000a90046001600160a01b03166001600160a01b031663dd62ed3e8b8d6040516001600160e01b031960e085901b1681526001600160a01b0392831660048201529116602482015260440160206040518083038186806133ab614dd2565b6040516001600160e01b031960e086901b1681526001600160a01b039384166004820152919092166024820152604481019190915260640160006040518083038160008780613d94614dd2565b158015613da957600080613da6614d0c565b50505b505a613db3614f09565b505050505050158015613dd3573d6000803e3d6000613dd0614d0c565b50505b50505050611c9084848461493c565b613dea614906565b6001600160a01b0316637c3125416040518163ffffffff1660e01b81526004016000604051808303818680613960614dd2565b613e2561390e565b6001600160a01b03165a613e3761501d565b6001600160a01b031614612b745760405162461bcd60e51b815260206004820152601e60248201527f4f6e6c792045786368616e6765722063616e20696e766f6b6520746869730000604482015260640160405180910390611cab614d0c565b613e9f614906565b6001600160a01b0316631ce00ba283836040516001600160e01b031960e085901b168152600481019290925260248201526044016000604051808303818680613ee6614dd2565b158015613efb57600080613ef8614d0c565b50505b505a613f05614e1e565b5050505050158015612b6d573d6000803e3d6000611957614d0c565b613f29614c80565b6001600160a01b03165a613f3b61501d565b6001600160a01b031614612b745760405162461bcd60e51b815260206004820152601d60248201527f43616e206f6e6c7920626520696e766f6b656420627920627269646765000000604482015260640160405180910390611cab614d0c565b60006005613fa7614d77565b6001600160a01b036101009290920a90041663b46310f68361409c8460006005613fcf614d77565b906101000a90046001600160a01b03166001600160a01b03166370a08231886040516001600160e01b031960e084901b1681526001600160a01b0390911660048201526024016020604051808303818680614028614dd2565b15801561403d5760008061403a614d0c565b50505b505a614047614e1e565b5050505050158015614066573d6000803e3d6000614063614d0c565b50505b505050506040513d602081101561408557600080614082614d0c565b50505b8101908080519392505063ffffffff614ca3169050565b6040516001600160e01b031960e085901b1681526001600160a01b0390921660048301526024820152604401600060405180830381600087806140dd614dd2565b1580156140f2576000806140ef614d0c565b50505b505a6140fc614f09565b50505050505015801561411c573d6000803e3d6000614119614d0c565b50505b505050506141835a63996d79a5598160e01b8152602081600483336000905af158600e01573d6000803e3d6000fd5b3d6001141558600a015760016000f35b8051925060005b604081101561417957600082820152602001614162565b5050508383614798565b6134c9816008614191614d77565b9063ffffffff614ca316565b6001600160a01b031690565b606060056040519080825280602002602001820160405280156141d6578160200160208202803883390190505b5090506d53796e746865746978537461746560901b816000815181106141f857fe5b6020026020010181815250506b53797374656d53746174757360a01b8160018151811061422157fe5b6020026020010181815250506822bc31b430b733b2b960b91b8160028151811061424757fe5b6020026020010181815250506524b9b9bab2b960d11b8160038151811061426a57fe5b602002602001018181525050722932bbb0b93239a234b9ba3934b13aba34b7b760691b8160048151811061429a57fe5b60200260200101818152505090565b606081518351016040519080825280602002602001820160405280156142d9578160200160208202803883390190505b50905060005b835181101561431b578381815181106142f457fe5b602002602001015182828151811061430857fe5b60209081029190910101526001016142df565b5060005b825181101561435e5782818151811061433457fe5b602002602001015182828651018151811061434b57fe5b602090810291909101015260010161431f565b5092915050565b60006002614371614d77565b906101000a90046001600160a01b03166001600160a01b03165a61439361501d565b6001600160a01b0316141580156143df5750600060036143b1614d77565b906101000a90046001600160a01b03166001600160a01b03165a6143d361501d565b6001600160a01b031614155b801561442057505a6143ef61501d565b6001600160a01b031660006004614404614d77565b906101000a90046001600160a01b03166001600160a01b031614155b15614461575a61442e61501d565b600460018161443b614d77565b816001600160a01b0302191690836001600160a01b031602179061445d614fcf565b5050505b60008061446c614d77565b6001600160a01b036101009290920a9004166000600461448a614d77565b906101000a90046001600160a01b03166001600160a01b031614612b745760405162461bcd60e51b815260206004820152601360248201527227bbb732b91037b7363c90333ab731ba34b7b760691b604482015260640160405180910390611cab614d0c565b600060026144fc614d77565b906101000a90046001600160a01b03166001600160a01b031663907dff97826040516001600160a01b0390911660208201526040908101905160208183030381529060405260016040517f546f6b656e5374617465557064617465642861646472657373290000000000008152601a01604051809103902060008060006040518763ffffffff1660e01b815260040180806020018781526020018681526020018560001b81526020018460001b81526020018360001b8152602001828103825288818151815260200191508051906020019080838360005b838110156145ec5780820151838201526020016145d4565b50505050905090810190601f1680156146195780820380516001836020036101000a031916815260200191505b5097505050505050505060006040518083038160008780611563614dd2565b60006114d784848461493c565b60006002614651614d77565b906101000a90046001600160a01b03166001600160a01b03165a61467361501d565b6001600160a01b031614806146bc57506000600361468f614d77565b906101000a90046001600160a01b03166001600160a01b03165a6146b161501d565b6001600160a01b0316145b612b745760405162461bcd60e51b815260206004820152601760248201527f4f6e6c79207468652070726f78792063616e2063616c6c000000000000000000604482015260640160405180910390611cab614d0c565b6000613686722932bbb0b93239a234b9ba3934b13aba34b7b760691b614805565b6000828211156147925760405162461bcd60e51b815260206004820152601e60248201527f536166654d6174683a207375627472616374696f6e206f766572666c6f77000060448201526064016040518091039061478f614d0c565b50505b50900390565b600060026147a4614d77565b906101000a90046001600160a01b03166001600160a01b031663907dff97826040516020018082815260200191505060405160208183030381529060405260036040518060216151cd8239602101905060405180910390206137ce8861419d565b6000818152600a602052806040812060009061481f614d77565b6001600160a01b036101009290920a90041690508015158360405170026b4b9b9b4b7339030b2323932b9b99d1607d1b602082015260318101919091526051016040516020818303038152906040529061435e5760405162461bcd60e51b81526004018080602001828103825283818151815260200191508051906020019080838360005b838110156148bc5780820151838201526020016148a4565b50505050905090810190601f1680156148e95780820380516001836020036101000a031916815260200191505b5092505050604051809103906148fd614d0c565b50505092915050565b60006136866b53797374656d53746174757360a01b614805565b60006136866d53796e746865746978537461746560901b614805565b60006001600160a01b038316158015906149c057505a63996d79a5598160e01b8152602081600483336000905af158600e01573d6000803e3d6000fd5b3d6001141558600a015760016000f35b8051925060005b60408110156149a757600082820152602001614990565b5050506001600160a01b0316836001600160a01b031614155b80156149f95750600060026149d3614d77565b906101000a90046001600160a01b03166001600160a01b0316836001600160a01b031614155b614a525760405162461bcd60e51b815260206004820152601f60248201527f43616e6e6f74207472616e7366657220746f2074686973206164647265737300604482015260640160405180910390614a4f614d0c565b50505b60006005614a5e614d77565b6001600160a01b036101009290920a90041663b46310f685614adf8560006005614a86614d77565b906101000a90046001600160a01b03166001600160a01b03166370a082318a6040516001600160e01b031960e084901b1681526001600160a01b03909116600482015260240160206040518083038186806133ab614dd2565b6040516001600160e01b031960e085901b1681526001600160a01b039092166004830152602482015260440160006040518083038160008780614b20614dd2565b158015614b3557600080614b32614d0c565b50505b505a614b3f614f09565b505050505050158015614b5f573d6000803e3d6000614b5c614d0c565b50505b505050506005600090614b70614d77565b6001600160a01b036101009290920a90041663b46310f684614bf18560006005614b98614d77565b906101000a90046001600160a01b03166001600160a01b03166370a08231896040516001600160e01b031960e084901b1681526001600160a01b0390911660048201526024016020604051808303818680614028614dd2565b6040516001600160e01b031960e085901b1681526001600160a01b039092166004830152602482015260440160006040518083038160008780614c32614dd2565b158015614c4757600080614c44614d0c565b50505b505a614c51614f09565b505050505050158015614c71573d6000803e3d6000614c6e614d0c565b50505b50505050612d87848484614798565b60006136867453796e746865746978427269646765546f4261736560581b614805565b600082820183811015614d055760405162461bcd60e51b815260206004820152601b60248201527f536166654d6174683a206164646974696f6e206f766572666c6f770000000000604482015260640160405180910390614d02614d0c565b50505b9392505050565b632a2a7adb598160e01b8152600481016020815285602082015260005b86811015614d44578086015182820160400152602001614d29565b506020828760640184336000905af158600e01573d6000803e3d6000fd5b3d6001141558600a015760016000f35b505050565b6303daa959598160e01b8152836004820152602081602483336000905af158600e01573d6000803e3d6000fd5b3d6001141558600a015760016000f35b8051935060005b60408110156126e057600082820152602001614dbb565b638435035b598160e01b8152836004820152602081602483336000905af158600e01573d6000803e3d6000fd5b3d6001141558600a015760016000f35b80516000825293506020614dbb565b638540661f598160e01b8152614e4f565b808083111561115e575090919050565b808083101561115e575090919050565b836004820152846024820152606060448201528660648201526084810160005b88811015614e87578088015182820152602001614e6f565b506060828960a40184336000905af158600e01573d6000803e3d6000fd5b3d6001141558600a015760016000f35b815160408301513d6000853e8b8b82606087013350600060045af15059614edc8d3d614e3f565b8c01614ee88187614e2f565b5b82811015614efd5760008152602001614ee9565b50929c50505050505050565b6385979f76598160e01b8152836004820152846024820152606060448201528760648201526084810160005b89811015614f4d578089015182820152602001614f35565b506060828a60a40184336000905af158600e01573d6000803e3d6000fd5b3d6001141558600a015760016000f35b815160408301513d6000853e8c8c82606087013350600060045af15059614fa28e3d614e3f565b8d01614fae8187614e2f565b5b82811015614fc35760008152602001614faf565b50929d50505050505050565b6322bd64c0598160e01b8152836004820152846024820152600081604483336000905af158600e01573d6000803e3d6000fd5b3d6001141558600a015760016000f35b600081526020614dbb565b6373509064598160e01b8152602081600483336000905af158600e01573d6000803e3d6000fd5b3d6001141558600a015760016000f35b80516000825293506020614dbb56fe596f75206d757374206265206e6f6d696e61746564206265666f726520796f752063616e20616363657074206f776e65727368697045786368616e67655265636c61696d28616464726573732c627974657333322c75696e743235362945786368616e6765547261636b696e6728627974657333322c627974657333322c75696e743235362c75696e743235362953796e746845786368616e676528616464726573732c627974657333322c75696e743235362c627974657333322c75696e743235362c616464726573732943616e6e6f74207472616e73666572207374616b6564206f7220657363726f77656420534e584f6e6c792074686520636f6e7472616374206f776e6572206d617920706572666f726d207468697320616374696f6e417070726f76616c28616464726573732c616464726573732c75696e743235362945786368616e676552656261746528616464726573732c627974657333322c75696e74323536295472616e7366657228616464726573732c616464726573732c75696e7432353629", + "bytecode": "60806040523480156200001157600080fd5b50604051620042f7380380620042f7833981810160405260a08110156200003757600080fd5b508051602080830151604080850151606086015160809096015182518084018452601781527f53796e746865746978204e6574776f726b20546f6b656e00000000000000000081870152835180850190945260038452620a69cb60eb1b958401959095529495929490939091869186918691869186918291879187918660128986816001600160a01b03811662000115576040805162461bcd60e51b815260206004820152601960248201527f4f776e657220616464726573732063616e6e6f74206265203000000000000000604482015290519081900360640190fd5b600080546001600160a01b0319166001600160a01b038316908117825560408051928352602083019190915280517fb532073b38c83145e3e5135377a08bf9aab55bc0fd7c1179cd4fb995d2a5159c9281900390910190a1506000546001600160a01b0316620001c0576040805162461bcd60e51b815260206004820152601160248201527013dddb995c881b5d5cdd081899481cd95d607a1b604482015290519081900360640190fd5b600280546001600160a01b0383166001600160a01b0319909116811790915560408051918252517ffc80377ca9c49cc11ae6982f390a42db976d5530af7c43889264b13fbbd7c57e9181900360200190a150600480546001600160a01b0319166001600160a01b038816179055845162000242906005906020880190620002ab565b50835162000258906006906020870190620002ab565b50506007919091556008805460ff191660ff90921691909117610100600160a81b0319166101006001600160a01b0397909716969096029590951790945550620003509c50505050505050505050505050565b828054600181600116156101000203166002900490600052602060002090601f016020900481019282601f10620002ee57805160ff19168380011785556200031e565b828001600101855582156200031e579182015b828111156200031e57825182559160200191906001019062000301565b506200032c92915062000330565b5090565b6200034d91905b808211156200032c576000815560010162000337565b90565b613f9780620003606000396000f3fe608060405234801561001057600080fd5b50600436106103f15760003560e01c80637418536011610215578063ace88afd11610125578063d8a1f76f116100b8578063e8e09b8b11610087578063e8e09b8b14610c94578063e90dd9e214610cc0578063ec55688914610cc8578063edef719a14610cd0578063ee52a2f314610cfc576103f1565b8063d8a1f76f14610c15578063dbf6334014610c32578063dd62ed3e14610c3a578063e6203ed114610c68576103f1565b8063c62e5df8116100f4578063c62e5df814610b7a578063c836fa0a14610ba9578063d37c4d8b14610be1578063d67bdd2514610c0d576103f1565b8063ace88afd14610aee578063af086c7e14610b20578063bc67f83214610b28578063c2bf388014610b4e576103f1565b80639324cac7116101a8578063987757dd11610177578063987757dd14610a335780639f76980714610a50578063a311c7c214610a76578063a5fdc5de14610a9c578063a9059cbb14610ac2576103f1565b80639324cac7146109f557806395d89b41146109fd57806397107d6d14610a055780639741fb2214610a2b576103f1565b8063899ffef4116101e4578063899ffef4146109825780638a2900141461098a5780638da5cb5b146109a757806391e56b68146109af576103f1565b8063741853601461093857806379ba509714610940578063835e119c1461094857806383d625d414610965576103f1565b80632c955fa7116103105780634e99bda9116102a35780636ac0bf9c116102725780636ac0bf9c1461081c5780636c00f310146108425780636f01a9861461088857806370a08231146108ba57806372cb051f146108e0576103f1565b80634e99bda9146107a257806353a47bb7146107aa5780635af090ef146107b2578063666ed4f1146107f0576103f1565b8063313ce567116102df578063313ce5671461073a578063320223db1461074257806332608039146107685780633e89b9e514610785576103f1565b80632c955fa7146106895780632d3169eb146106af5780632e0f2625146106de57806330ead760146106fc576103f1565b806316b2213f1161038857806323b872dd1161035757806323b872dd14610626578063295da87d1461065c5780632a905318146106795780632af64bd314610681576103f1565b806316b2213f146105d357806318160ddd146105f957806318821400146106015780631fce304d14610609576103f1565b80630e30963c116103c45780630e30963c1461050f5780631137aedf1461055f5780631249c58b146105a35780631627540c146105ab576103f1565b806304f3bcec146103f657806305b3c1c91461041a57806306fdde0314610452578063095ea7b3146104cf575b600080fd5b6103fe610d25565b604080516001600160a01b039092168252519081900360200190f35b6104406004803603602081101561043057600080fd5b50356001600160a01b0316610d39565b60408051918252519081900360200190f35b61045a610dca565b6040805160208082528351818301528351919283929083019185019080838360005b8381101561049457818101518382015260200161047c565b50505050905090810190601f1680156104c15780820380516001836020036101000a031916815260200191505b509250505060405180910390f35b6104fb600480360360408110156104e557600080fd5b506001600160a01b038135169060200135610e58565b604080519115158252519081900360200190f35b61053e6004803603608081101561052557600080fd5b5080359060208101359060408101359060600135610ef3565b604080519283526001600160a01b0390911660208301528051918290030190f35b6105856004803603602081101561057557600080fd5b50356001600160a01b0316610f07565b60408051938452602084019290925282820152519081900360600190f35b6104fb610fad565b6105d1600480360360208110156105c157600080fd5b50356001600160a01b0316610fba565b005b610440600480360360208110156105e957600080fd5b50356001600160a01b0316611016565b610440611075565b61045a61107b565b6104fb6004803603602081101561061f57600080fd5b50356110b4565b6104fb6004803603606081101561063c57600080fd5b506001600160a01b03813581169160208101359091169060400135611147565b6105d16004803603602081101561067257600080fd5b5035611184565b61045a61120e565b6104fb61122d565b6105d16004803603602081101561069f57600080fd5b50356001600160a01b031661133e565b6105d1600480360360808110156106c557600080fd5b50803590602081013590604081013590606001356113ad565b6106e66114f0565b6040805160ff9092168252519081900360200190f35b610440600480360360a081101561071257600080fd5b508035906020810135906040810135906001600160a01b0360608201351690608001356114f5565b6106e66115d3565b6105d16004803603602081101561075857600080fd5b50356001600160a01b03166115dc565b6103fe6004803603602081101561077e57600080fd5b503561164b565b6104406004803603602081101561079b57600080fd5b5035611698565b6104fb6116f2565b6103fe611765565b610440600480360360a08110156107c857600080fd5b508035906020810135906040810135906001600160a01b036060820135169060800135611774565b6105d16004803603604081101561080657600080fd5b506001600160a01b038135169060200135611787565b6104406004803603602081101561083257600080fd5b50356001600160a01b031661179d565b6105d1600480360360c081101561085857600080fd5b506001600160a01b03813581169160208101359160408201359160608101359160808201359160a001351661189f565b6105d16004803603606081101561089e57600080fd5b506001600160a01b0381351690602081013590604001356119fe565b610440600480360360208110156108d057600080fd5b50356001600160a01b0316611b3f565b6108e8611b92565b60408051602080825283518183015283519192839290830191858101910280838360005b8381101561092457818101518382015260200161090c565b505050509050019250505060405180910390f35b6105d1611ca2565b6105d1611e7b565b6103fe6004803603602081101561095e57600080fd5b5035611f37565b6104406004803603602081101561097b57600080fd5b5035611f84565b6108e8611fde565b6105d1600480360360208110156109a057600080fd5b5035612052565b6103fe6120bf565b610440600480360360c08110156109c557600080fd5b506001600160a01b0381358116916020810135916040820135916060810135916080820135169060a001356120ce565b6104406121b1565b61045a6121bc565b6105d160048036036020811015610a1b57600080fd5b50356001600160a01b0316612217565b6105d1612273565b61058560048036036020811015610a4957600080fd5b50356122f5565b6105d160048036036020811015610a6657600080fd5b50356001600160a01b0316612375565b61044060048036036020811015610a8c57600080fd5b50356001600160a01b03166123a1565b61044060048036036020811015610ab257600080fd5b50356001600160a01b0316612400565b6104fb60048036036040811015610ad857600080fd5b506001600160a01b03813516906020013561245f565b6105d160048036036060811015610b0457600080fd5b506001600160a01b03813516906020810135906040013561249f565b6105d16124ff565b6105d160048036036020811015610b3e57600080fd5b50356001600160a01b0316612566565b6105d160048036036040811015610b6457600080fd5b506001600160a01b038135169060200135612590565b61044060048036036080811015610b9057600080fd5b5080359060208101359060408101359060600135612622565b61044060048036036080811015610bbf57600080fd5b506001600160a01b03813516906020810135906040810135906060013561262c565b61044060048036036040811015610bf757600080fd5b506001600160a01b03813516906020013561270a565b6103fe6127a4565b6105d160048036036020811015610c2b57600080fd5b50356127b3565b610440612841565b61044060048036036040811015610c5057600080fd5b506001600160a01b0381358116916020013516612883565b6104fb60048036036040811015610c7e57600080fd5b506001600160a01b0381351690602001356128de565b6105d160048036036040811015610caa57600080fd5b506001600160a01b0381351690602001356129c1565b6103fe612a37565b6103fe612a46565b6105d160048036036040811015610ce657600080fd5b506001600160a01b038135169060200135612a55565b61044060048036036060811015610d1257600080fd5b5080359060208101359060400135612b8a565b60085461010090046001600160a01b031681565b6000610d43612c68565b6001600160a01b03166305b3c1c9836040518263ffffffff1660e01b815260040180826001600160a01b03166001600160a01b0316815260200191505060206040518083038186803b158015610d9857600080fd5b505afa158015610dac573d6000803e3d6000fd5b505050506040513d6020811015610dc257600080fd5b505192915050565b6005805460408051602060026001851615610100026000190190941693909304601f81018490048402820184019092528181529291830182828015610e505780601f10610e2557610100808354040283529160200191610e50565b820191906000526020600020905b815481529060010190602001808311610e3357829003601f168201915b505050505081565b6000610e62612c81565b6003546004805460408051633691826360e21b81526001600160a01b03948516938101849052878516602482015260448101879052905192939091169163da46098c9160648082019260009290919082900301818387803b158015610ec657600080fd5b505af1158015610eda573d6000803e3d6000fd5b50505050610ee9818585612cc0565b5060019392505050565b600080610efe612d8a565b94509492505050565b6000806000610f14612c68565b6001600160a01b0316631137aedf856040518263ffffffff1660e01b815260040180826001600160a01b03166001600160a01b0316815260200191505060606040518083038186803b158015610f6957600080fd5b505afa158015610f7d573d6000803e3d6000fd5b505050506040513d6060811015610f9357600080fd5b508051602082015160409092015190969195509350915050565b6000610fb7612d8a565b90565b610fc2612dd7565b600180546001600160a01b0383166001600160a01b0319909116811790915560408051918252517f906a1c6bd7e3091ea86693dd029a831c19049ce77f1dce2ce0bab1cacbabce229181900360200190a150565b6000611020612c68565b6001600160a01b03166316b2213f836040518263ffffffff1660e01b815260040180826001600160a01b03166001600160a01b0316815260200191505060206040518083038186803b158015610d9857600080fd5b60075481565b6040518060400160405280601781526020017f53796e746865746978204e6574776f726b20546f6b656e00000000000000000081525081565b6000806110bf612e20565b600354604080516301670a7b60e21b81526001600160a01b039283166004820152602481018790529051929091169163059c29ec91604480820192602092909190829003018186803b15801561111457600080fd5b505afa158015611128573d6000803e3d6000fd5b505050506040513d602081101561113e57600080fd5b50511192915050565b6000611151612c81565b611159612e37565b6111638483612e8b565b5060035461117c906001600160a01b03168585856130bf565b949350505050565b61118c6131b7565b611194612c81565b61119c612c68565b6003546040805163b06e8c6560e01b81526001600160a01b039283166004820152602481018590529051929091169163b06e8c659160448082019260009290919082900301818387803b1580156111f257600080fd5b505af1158015611206573d6000803e3d6000fd5b505050505b50565b604051806040016040528060038152602001620a69cb60eb1b81525081565b60006060611239611fde565b905060005b815181101561133557600082828151811061125557fe5b6020908102919091018101516000818152600983526040908190205460085482516321f8a72160e01b81526004810185905292519395506001600160a01b0391821694610100909104909116926321f8a721926024808201939291829003018186803b1580156112c457600080fd5b505afa1580156112d8573d6000803e3d6000fd5b505050506040513d60208110156112ee57600080fd5b50516001600160a01b031614158061131b57506000818152600960205260409020546001600160a01b0316155b1561132c5760009350505050610fb7565b5060010161123e565b50600191505090565b6113466131b7565b61134e612c81565b611356612c68565b6003546040805163159fa0d560e11b81526001600160a01b038581166004830152928316602482015290519290911691632b3f41aa9160448082019260009290919082900301818387803b1580156111f257600080fd5b6113b56131f7565b6002805460408051602081018790528082018690526060808201869052825180830390910181526080909101918290526001600160a01b039092169263907dff979291806031613e04823960310190506040518091039020886000806040518763ffffffff1660e01b815260040180806020018781526020018681526020018581526020018460001b81526020018360001b8152602001828103825288818151815260200191508051906020019080838360005b83811015611481578181015183820152602001611469565b50505050905090810190601f1680156114ae5780820380516001836020036101000a031916815260200191505b50975050505050505050600060405180830381600087803b1580156114d257600080fd5b505af11580156114e6573d6000803e3d6000fd5b5050505050505050565b601281565b600085846115038282613264565b61150b612c81565b611513612e20565b600354604080516327c319e960e11b81526001600160a01b039283166004820181905260248201819052604482018d9052606482018c9052608482018b905260a4820152600060c4820181905289841660e4830152610104820189905282519490931693634f8633d29361012480840194938390030190829087803b15801561159b57600080fd5b505af11580156115af573d6000803e3d6000fd5b505050506040513d60408110156115c557600080fd5b505198975050505050505050565b60085460ff1681565b6115e46131b7565b6115ec612c81565b6115f4612c68565b6003546040805163fd864ccf60e01b81526001600160a01b03858116600483015292831660248201529051929091169163fd864ccf9160448082019260009290919082900301818387803b1580156111f257600080fd5b6000611655612c68565b6001600160a01b03166332608039836040518263ffffffff1660e01b81526004018082815260200191505060206040518083038186803b158015610d9857600080fd5b60006116a2612c68565b6001600160a01b0316637b1001b78360016040518363ffffffff1660e01b815260040180838152602001821515151581526020019250505060206040518083038186803b158015610d9857600080fd5b60006116fc612c68565b6001600160a01b0316634e99bda96040518163ffffffff1660e01b815260040160206040518083038186803b15801561173457600080fd5b505afa158015611748573d6000803e3d6000fd5b505050506040513d602081101561175e57600080fd5b5051905090565b6001546001600160a01b031681565b600061177e612d8a565b95945050505050565b61178f6132cb565b6117998282613338565b5050565b60006117a7612c68565b60048054604080516370a0823160e01b81526001600160a01b0387811694820194909452905193831693636bed041593879316916370a08231916024808301926020929190829003018186803b15801561180057600080fd5b505afa158015611814573d6000803e3d6000fd5b505050506040513d602081101561182a57600080fd5b5051604080516001600160e01b031960e086901b1681526001600160a01b03909316600484015260248301919091528051604480840193829003018186803b15801561187557600080fd5b505afa158015611889573d6000803e3d6000fd5b505050506040513d6040811015610dc257600080fd5b6118a76131f7565b60028054604080516020810189905280820188905260608101879052608081018690526001600160a01b0385811660a0808401919091528351808403909101815260c0909201928390529092169263907dff97929180603e613e358239603e019050604051809103902061191a8b613455565b6000806040518763ffffffff1660e01b815260040180806020018781526020018681526020018581526020018460001b81526020018360001b8152602001828103825288818151815260200191508051906020019080838360005b8381101561198d578181015183820152602001611975565b50505050905090810190601f1680156119ba5780820380516001836020036101000a031916815260200191505b50975050505050505050600060405180830381600087803b1580156119de57600080fd5b505af11580156119f2573d6000803e3d6000fd5b50505050505050505050565b611a066131f7565b6002805460408051602081018690528082018590528151808203830181526060909101918290526001600160a01b039092169263907dff979291806027613ee9823960270190506040518091039020611a5e88613455565b6000806040518763ffffffff1660e01b815260040180806020018781526020018681526020018581526020018460001b81526020018360001b8152602001828103825288818151815260200191508051906020019080838360005b83811015611ad1578181015183820152602001611ab9565b50505050905090810190601f168015611afe5780820380516001836020036101000a031916815260200191505b50975050505050505050600060405180830381600087803b158015611b2257600080fd5b505af1158015611b36573d6000803e3d6000fd5b50505050505050565b60048054604080516370a0823160e01b81526001600160a01b03858116948201949094529051600093909216916370a0823191602480820192602092909190829003018186803b158015610d9857600080fd5b6060611b9c612c68565b6001600160a01b03166372cb051f6040518163ffffffff1660e01b815260040160006040518083038186803b158015611bd457600080fd5b505afa158015611be8573d6000803e3d6000fd5b505050506040513d6000823e601f3d908101601f191682016040526020811015611c1157600080fd5b8101908080516040519392919084640100000000821115611c3157600080fd5b908301906020820185811115611c4657600080fd5b8251866020820283011164010000000082111715611c6357600080fd5b82525081516020918201928201910280838360005b83811015611c90578181015183820152602001611c78565b50505050905001604052505050905090565b6060611cac611fde565b905060005b8151811015611799576000828281518110611cc857fe5b602002602001015190506000600860019054906101000a90046001600160a01b03166001600160a01b031663dacb2d01838460405160200180807f5265736f6c766572206d697373696e67207461726765743a20000000000000008152506019018281526020019150506040516020818303038152906040526040518363ffffffff1660e01b81526004018083815260200180602001828103825283818151815260200191508051906020019080838360005b83811015611d93578181015183820152602001611d7b565b50505050905090810190601f168015611dc05780820380516001836020036101000a031916815260200191505b50935050505060206040518083038186803b158015611dde57600080fd5b505afa158015611df2573d6000803e3d6000fd5b505050506040513d6020811015611e0857600080fd5b505160008381526009602090815260409182902080546001600160a01b0319166001600160a01b03851690811790915582518681529182015281519293507f88a93678a3692f6789d9546fc621bf7234b101ddb7d4fe479455112831b8aa68929081900390910190a15050600101611cb1565b6001546001600160a01b03163314611ec45760405162461bcd60e51b8152600401808060200182810382526035815260200180613da76035913960400191505060405180910390fd5b600054600154604080516001600160a01b03938416815292909116602083015280517fb532073b38c83145e3e5135377a08bf9aab55bc0fd7c1179cd4fb995d2a5159c9281900390910190a160018054600080546001600160a01b03199081166001600160a01b03841617909155169055565b6000611f41612c68565b6001600160a01b031663835e119c836040518263ffffffff1660e01b81526004018082815260200191505060206040518083038186803b158015610d9857600080fd5b6000611f8e612c68565b6001600160a01b0316637b1001b78360006040518363ffffffff1660e01b815260040180838152602001821515151581526020019250505060206040518083038186803b158015610d9857600080fd5b606080611fe9613461565b60408051600180825281830190925291925060609190602080830190803883390190505090507453796e746865746978427269646765546f4261736560581b8160008151811061203557fe5b60200260200101818152505061204b8282613554565b9250505090565b61205a6131b7565b612062612c81565b61206a612c68565b600354604080516285c0d160e31b81526001600160a01b039283166004820152602481018590529051929091169163042e06889160448082019260009290919082900301818387803b1580156111f257600080fd5b6000546001600160a01b031681565b600085846120dc8282613264565b6120e4612c81565b6120ec612e20565b600354604080516327c319e960e11b81526001600160a01b038d8116600483018190529381166024830152604482018d9052606482018c9052608482018b905260a4820193909352600060c4820181905289841660e4830152610104820189905282519490931693634f8633d29361012480840194938390030190829087803b15801561217857600080fd5b505af115801561218c573d6000803e3d6000fd5b505050506040513d60408110156121a257600080fd5b50519998505050505050505050565b631cd554d160e21b81565b6006805460408051602060026001851615610100026000190190941693909304601f81018490048402820184019092528181529291830182828015610e505780601f10610e2557610100808354040283529160200191610e50565b61221f612dd7565b600280546001600160a01b0383166001600160a01b0319909116811790915560408051918252517ffc80377ca9c49cc11ae6982f390a42db976d5530af7c43889264b13fbbd7c57e9181900360200190a150565b61227b6131b7565b612283612c81565b61228b612c68565b600354604080516324beb82560e11b81526001600160a01b0392831660048201529051929091169163497d704a9160248082019260009290919082900301818387803b1580156122da57600080fd5b505af11580156122ee573d6000803e3d6000fd5b505050505b565b6000806000612302612c81565b61230a612e20565b600354604080516306c5a00b60e21b81526001600160a01b0392831660048201526024810188905290519290911691631b16802c916044808201926060929091908290030181600087803b15801561236157600080fd5b505af1158015610f7d573d6000803e3d6000fd5b61237d613610565b600480546001600160a01b0319166001600160a01b03831617905561120b816136a9565b60006123ab612c68565b6001600160a01b031663a311c7c2836040518263ffffffff1660e01b815260040180826001600160a01b03166001600160a01b0316815260200191505060206040518083038186803b158015610d9857600080fd5b600061240a612c68565b6001600160a01b031663a5fdc5de836040518263ffffffff1660e01b815260040180826001600160a01b03166001600160a01b0316815260200191505060206040518083038186803b158015610d9857600080fd5b6000612469612c81565b612471612e37565b600354612487906001600160a01b031683612e8b565b50600354610ee9906001600160a01b031684846137d4565b6124a76131f7565b6002805460408051602081018690528082018590528151808203830181526060909101918290526001600160a01b039092169263907dff979291806028613ddc823960280190506040518091039020611a5e88613455565b6125076131b7565b61250f612c81565b612517612c68565b6003546040805163644bb89960e11b81526001600160a01b0392831660048201529051929091169163c89771329160248082019260009290919082900301818387803b1580156122da57600080fd5b61256e6137e1565b600380546001600160a01b0319166001600160a01b0392909216919091179055565b6125986131b7565b6125a0612c81565b6125a8612c68565b60035460408051632694552d60e21b81526001600160a01b03868116600483015292831660248201526044810185905290519290911691639a5154b49160648082019260009290919082900301818387803b15801561260657600080fd5b505af115801561261a573d6000803e3d6000fd5b505050505050565b600061117c612d8a565b6000838261263a8282613264565b612642612c81565b61264a612e20565b600354604080516327c319e960e11b81526001600160a01b038b8116600483018190529381166024830152604482018b9052606482018a90526084820189905260a48201849052600060c4830181905260e483019490945261010482018490528251941693634f8633d29361012480840194938390030190829087803b1580156126d357600080fd5b505af11580156126e7573d6000803e3d6000fd5b505050506040513d60408110156126fd57600080fd5b5051979650505050505050565b6000612714612c68565b6001600160a01b031663d37c4d8b84846040518363ffffffff1660e01b815260040180836001600160a01b03166001600160a01b031681526020018281526020019250505060206040518083038186803b15801561277157600080fd5b505afa158015612785573d6000803e3d6000fd5b505050506040513d602081101561279b57600080fd5b50519392505050565b6003546001600160a01b031681565b6127bb6132cb565b60006127c5613840565b90506127d18183613338565b806001600160a01b03166359974e38836040518263ffffffff1660e01b815260040180828152602001915050602060405180830381600087803b15801561281757600080fd5b505af115801561282b573d6000803e3d6000fd5b505050506040513d60208110156122ee57600080fd5b600061284b612c68565b6001600160a01b031663dbf633406040518163ffffffff1660e01b815260040160206040518083038186803b15801561173457600080fd5b6004805460408051636eb1769f60e11b81526001600160a01b0386811694820194909452848416602482015290516000939092169163dd62ed3e91604480820192602092909190829003018186803b15801561277157600080fd5b60006128e8612e37565b6128f0612c81565b6000806128fb612c68565b6003546040805163298f137d60e21b81526001600160a01b0389811660048301526024820189905292831660448201528151939092169263a63c4df49260648082019392918290030181600087803b15801561295657600080fd5b505af115801561296a573d6000803e3d6000fd5b505050506040513d604081101561298057600080fd5b50805160209091015160035491935091506129a9908690849084906001600160a01b0316613861565b60035461177e9086906001600160a01b0316846137d4565b6129c96131b7565b6129d1612c81565b6129d9612c68565b6003546040805163227635b160e11b81526001600160a01b038681166004830152928316602482015260448101859052905192909116916344ec6b629160648082019260009290919082900301818387803b15801561260657600080fd5b6004546001600160a01b031681565b6002546001600160a01b031681565b612a5d6132cb565b612a65612e37565b60048054604080516370a0823160e01b81526001600160a01b03868116948201949094529051929091169163b46310f6918591612afd91869186916370a08231916024808301926020929190829003018186803b158015612ac557600080fd5b505afa158015612ad9573d6000803e3d6000fd5b505050506040513d6020811015612aef57600080fd5b50519063ffffffff61393816565b6040518363ffffffff1660e01b815260040180836001600160a01b03166001600160a01b0316815260200182815260200192505050600060405180830381600087803b158015612b4c57600080fd5b505af1158015612b60573d6000803e3d6000fd5b50505050612b7082600083613995565b600754612b83908263ffffffff61393816565b6007555050565b60008382612b988282613264565b612ba0612c81565b612ba8612e20565b600354604080516327c319e960e11b81526001600160a01b039283166004820181905260248201819052604482018b9052606482018a90526084820189905260a48201819052600060c4830181905260e4830191909152610104820181905282519490931693634f8633d29361012480840194938390030190829087803b158015612c3257600080fd5b505af1158015612c46573d6000803e3d6000fd5b505050506040513d6040811015612c5c57600080fd5b50519695505050505050565b6000612c7c6524b9b9bab2b960d11b6139e8565b905090565b6002546001600160a01b03163314801590612ca757506003546001600160a01b03163314155b156122f357600380546001600160a01b03191633179055565b60025460408051602080820185905282518083039091018152908201918290526001600160a01b039092169163907dff9791600390806021613ec8823960210190506040518091039020612d1388613455565b612d1c88613455565b60006040518763ffffffff1660e01b815260040180806020018781526020018681526020018581526020018481526020018360001b81526020018281038252888181518152602001915080519060200190808383600083811015611ad1578181015183820152602001611ab9565b6040805162461bcd60e51b815260206004820152601b60248201527f43616e6e6f742062652072756e206f6e2074686973206c617965720000000000604482015290519081900360640190fd5b6000546001600160a01b031633146122f35760405162461bcd60e51b815260040180806020018281038252602f815260200180613e99602f913960400191505060405180910390fd5b6000612c7c6822bc31b430b733b2b960b91b6139e8565b612e3f613ac5565b6001600160a01b031663086dabd16040518163ffffffff1660e01b815260040160006040518083038186803b158015612e7757600080fd5b505afa1580156122ee573d6000803e3d6000fd5b600080612e96613adf565b60408051631167f01160e31b81526001600160a01b0387811660048301528251931692638b3f808892602480840193919291829003018186803b158015612edc57600080fd5b505afa158015612ef0573d6000803e3d6000fd5b505050506040513d6040811015612f0657600080fd5b505190508015610ee957600080612f1b612c68565b60048054604080516370a0823160e01b81526001600160a01b038b811694820194909452905193831693636bed0415938b9316916370a08231916024808301926020929190829003018186803b158015612f7457600080fd5b505afa158015612f88573d6000803e3d6000fd5b505050506040513d6020811015612f9e57600080fd5b5051604080516001600160e01b031960e086901b1681526001600160a01b03909316600484015260248301919091528051604480840193829003018186803b158015612fe957600080fd5b505afa158015612ffd573d6000803e3d6000fd5b505050506040513d604081101561301357600080fd5b5080516020909101519092509050818511156130605760405162461bcd60e51b8152600401808060200182810382526026815260200180613e736026913960400191505060405180910390fd5b80156130b3576040805162461bcd60e51b815260206004820152601e60248201527f412073796e7468206f7220534e58207261746520697320696e76616c69640000604482015290519081900360640190fd5b50600195945050505050565b6004805460408051636eb1769f60e11b81526001600160a01b0387811694820194909452878416602482015290516000939092169163da46098c918791899161312c918891879163dd62ed3e91604480820192602092909190829003018186803b158015612ac557600080fd5b6040518463ffffffff1660e01b815260040180846001600160a01b03166001600160a01b03168152602001836001600160a01b03166001600160a01b031681526020018281526020019350505050600060405180830381600087803b15801561319457600080fd5b505af11580156131a8573d6000803e3d6000fd5b5050505061177e848484613afb565b6131bf613ac5565b6001600160a01b0316637c3125416040518163ffffffff1660e01b815260040160006040518083038186803b158015612e7757600080fd5b6131ff612e20565b6001600160a01b0316336001600160a01b0316146122f3576040805162461bcd60e51b815260206004820152601e60248201527f4f6e6c792045786368616e6765722063616e20696e766f6b6520746869730000604482015290519081900360640190fd5b61326c613ac5565b6001600160a01b0316631ce00ba283836040518363ffffffff1660e01b8152600401808381526020018281526020019250505060006040518083038186803b1580156132b757600080fd5b505afa15801561261a573d6000803e3d6000fd5b6132d3613d22565b6001600160a01b0316336001600160a01b0316146122f3576040805162461bcd60e51b815260206004820152601d60248201527f43616e206f6e6c7920626520696e766f6b656420627920627269646765000000604482015290519081900360640190fd5b60048054604080516370a0823160e01b81526001600160a01b03868116948201949094529051929091169163b46310f69185916133d091869186916370a08231916024808301926020929190829003018186803b15801561339857600080fd5b505afa1580156133ac573d6000803e3d6000fd5b505050506040513d60208110156133c257600080fd5b50519063ffffffff613d4516565b6040518363ffffffff1660e01b815260040180836001600160a01b03166001600160a01b0316815260200182815260200192505050600060405180830381600087803b15801561341f57600080fd5b505af1158015613433573d6000803e3d6000fd5b50505050613442308383613995565b600754612b83908263ffffffff613d4516565b6001600160a01b031690565b60408051600580825260c082019092526060916020820160a0803883390190505090506d53796e746865746978537461746560901b816000815181106134a357fe5b6020026020010181815250506b53797374656d53746174757360a01b816001815181106134cc57fe5b6020026020010181815250506822bc31b430b733b2b960b91b816002815181106134f257fe5b6020026020010181815250506524b9b9bab2b960d11b8160038151811061351557fe5b602002602001018181525050722932bbb0b93239a234b9ba3934b13aba34b7b760691b8160048151811061354557fe5b60200260200101818152505090565b60608151835101604051908082528060200260200182016040528015613584578160200160208202803883390190505b50905060005b83518110156135c65783818151811061359f57fe5b60200260200101518282815181106135b357fe5b602090810291909101015260010161358a565b5060005b8251811015613609578281815181106135df57fe5b60200260200101518282865101815181106135f657fe5b60209081029190910101526001016135ca565b5092915050565b6002546001600160a01b0316331480159061363657506003546001600160a01b03163314155b1561364e57600380546001600160a01b031916331790555b6000546003546001600160a01b039081169116146122f3576040805162461bcd60e51b815260206004820152601360248201527227bbb732b91037b7363c90333ab731ba34b7b760691b604482015290519081900360640190fd5b600254604080516001600160a01b038481166020808401919091528351808403820181528385018086527f546f6b656e5374617465557064617465642861646472657373290000000000009052935192839003605a01832063907dff9760e01b8452600160248501819052604485018290526000606486018190526084860181905260a4860181905260c060048701908152875160c48801528751959098169763907dff97979692959394919384938493839260e490920191908a0190808383885b8381101561378357818101518382015260200161376b565b50505050905090810190601f1680156137b05780820380516001836020036101000a031916815260200191505b50975050505050505050600060405180830381600087803b1580156111f257600080fd5b600061117c848484613afb565b6002546001600160a01b031633146122f3576040805162461bcd60e51b815260206004820152601760248201527f4f6e6c79207468652070726f78792063616e2063616c6c000000000000000000604482015290519081900360640190fd5b6000612c7c722932bbb0b93239a234b9ba3934b13aba34b7b760691b6139e8565b6002805460408051602081018790528082018690526001600160a01b03858116606080840191909152835180840390910181526080909201928390529092169263907dff979291806032613f108239603201905060405180910390206138c689613455565b6000806040518763ffffffff1660e01b815260040180806020018781526020018681526020018581526020018460001b81526020018360001b81526020018281038252888181518152602001915080519060200190808383600083811015611481578181015183820152602001611469565b60008282111561398f576040805162461bcd60e51b815260206004820152601e60248201527f536166654d6174683a207375627472616374696f6e206f766572666c6f770000604482015290519081900360640190fd5b50900390565b60025460408051602080820185905282518083039091018152908201918290526001600160a01b039092169163907dff9791600390806021613f42823960210190506040518091039020612d1388613455565b600081815260096020908152604080832054815170026b4b9b9b4b7339030b2323932b9b99d1607d1b9381019390935260318084018690528251808503909101815260519093019091526001600160a01b031690816136095760405162461bcd60e51b81526004018080602001828103825283818151815260200191508051906020019080838360005b83811015613a8a578181015183820152602001613a72565b50505050905090810190601f168015613ab75780820380516001836020036101000a031916815260200191505b509250505060405180910390fd5b6000612c7c6b53797374656d53746174757360a01b6139e8565b6000612c7c6d53796e746865746978537461746560901b6139e8565b60006001600160a01b03831615801590613b1e57506001600160a01b0383163014155b8015613b3857506002546001600160a01b03848116911614155b613b89576040805162461bcd60e51b815260206004820152601f60248201527f43616e6e6f74207472616e7366657220746f2074686973206164647265737300604482015290519081900360640190fd5b60048054604080516370a0823160e01b81526001600160a01b03888116948201949094529051929091169163b46310f6918791613be991879186916370a08231916024808301926020929190829003018186803b158015612ac557600080fd5b6040518363ffffffff1660e01b815260040180836001600160a01b03166001600160a01b0316815260200182815260200192505050600060405180830381600087803b158015613c3857600080fd5b505af1158015613c4c573d6000803e3d6000fd5b505060048054604080516370a0823160e01b81526001600160a01b0389811694820194909452905192909116935063b46310f692508691613cb091879186916370a08231916024808301926020929190829003018186803b15801561339857600080fd5b6040518363ffffffff1660e01b815260040180836001600160a01b03166001600160a01b0316815260200182815260200192505050600060405180830381600087803b158015613cff57600080fd5b505af1158015613d13573d6000803e3d6000fd5b50505050610ee9848484613995565b6000612c7c7453796e746865746978427269646765546f4261736560581b6139e8565b600082820183811015613d9f576040805162461bcd60e51b815260206004820152601b60248201527f536166654d6174683a206164646974696f6e206f766572666c6f770000000000604482015290519081900360640190fd5b939250505056fe596f75206d757374206265206e6f6d696e61746564206265666f726520796f752063616e20616363657074206f776e65727368697045786368616e67655265636c61696d28616464726573732c627974657333322c75696e743235362945786368616e6765547261636b696e6728627974657333322c627974657333322c75696e743235362c75696e743235362953796e746845786368616e676528616464726573732c627974657333322c75696e743235362c627974657333322c75696e743235362c616464726573732943616e6e6f74207472616e73666572207374616b6564206f7220657363726f77656420534e584f6e6c792074686520636f6e7472616374206f776e6572206d617920706572666f726d207468697320616374696f6e417070726f76616c28616464726573732c616464726573732c75696e743235362945786368616e676552656261746528616464726573732c627974657333322c75696e74323536294163636f756e744c69717569646174656428616464726573732c75696e743235362c75696e743235362c61646472657373295472616e7366657228616464726573732c616464726573732c75696e7432353629a265627a7a7231582046b95ecf0aafa1112c0aa66ee1e9d26dc5fd4e44c0171b7a85c17e188b20e23964736f6c63430005100032", "abi": [ { "inputs": [ @@ -14326,6 +14335,37 @@ "stateMutability": "nonpayable", "type": "constructor" }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "account", + "type": "address" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "snxRedeemed", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "amountLiquidated", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "address", + "name": "liquidator", + "type": "address" + } + ], + "name": "AccountLiquidated", + "type": "event" + }, { "anonymous": false, "inputs": [ @@ -15083,6 +15123,42 @@ "stateMutability": "nonpayable", "type": "function" }, + { + "constant": false, + "inputs": [ + { + "internalType": "bytes32", + "name": "", + "type": "bytes32" + }, + { + "internalType": "uint256", + "name": "", + "type": "uint256" + }, + { + "internalType": "bytes32", + "name": "", + "type": "bytes32" + }, + { + "internalType": "bytes32", + "name": "", + "type": "bytes32" + } + ], + "name": "exchangeAtomically", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "payable": false, + "stateMutability": "nonpayable", + "type": "function" + }, { "constant": false, "inputs": [ @@ -15239,7 +15315,7 @@ "outputs": [ { "internalType": "uint256", - "name": "amountReceived", + "name": "", "type": "uint256" } ], @@ -15288,21 +15364,6 @@ "stateMutability": "nonpayable", "type": "function" }, - { - "constant": true, - "inputs": [], - "name": "integrationProxy", - "outputs": [ - { - "internalType": "contract Proxy", - "name": "", - "type": "address" - } - ], - "payable": false, - "stateMutability": "view", - "type": "function" - }, { "constant": true, "inputs": [], @@ -15403,12 +15464,12 @@ "inputs": [ { "internalType": "address", - "name": "", + "name": "account", "type": "address" }, { "internalType": "uint256", - "name": "", + "name": "susdAmount", "type": "uint256" } ], @@ -15670,21 +15731,6 @@ "stateMutability": "view", "type": "function" }, - { - "constant": false, - "inputs": [ - { - "internalType": "address payable", - "name": "_integrationProxy", - "type": "address" - } - ], - "name": "setIntegrationProxy", - "outputs": [], - "payable": false, - "stateMutability": "nonpayable", - "type": "function" - }, { "constant": false, "inputs": [ @@ -15970,15 +16016,15 @@ } ], "source": { - "keccak256": "0xbbe79669c2586e5f0a7f35a0577d21b881e0e083b9fc297fa16d6c1de064c749", + "keccak256": "0x87c92d55af4f4b01932ec25770439770836ed60b9652a3f4ab1ec6e0d82d302a", "urls": [ - "bzz-raw://45f229865baabb02bd1d2fc70e2689e8eddb335483c56fbc8a40aa839725831c", - "dweb:/ipfs/QmZ7hK15fu8JkDQRbHzVCHgyKLL6P7VJeuGEaUEY7ZHpom" + "bzz-raw://8eeb6e102aeb9e33d5bc3feafa27ced0cbf46166b8642b55711ae8088cbef77d", + "dweb:/ipfs/QmWnq7ZBagWFnHz4o82gyM193MZqeD5bqNiZxtd8R23N5D" ] }, "metadata": { "compiler": { - "version": "0.5.16-develop.2020.12.10+ovm+commit.25adf37d" + "version": "0.5.16+commit.9c3226ce" }, "language": "Solidity", "settings": { @@ -15995,10 +16041,10 @@ }, "sources": { "MintableSynthetix.sol": { - "keccak256": "0xbbe79669c2586e5f0a7f35a0577d21b881e0e083b9fc297fa16d6c1de064c749", + "keccak256": "0x87c92d55af4f4b01932ec25770439770836ed60b9652a3f4ab1ec6e0d82d302a", "urls": [ - "bzz-raw://45f229865baabb02bd1d2fc70e2689e8eddb335483c56fbc8a40aa839725831c", - "dweb:/ipfs/QmZ7hK15fu8JkDQRbHzVCHgyKLL6P7VJeuGEaUEY7ZHpom" + "bzz-raw://8eeb6e102aeb9e33d5bc3feafa27ced0cbf46166b8642b55711ae8088cbef77d", + "dweb:/ipfs/QmWnq7ZBagWFnHz4o82gyM193MZqeD5bqNiZxtd8R23N5D" ] } }, @@ -29712,6 +29758,1207 @@ }, "version": 1 } + }, + "CollateralEth": { + "bytecode": "6080604052600d805460ff191660011790553480156200001e57600080fd5b506040516200476938038062004769833981016040819052620000419162000150565b8585858585858380876001600160a01b0381166200007c5760405162461bcd60e51b8152600401620000739062000261565b60405180910390fd5b600080546001600160a01b0319166001600160a01b0383161781556040517fb532073b38c83145e3e5135377a08bf9aab55bc0fd7c1179cd4fb995d2a5159c91620000c99184906200023b565b60405180910390a150600280546001600160a01b039283166001600160a01b0319918216179091556006805498909216971696909617909555600492909255600a55600b5550506001600e5550620002e295505050505050565b80516200013081620002b2565b92915050565b80516200013081620002cc565b80516200013081620002d7565b60008060008060008060c087890312156200016a57600080fd5b600062000178898962000123565b96505060206200018b89828a0162000143565b95505060406200019e89828a0162000123565b9450506060620001b189828a0162000136565b9350506080620001c489828a0162000136565b92505060a0620001d789828a0162000136565b9150509295509295509295565b620001ef81620002a5565b82525050565b620001ef816200027c565b60006200020f60198362000273565b7f4f776e657220616464726573732063616e6e6f74206265203000000000000000815260200192915050565b604081016200024b8285620001e4565b6200025a6020830184620001f5565b9392505050565b60208082528101620001308162000200565b90815260200190565b6000620001308262000299565b90565b600062000130826200027c565b6001600160a01b031690565b600062000130826200028c565b620002bd816200027c565b8114620002c957600080fd5b50565b620002bd8162000289565b620002bd816200028c565b61447780620002f26000396000f3fe60806040526004361061020f5760003560e01c806372e18b6a11610118578063925ead11116100a0578063ba2de9bc1161006f578063ba2de9bc146105b0578063d2b8035a146105c5578063de81eda9146105e5578063e1ec3c6814610605578063f3f437031461063a5761020f565b8063925ead1114610546578063a76cdfa51461055b578063aa2d8ce31461057b578063b562a1ab1461059b5761020f565b8063846321a4116100e7578063846321a4146104af578063899ffef4146104cf5780638cd2e0c7146104f15780638da5cb5b1461051157806390abb4d9146105265761020f565b806372e18b6a14610445578063741853601461046557806379ba50971461047a5780637e1323551461048f5761020f565b8063379607f51161019b578063441a3e701161016a578063441a3e70146103bb57806347e7ef24146103db578063481c6a75146103ee57806353a47bb7146104035780635eb2ad01146104255761020f565b8063379607f51461034657806338245377146103665780634065b81b1461038657806341c738011461039b5761020f565b80631627540c116101e25780631627540c146102af57806323d60e2e146102cf5780632af64bd3146102ef57806330edd96114610311578063361e2086146103315761020f565b806304f3bcec1461021457806306c19e3f1461023f5780630710285c1461025f5780630aebeb4e14610281575b600080fd5b34801561022057600080fd5b5061022961065a565b6040516102369190614009565b60405180910390f35b61025261024d3660046136c0565b610669565b6040516102369190613f97565b34801561026b57600080fd5b5061027f61027a36600461355b565b61067f565b005b34801561028d57600080fd5b506102a161029c366004613684565b6106c5565b604051610236929190613fb3565b3480156102bb57600080fd5b5061027f6102ca3660046134e5565b61070e565b3480156102db57600080fd5b5061027f6102ea3660046135a8565b61076c565b3480156102fb57600080fd5b5061030461083b565b6040516102369190613f89565b34801561031d57600080fd5b5061025261032c366004613684565b610953565b34801561033d57600080fd5b50610252610971565b34801561035257600080fd5b5061027f610361366004613684565b610977565b34801561037257600080fd5b50610252610381366004613684565b610a4c565b34801561039257600080fd5b50610304610a5e565b3480156103a757600080fd5b506102526103b6366004613684565b610a67565b3480156103c757600080fd5b506102a16103d63660046136c0565b610b7f565b6102a16103e9366004613521565b610bca565b3480156103fa57600080fd5b50610229610be3565b34801561040f57600080fd5b50610418610bf2565b6040516102369190613f26565b34801561043157600080fd5b5061027f610440366004613521565b610c01565b34801561045157600080fd5b506103046104603660046135a8565b610c37565b34801561047157600080fd5b5061027f610cfb565b34801561048657600080fd5b5061027f610e4d565b34801561049b57600080fd5b506102526104aa3660046136c0565b610ee9565b3480156104bb57600080fd5b5061027f6104ca366004613684565b610f2e565b3480156104db57600080fd5b506104e4610f6b565b6040516102369190613f78565b3480156104fd57600080fd5b506102a161050c36600461355b565b6110fb565b34801561051d57600080fd5b50610418611116565b34801561053257600080fd5b5061027f610541366004613618565b611125565b34801561055257600080fd5b50610252611171565b34801561056757600080fd5b5061027f610576366004613684565b611177565b34801561058757600080fd5b50610252610596366004613684565b6111b4565b3480156105a757600080fd5b50610252611278565b3480156105bc57600080fd5b5061025261127e565b3480156105d157600080fd5b506102a16105e03660046136c0565b611284565b3480156105f157600080fd5b50610418610600366004613684565b611291565b34801561061157600080fd5b50610625610620366004613684565b6112ac565b604051610236999897969594939291906141ee565b34801561064657600080fd5b506102526106553660046134e5565b611306565b6002546001600160a01b031681565b60006106783484846000611318565b9392505050565b600061068c8484846119c0565b336000908152600f60205260409020549091506106af908263ffffffff611d2716565b336000908152600f602052604090205550505050565b6000806106d23384611d4c565b336000908152600f602052604090205491935091506106f7908263ffffffff611d2716565b336000908152600f60205260409020559092909150565b610716611e42565b600180546001600160a01b0319166001600160a01b0383161790556040517f906a1c6bd7e3091ea86693dd029a831c19049ce77f1dce2ce0bab1cacbabce2290610761908390613f26565b60405180910390a150565b610774611e42565b82811461079c5760405162461bcd60e51b815260040161079390614048565b60405180910390fd5b60005b8381101561082c5760008585838181106107b557fe5b600780546001810182556000918252602090920293909301357fa66cc928b5edb82af9bd49922954155ab7b0942694bea4ce44661d9a8736c688909101819055925082916008915086868681811061080957fe5b60209081029290920135835250810191909152604001600020555060010161079f565b50610835610cfb565b50505050565b60006060610847610f6b565b905060005b815181101561094957600082828151811061086357fe5b602090810291909101810151600081815260039092526040918290205460025492516321f8a72160e01b81529193506001600160a01b039081169216906321f8a721906108b4908590600401613f97565b60206040518083038186803b1580156108cc57600080fd5b505afa1580156108e0573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052506109049190810190613503565b6001600160a01b031614158061092f57506000818152600360205260409020546001600160a01b0316155b156109405760009350505050610950565b5060010161084c565b5060019150505b90565b6007818154811061096057fe5b600091825260209091200154905081565b600c5481565b600e805460010190819055336000908152600f60205260409020546109a2908363ffffffff611e6e16565b336000818152600f602052604080822093909355915184906109c390613f1b565b60006040518083038185875af1925050503d8060008114610a00576040519150601f19603f3d011682016040523d82523d6000602084013e610a05565b606091505b5050905080610a265760405162461bcd60e51b815260040161079390614058565b50600e548114610a485760405162461bcd60e51b815260040161079390614168565b5050565b60086020526000908152604090205481565b600d5460ff1681565b6000610a71613407565b506000828152600560208181526040928390208351610120810185528154815260018201546001600160a01b03169281019290925260028101549382019390935260038301546060820152600483015460808201529082015460ff16151560a0820152600682015460c0820152600782015460e0820152600890910154610100820152610afc611e96565b6001600160a01b031663fbfeca4082600a546004546040518463ffffffff1660e01b8152600401610b2f939291906141a5565b60206040518083038186803b158015610b4757600080fd5b505afa158015610b5b573d6000803e3d6000fd5b505050506040513d601f19601f8201168201806040525061067891908101906136a2565b600080610b8c8484611eb7565b336000908152600f60205260409020549193509150610bb1908463ffffffff611d2716565b336000908152600f602052604090205590939092509050565b600080610bd8848434611f5c565b909590945092505050565b6006546001600160a01b031681565b6001546001600160a01b031681565b610c09611e42565b600090815260096020526040902080546001600160a01b0319166001600160a01b0392909216919091179055565b6007546000908414610c4b57506000610cf3565b60005b84811015610ced576000868683818110610c6457fe5b9050602002013590508060078381548110610c7b57fe5b906000526020600020015414610c9657600092505050610cf3565b60078281548110610ca357fe5b906000526020600020015460086000878786818110610cbe57fe5b9050602002013581526020019081526020016000205414610ce457600092505050610cf3565b50600101610c4e565b50600190505b949350505050565b6060610d05610f6b565b905060005b8151811015610a48576000828281518110610d2157fe5b602002602001015190506000600260009054906101000a90046001600160a01b03166001600160a01b031663dacb2d018384604051602001610d639190613f10565b6040516020818303038152906040526040518363ffffffff1660e01b8152600401610d8f929190613fc1565b60206040518083038186803b158015610da757600080fd5b505afa158015610dbb573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250610ddf9190810190613503565b6000838152600360205260409081902080546001600160a01b0319166001600160a01b038416179055519091507f88a93678a3692f6789d9546fc621bf7234b101ddb7d4fe479455112831b8aa6890610e3b9084908490613fa5565b60405180910390a15050600101610d0a565b6001546001600160a01b03163314610e775760405162461bcd60e51b815260040161079390614038565b6000546001546040517fb532073b38c83145e3e5135377a08bf9aab55bc0fd7c1179cd4fb995d2a5159c92610eba926001600160a01b0391821692911690613f4f565b60405180910390a160018054600080546001600160a01b03199081166001600160a01b03841617909155169055565b6000610ef3611e96565b6001600160a01b0316638a7399758484600a546004546040518563ffffffff1660e01b8152600401610b2f94939291906142da565b92915050565b610f36611e42565b600b8190556040517fd19fe8ad9152af12b174a60210fb798db0767d63973ebb97298dc44d67a5c82d90610761908390613f97565b606080610f7661202d565b60408051600680825260e08201909252919250606091906020820160c08038833901905050905066119959541bdbdb60ca1b81600081518110610fb557fe5b6020026020010181815250506c45786368616e6765526174657360981b81600181518110610fdf57fe5b6020026020010181815250506822bc31b430b733b2b960b91b8160028151811061100557fe5b6020026020010181815250506b53797374656d53746174757360a01b8160038151811061102e57fe5b6020026020010181815250506814de5b9d1a1cd554d160ba1b8160048151811061105457fe5b6020026020010181815250506d10dbdb1b185d195c985b155d1a5b60921b8160058151811061107f57fe5b6020026020010181815250506060611097838361207e565b90506110f38160078054806020026020016040519081016040528092919081815260200182805480156110e957602002820191906000526020600020905b8154815260200190600101908083116110d5575b505050505061207e565b935050505090565b60008061110a8533868661213a565b90969095509350505050565b6000546001600160a01b031681565b61112d611e42565b600d805460ff191682151517908190556040517f261991749e1b2436706a31bde8bf184bb37fe21e303709b78d3b881afacadaa2916107619160ff90911690613f89565b600a5481565b61117f611e42565b600c8190556040517fe7bd72551c54d568cd97b00dc52d2787b5c5d4f0070d3582c1e8ba25141f799c90610761908390613f97565b60006111be613407565b506000828152600560208181526040928390208351610120810185528154815260018201546001600160a01b03169281019290925260028101549382019390935260038301546060820152600483015460808201529082015460ff16151560a0820152600682015460c0820152600782015460e0820152600890910154610100820152611249611e96565b6001600160a01b031663e99f9647826004546040518363ffffffff1660e01b8152600401610b2f929190614188565b60045481565b600b5481565b600080610bd884846122e9565b6009602052600090815260409020546001600160a01b031681565b600560208190526000918252604090912080546001820154600283015460038401546004850154958501546006860154600787015460089097015495976001600160a01b0390951696939592949360ff9092169290919089565b600f6020526000908152604090205481565b600061132261272b565b61132a6127cd565b600d5460ff1661134c5760405162461bcd60e51b815260040161079390614118565b6000838152600860205260409020546113775760405162461bcd60e51b815260040161079390614068565b61137f612821565b6001600160a01b0316632528f0fe846040518263ffffffff1660e01b81526004016113aa9190613f97565b60206040518083038186803b1580156113c257600080fd5b505afa1580156113d6573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052506113fa9190810190613636565b156114175760405162461bcd60e51b8152600401610793906140f8565b600b548510156114395760405162461bcd60e51b815260040161079390614148565b6006546040516302d35b2d60e61b815260009182916001600160a01b039091169063b4d6cb40906114709089908990600401613fb3565b604080518083038186803b15801561148757600080fd5b505afa15801561149b573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052506114bf9190810190613654565b915091508180156114ce575080155b6114ea5760405162461bcd60e51b8152600401610793906140d8565b6114f48786610ee9565b8611156115135760405162461bcd60e51b815260040161079390614138565b600061152a600c548861283c90919063ffffffff16565b9050600061153e888363ffffffff611e6e16565b9050600660009054906101000a90046001600160a01b03166001600160a01b031663b3b467326040518163ffffffff1660e01b8152600401602060405180830381600087803b15801561159057600080fd5b505af11580156115a4573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052506115c891908101906136a2565b60408051610120810182528281523360208083019182528284018e8152606084018d8152608085018f81528d151560a08701908152600060c0880181815260e08901828152426101008b019081528c84526005988990529a9092209851895596516001890180546001600160a01b0319166001600160a01b03909216919091179055935160028801559151600387015551600486015551918401805460ff191692151592909217909155905160068301555160078201559051600882015590955061169290612851565b61169c8288612943565b851561188f576116aa612b2f565b6001600160a01b031663867904b4336116c1612821565b6001600160a01b031663654a60ac8b86631cd554d160e21b6040518463ffffffff1660e01b81526004016116f793929190613fe1565b60206040518083038186803b15801561170f57600080fd5b505afa158015611723573d6000803e3d6000fd5b505050506040513d601f19601f8201168201806040525061174791908101906136a2565b6040518363ffffffff1660e01b8152600401611764929190613f34565b600060405180830381600087803b15801561177e57600080fd5b505af1158015611792573d6000803e3d6000fd5b505060065460405163e31f27c160e01b81526001600160a01b03909116925063e31f27c191506117c8908a908c90600401613fb3565b600060405180830381600087803b1580156117e257600080fd5b505af11580156117f6573d6000803e3d6000fd5b5050506000888152600960205260409020546001600160a01b031615905061188a576000878152600960205260409081902054905163db454a5160e01b81526001600160a01b039091169063db454a51906118579033908c90600401613f34565b600060405180830381600087803b15801561187157600080fd5b505af1158015611885573d6000803e3d6000fd5b505050505b61196b565b6000878152600860205260409020546118a790612b46565b6001600160a01b031663867904b433836040518363ffffffff1660e01b81526004016118d4929190613f34565b600060405180830381600087803b1580156118ee57600080fd5b505af1158015611902573d6000803e3d6000fd5b50506006546040516375ca5def60e11b81526001600160a01b03909116925063eb94bbde9150611938908a908c90600401613fb3565b600060405180830381600087803b15801561195257600080fd5b505af1158015611966573d6000803e3d6000fd5b505050505b336001600160a01b03167f604952b18be5fed608cbdd28101dc57bd667055c9678ec6d44fb1d8e4c7c172a868a8c8b876040516119ac9594939291906142f5565b60405180910390a250505050949350505050565b60006119ca61272b565b6119d26127cd565b600082116119f25760405162461bcd60e51b815260040161079390614178565b60006119fe8486612b51565b9050611a0f33826003015485612ba5565b600a54611a1a611e96565b6001600160a01b031663e99f9647836004546040518363ffffffff1660e01b8152600401611a499291906141d0565b60206040518083038186803b158015611a6157600080fd5b505afa158015611a75573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250611a9991908101906136a2565b10611ab65760405162461bcd60e51b815260040161079390614158565b6000611ac0611e96565b6001600160a01b031663fbfeca4083600a546004546040518463ffffffff1660e01b8152600401611af3939291906141df565b60206040518083038186803b158015611b0b57600080fd5b505afa158015611b1f573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250611b4391908101906136a2565b90506000848210611b545784611b56565b815b90506000611b7584600601548560040154611d2790919063ffffffff16565b9050808210611b9657611b89883386612c5d565b9550610678945050505050565b611ba533856003015484612ba5565b611baf8483612cce565b611bb7611e96565b6001600160a01b0316633c4aa0f38560030154846004546040518463ffffffff1660e01b8152600401611bec93929190613fe1565b60206040518083038186803b158015611c0457600080fd5b505afa158015611c18573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250611c3c91908101906136a2565b6002850154909550611c54908663ffffffff611e6e16565b60028501556003840154600090815260086020526040902054611c7690612b46565b6001600160a01b0316639dc29fac33846040518363ffffffff1660e01b8152600401611ca3929190613f34565b600060405180830381600087803b158015611cbd57600080fd5b505af1158015611cd1573d6000803e3d6000fd5b50505050876001600160a01b03167fb6e43890aeea54fbe6c0ed628e78172a0ff30bbcb1d70d8b130b12c366bac4c588338589604051611d149493929190614274565b60405180910390a2505050509392505050565b6000828201838110156106785760405162461bcd60e51b815260040161079390614078565b600080611d5761272b565b611d5f6127cd565b6000611d6b8486612b51565b60408051610120810182528254815260018301546001600160a01b031660208201526002830154918101919091526003820154606082015260048201546080820152600582015460ff16151560a0820152600682015460c0820152600782015460e08201526008820154610100820152909150611de790612ebd565b611df2858683612f0a565b60405191945092506001600160a01b038616907fcab22a4e95d29d40da2ace3f6ec72b49954a9bc7b2584f8fd47bf7f357a3ed6f90611e32908790613f97565b60405180910390a2509250929050565b6000546001600160a01b03163314611e6c5760405162461bcd60e51b8152600401610793906140b8565b565b600082821115611e905760405162461bcd60e51b815260040161079390614098565b50900390565b6000611eb26d10dbdb1b185d195c985b155d1a5b60921b613170565b905090565b600080611ec261272b565b611eca6127cd565b6000611ed68533612b51565b6002810154909150611eee908563ffffffff611e6e16565b6002820155611efc816131cd565b336001600160a01b03167ffae26280bca25d80f1501a9e363c73d3845e651c9aaae54f1fc09a9dcd5f330386868460020154604051611f3d93929190613fe1565b60405180910390a28060040154816002015492509250505b9250929050565b600080611f6761272b565b611f6f6127cd565b60008311611f8f5760405162461bcd60e51b815260040161079390614128565b60008481526005602052604090206007810154611fab90613206565b611fb481612851565b6002810154611fc9908563ffffffff611d2716565b600282018190556040516001600160a01b038816917f0b1992dffc262be88559dcaf96464e9d661d8bfca7e82f2bb73e31932a82187c9161200e918991899190613fe1565b60405180910390a2806004015481600201549250925050935093915050565b604080516001808252818301909252606091602080830190803883390190505090506e466c657869626c6553746f7261676560881b8160008151811061206f57fe5b60200260200101818152505090565b606081518351016040519080825280602002602001820160405280156120ae578160200160208202803883390190505b50905060005b83518110156120f0578381815181106120c957fe5b60200260200101518282815181106120dd57fe5b60209081029190910101526001016120b4565b5060005b82518110156121335782818151811061210957fe5b602002602001015182828651018151811061212057fe5b60209081029190910101526001016120f4565b5092915050565b60008061214561272b565b61214d6127cd565b6000848152600560208181526040928390208351610120810185528154815260018201546001600160a01b03169281019290925260028101549382019390935260038301546060820152600483015460808201529082015460ff16151560a0820152600682015460c0820152600782015460e082015260088201546101008201526121d790612ebd565b6121e686826003015486612ba5565b6121ef81612851565b6121f98185612cce565b600381015460009081526008602052604090205461221690612b46565b6001600160a01b0316639dc29fac87866040518363ffffffff1660e01b8152600401612243929190613f6a565b600060405180830381600087803b15801561225d57600080fd5b505af1158015612271573d6000803e3d6000fd5b50505050428160080181905550856001600160a01b0316876001600160a01b03167fdf10512219e869922340b1b24b21d7d79bf71f411a6391cc7c3ef5dd2fe89e7f878785600401546040516122c993929190613fe1565b60405180910390a380600401548160020154925092505094509492505050565b6000806122f461272b565b6122fc6127cd565b60006123088533612b51565b60408051610120810182528254815260018301546001600160a01b031660208201526002830154918101919091526003820154606082015260048201546080820152600582015460ff16151560a0820152600682015460c0820152600782015460e0820152600882015461010082015290915061238490612ebd565b6004810154612399908563ffffffff611d2716565b60048201556123a7816131cd565b60006123be600c548661283c90919063ffffffff16565b905060006123d2868363ffffffff611e6e16565b600584015490915060ff16156125df57600654600384015460405163e31f27c160e01b81526001600160a01b039092169163e31f27c191612417918a90600401613fb3565b600060405180830381600087803b15801561243157600080fd5b505af1158015612445573d6000803e3d6000fd5b50505050612451612b2f565b6001600160a01b031663867904b433612468612821565b6001600160a01b031663654a60ac876003015486631cd554d160e21b6040518463ffffffff1660e01b81526004016124a293929190613fe1565b60206040518083038186803b1580156124ba57600080fd5b505afa1580156124ce573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052506124f291908101906136a2565b6040518363ffffffff1660e01b815260040161250f929190613f34565b600060405180830381600087803b15801561252957600080fd5b505af115801561253d573d6000803e3d6000fd5b5050505060038301546000908152600960205260409020546001600160a01b0316156125da5760038301546000908152600960205260409081902054905163db454a5160e01b81526001600160a01b039091169063db454a51906125a79033908a90600401613f34565b600060405180830381600087803b1580156125c157600080fd5b505af11580156125d5573d6000803e3d6000fd5b505050505b6126c3565b60065460038401546040516375ca5def60e11b81526001600160a01b039092169163eb94bbde91612614918a90600401613fb3565b600060405180830381600087803b15801561262e57600080fd5b505af1158015612642573d6000803e3d6000fd5b50505060038401546000908152600860205260409020546126639150612b46565b6001600160a01b031663867904b433836040518363ffffffff1660e01b8152600401612690929190613f34565b600060405180830381600087803b1580156126aa57600080fd5b505af11580156126be573d6000803e3d6000fd5b505050505b6126d1828460030154612943565b42600884015560405133907f5754fe57f36ac0f121901d7555aba517e6608590429d86a81c662cf3583106549061270b908a908a90613fb3565b60405180910390a282600401548360020154945094505050509250929050565b612733612821565b6001600160a01b0316632528f0fe6004546040518263ffffffff1660e01b81526004016127609190613f97565b60206040518083038186803b15801561277857600080fd5b505afa15801561278c573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052506127b09190810190613636565b15611e6c5760405162461bcd60e51b8152600401610793906140f8565b6127d5613223565b6001600160a01b0316637c3125416040518163ffffffff1660e01b815260040160006040518083038186803b15801561280d57600080fd5b505afa158015610835573d6000803e3d6000fd5b6000611eb26c45786368616e6765526174657360981b613170565b60006106788383670de0b6b3a764000061323d565b600654600782015460038301546005840154604051634002a33360e11b815260009485946001600160a01b03909116936380054666936128999360ff909116906004016142b2565b6040805180830381600087803b1580156128b257600080fd5b505af11580156128c6573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052506128ea91908101906136df565b9150915060008360070154600014612916576004840154612911908463ffffffff61327916565b612919565b60005b6006850154909150612931908263ffffffff611d2716565b60068501555060079092019190915550565b8115610a4857631cd554d160e21b81146129e85761295f612821565b6001600160a01b031663654a60ac8284631cd554d160e21b6040518463ffffffff1660e01b815260040161299593929190613fe1565b60206040518083038186803b1580156129ad57600080fd5b505afa1580156129c1573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052506129e591908101906136a2565b91505b6129f0612b2f565b6001600160a01b031663867904b4612a066132a3565b6001600160a01b031663eb1edd616040518163ffffffff1660e01b815260040160206040518083038186803b158015612a3e57600080fd5b505afa158015612a52573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250612a769190810190613503565b846040518363ffffffff1660e01b8152600401612a94929190613f6a565b600060405180830381600087803b158015612aae57600080fd5b505af1158015612ac2573d6000803e3d6000fd5b50505050612ace6132a3565b6001600160a01b03166322bf55ef836040518263ffffffff1660e01b8152600401612af99190613f97565b600060405180830381600087803b158015612b1357600080fd5b505af1158015612b27573d6000803e3d6000fd5b505050505050565b6000611eb26814de5b9d1a1cd554d160ba1b613170565b6000610f2882613170565b60008281526005602052604090206007810154612b6d90613206565b60018101546001600160a01b03838116911614612b9c5760405162461bcd60e51b815260040161079390614108565b610f2881612851565b6000828152600860205260409020548190612bbf90612b46565b6001600160a01b03166370a08231856040518263ffffffff1660e01b8152600401612bea9190613f26565b60206040518083038186803b158015612c0257600080fd5b505afa158015612c16573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250612c3a91908101906136a2565b1015612c585760405162461bcd60e51b8152600401610793906140a8565b505050565b600080612c6b858585612f0a565b8092508193505050836001600160a01b0316856001600160a01b03167f697721ed1b9d4866cb1aaa0692f62bb3abc1b01c2dafeaad053ffd4532aa7dbb85600001548585604051612cbe93929190613fe1565b60405180910390a3935093915050565b60008111612cee5760405162461bcd60e51b815260040161079390614178565b600682015415612d5057600082600601548211612d0b5781612d11565b82600601545b6006840154909150612d29908263ffffffff611e6e16565b6006840155612d3e828263ffffffff611e6e16565b9150612d4e818460030154612943565b505b8015610a48576004820154612d6b908263ffffffff611e6e16565b6004830155600582015460ff1615612e88576006546003830154604051635246f2b960e01b81526001600160a01b0390921691635246f2b991612db2918590600401613fb3565b600060405180830381600087803b158015612dcc57600080fd5b505af1158015612de0573d6000803e3d6000fd5b5050505060038201546000908152600960205260409020546001600160a01b031615612e8357600382015460009081526009602052604090819020546001840154915163f3fef3a360e01b81526001600160a01b039182169263f3fef3a392612e50929116908590600401613f34565b600060405180830381600087803b158015612e6a57600080fd5b505af1158015612e7e573d6000803e3d6000fd5b505050505b610a48565b600654600383015460405163e50a31b360e01b81526001600160a01b039092169163e50a31b391612af9918590600401613fb3565b612eca8160e00151613206565b42612ee9612ed7306132b8565b6101008401519063ffffffff611d2716565b1115612f075760405162461bcd60e51b8152600401610793906140e8565b50565b6000806000612f2a84600601548560040154611d2790919063ffffffff16565b90508360040154925083600201549150612f4985856003015483612ba5565b6003840154600090815260086020526040902054612f6690612b46565b6001600160a01b0316639dc29fac86836040518363ffffffff1660e01b8152600401612f93929190613f6a565b600060405180830381600087803b158015612fad57600080fd5b505af1158015612fc1573d6000803e3d6000fd5b50505050600584015460ff16156130df576006546003850154600480870154604051635246f2b960e01b81526001600160a01b0390941693635246f2b99361300c9390929101613fb3565b600060405180830381600087803b15801561302657600080fd5b505af115801561303a573d6000803e3d6000fd5b5050505060038401546000908152600960205260409020546001600160a01b0316156130da5760038401546000908152600960205260409081902054600480870154925163f3fef3a360e01b81526001600160a01b039092169263f3fef3a3926130a7928b929101613f6a565b600060405180830381600087803b1580156130c157600080fd5b505af11580156130d5573d6000803e3d6000fd5b505050505b61314c565b600654600385015460048087015460405163e50a31b360e01b81526001600160a01b039094169363e50a31b3936131199390929101613fb3565b600060405180830381600087803b15801561313357600080fd5b505af1158015613147573d6000803e3d6000fd5b505050505b61315e84600601548560030154612943565b6131678461338b565b50935093915050565b60008181526003602090815260408083205490516001600160a01b0390911691821515916131a091869101613ef0565b604051602081830303815290604052906121335760405162461bcd60e51b81526004016107939190614017565b60048101546131db57612f07565b600a5481546131e9906111b4565b11612f075760405162461bcd60e51b815260040161079390614028565b80612f075760405162461bcd60e51b815260040161079390614088565b6000611eb26b53797374656d53746174757360a01b613170565b600080600a8304613254868663ffffffff6133b016565b8161325b57fe5b0490506005600a82061061326d57600a015b600a9004949350505050565b6000670de0b6b3a7640000613294848463ffffffff6133b016565b8161329b57fe5b049392505050565b6000611eb266119959541bdbdb60ca1b613170565b60006132c26133ea565b6001600160a01b03166323257c2b6d53797374656d53657474696e677360901b6f696e746572616374696f6e44656c617960801b85604051602001613308929190613eca565b604051602081830303815290604052805190602001206040518363ffffffff1660e01b815260040161333b929190613fb3565b60206040518083038186803b15801561335357600080fd5b505afa158015613367573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250610f2891908101906136a2565b6000600482018190556002820181905560068201819055600782015542600890910155565b6000826133bf57506000610f28565b828202828482816133cc57fe5b04146106785760405162461bcd60e51b8152600401610793906140c8565b6000611eb26e466c657869626c6553746f7261676560881b613170565b6040518061012001604052806000815260200160006001600160a01b031681526020016000815260200160008019168152602001600081526020016000151581526020016000815260200160008152602001600081525090565b8035610f288161440e565b8051610f288161440e565b60008083601f84011261348957600080fd5b50813567ffffffffffffffff8111156134a157600080fd5b602083019150836020820283011115611f5557600080fd5b8035610f2881614422565b8051610f2881614422565b8035610f288161442b565b8051610f288161442b565b6000602082840312156134f757600080fd5b6000610cf38484613461565b60006020828403121561351557600080fd5b6000610cf3848461346c565b6000806040838503121561353457600080fd5b60006135408585613461565b9250506020613551858286016134cf565b9150509250929050565b60008060006060848603121561357057600080fd5b600061357c8686613461565b935050602061358d868287016134cf565b925050604061359e868287016134cf565b9150509250925092565b600080600080604085870312156135be57600080fd5b843567ffffffffffffffff8111156135d557600080fd5b6135e187828801613477565b9450945050602085013567ffffffffffffffff81111561360057600080fd5b61360c87828801613477565b95989497509550505050565b60006020828403121561362a57600080fd5b6000610cf384846134b9565b60006020828403121561364857600080fd5b6000610cf384846134c4565b6000806040838503121561366757600080fd5b600061367385856134c4565b9250506020613551858286016134c4565b60006020828403121561369657600080fd5b6000610cf384846134cf565b6000602082840312156136b457600080fd5b6000610cf384846134da565b600080604083850312156136d357600080fd5b600061354085856134cf565b600080604083850312156136f257600080fd5b60006136fe85856134da565b9250506020613551858286016134da565b600061371b83836137ae565b505060200190565b61372c8161437b565b82525050565b61372c8161436b565b61372c6137478261436b565b6143ed565b600061375782614347565b613761818561434b565b935061376c83614341565b8060005b8381101561379a578151613784888261370f565b975061378f83614341565b925050600101613770565b509495945050505050565b61372c81614376565b61372c81610950565b61372c6137c382610950565b610950565b61372c81614382565b60006137dc82614347565b6137e6818561434b565b93506137f681856020860161438d565b6137ff816143fe565b9093019392505050565b6000613816600e8361434b565b6d43726174696f20746f6f206c6f7760901b815260200192915050565b600061384060358361434b565b7f596f75206d757374206265206e6f6d696e61746564206265666f726520796f7581527402063616e20616363657074206f776e65727368697605c1b602082015260400192915050565b600061389760158361434b565b74082e4e4c2f240d8cadccee8d040dad2e6dac2e8c6d605b1b815260200192915050565b60006138c8600f8361434b565b6e151c985b9cd9995c8819985a5b1959608a1b815260200192915050565b60006138f360148361434b565b734e6f7420616c6c6f77656420746f20697373756560601b815260200192915050565b6000613923601b8361434b565b7f536166654d6174683a206164646974696f6e206f766572666c6f770000000000815260200192915050565b600061395c600e8361434b565b6d131bd85b881a5cc818db1bdcd95960921b815260200192915050565b6000613986601e8361434b565b7f536166654d6174683a207375627472616374696f6e206f766572666c6f770000815260200192915050565b60006139bf601183614354565b70026b4b9b9b4b7339030b2323932b9b99d1607d1b815260110192915050565b60006139ec60128361434b565b714e6f7420656e6f7567682062616c616e636560701b815260200192915050565b6000613a1a602f8361434b565b7f4f6e6c792074686520636f6e7472616374206f776e6572206d6179207065726681526e37b936903a3434b99030b1ba34b7b760891b602082015260400192915050565b6000613a6b60218361434b565b7f536166654d6174683a206d756c7469706c69636174696f6e206f766572666c6f8152607760f81b602082015260400192915050565b6000613aae601a8361434b565b7f44656274206c696d6974206f7220696e76616c69642072617465000000000000815260200192915050565b6000613ae760138361434b565b72149958d95b9d1b1e481a5b9d195c9858dd1959606a1b815260200192915050565b6000613b16601983614354565b7f5265736f6c766572206d697373696e67207461726765743a2000000000000000815260190192915050565b6000613b4f600c8361434b565b6b496e76616c6964207261746560a01b815260200192915050565b6000613b7760108361434b565b6f26bab9ba103132903137b93937bbb2b960811b815260200192915050565b6000610f28600083614354565b6000613bb0600d8361434b565b6c13dc195b88191a5cd8589b1959609a1b815260200192915050565b6000613bd960178361434b565b7f4465706f736974206d7573742062652061626f76652030000000000000000000815260200192915050565b6000613c1260178361434b565b7f457863656564206d617820626f72726f7720706f776572000000000000000000815260200192915050565b6000613c4b60158361434b565b74139bdd08195b9bdd59da0818dbdb1b185d195c985b605a1b815260200192915050565b6000613c7c60168361434b565b7543726174696f2061626f7665206c697120726174696f60501b815260200192915050565b6000613cae601f8361434b565b7f5265656e7472616e637947756172643a207265656e7472616e742063616c6c00815260200192915050565b6000613ce760178361434b565b7f5061796d656e74206d7573742062652061626f76652030000000000000000000815260200192915050565b8051610120830190613d2584826137ae565b506020820151613d386020850182613732565b506040820151613d4b60408501826137ae565b506060820151613d5e60608501826137ae565b506080820151613d7160808501826137ae565b5060a0820151613d8460a08501826137a5565b5060c0820151613d9760c08501826137ae565b5060e0820151613daa60e08501826137ae565b506101008201516108356101008501826137ae565b8054610120830190613dd0816143df565b613dda85826137ae565b50506001820154613dea816143b9565b613df76020860182613732565b50506002820154613e07816143df565b613e1460408601826137ae565b50506003820154613e24816143df565b613e3160608601826137ae565b50506004820154613e41816143df565b613e4e60808601826137ae565b50506005820154613e5e816143cc565b613e6b60a08601826137a5565b50506006820154613e7b816143df565b613e8860c08601826137ae565b50506007820154613e98816143df565b613ea560e08601826137ae565b50506008820154613eb5816143df565b613ec36101008601826137ae565b5050505050565b6000613ed682856137b7565b602082019150613ee6828461373b565b5060140192915050565b6000613efb826139b2565b9150613f0782846137b7565b50602001919050565b6000613efb82613b09565b6000610f2882613b96565b60208101610f288284613732565b60408101613f428285613723565b61067860208301846137ae565b60408101613f5d8285613732565b6106786020830184613732565b60408101613f428285613732565b60208082528101610678818461374c565b60208101610f2882846137a5565b60208101610f2882846137ae565b60408101613f5d82856137ae565b60408101613f4282856137ae565b60408101613fcf82856137ae565b8181036020830152610cf381846137d1565b60608101613fef82866137ae565b613ffc60208301856137ae565b610cf360408301846137ae565b60208101610f2882846137c8565b6020808252810161067881846137d1565b60208082528101610f2881613809565b60208082528101610f2881613833565b60208082528101610f288161388a565b60208082528101610f28816138bb565b60208082528101610f28816138e6565b60208082528101610f2881613916565b60208082528101610f288161394f565b60208082528101610f2881613979565b60208082528101610f28816139df565b60208082528101610f2881613a0d565b60208082528101610f2881613a5e565b60208082528101610f2881613aa1565b60208082528101610f2881613ada565b60208082528101610f2881613b42565b60208082528101610f2881613b6a565b60208082528101610f2881613ba3565b60208082528101610f2881613bcc565b60208082528101610f2881613c05565b60208082528101610f2881613c3e565b60208082528101610f2881613c6f565b60208082528101610f2881613ca1565b60208082528101610f2881613cda565b61014081016141978285613d13565b6106786101208301846137ae565b61016081016141b48286613d13565b6141c26101208301856137ae565b610cf36101408301846137ae565b61014081016141978285613dbf565b61016081016141b48286613dbf565b61012081016141fd828c6137ae565b61420a602083018b613732565b614217604083018a6137ae565b61422460608301896137ae565b61423160808301886137ae565b61423e60a08301876137a5565b61424b60c08301866137ae565b61425860e08301856137ae565b6142666101008301846137ae565b9a9950505050505050505050565b6080810161428282876137ae565b61428f6020830186613723565b61429c60408301856137ae565b6142a960608301846137ae565b95945050505050565b606081016142c082866137ae565b6142cd60208301856137ae565b610cf360408301846137a5565b608081016142e882876137ae565b61428f60208301866137ae565b60a0810161430382886137ae565b61431060208301876137ae565b61431d60408301866137ae565b61432a60608301856137ae565b61433760808301846137ae565b9695505050505050565b60200190565b5190565b90815260200190565b919050565b6001600160a01b031690565b60ff1690565b6000610f2882614359565b151590565b6000610f28825b6000610f288261436b565b60005b838110156143a8578181015183820152602001614390565b838111156108355750506000910152565b6000610f286143c783610950565b614359565b6000610f286143da83610950565b614365565b6000610f286137c383610950565b6000610f28826000610f2882614408565b601f01601f191690565b60601b90565b6144178161436b565b8114612f0757600080fd5b61441781614376565b6144178161095056fea365627a7a723158207ea733850eab679e1a0115a60d2318d891ec530c9063b5a96fa092a6360815b46c6578706572696d656e74616cf564736f6c63430005100040", + "abi": [ + { + "inputs": [ + { + "internalType": "address", + "name": "_owner", + "type": "address" + }, + { + "internalType": "contract ICollateralManager", + "name": "_manager", + "type": "address" + }, + { + "internalType": "address", + "name": "_resolver", + "type": "address" + }, + { + "internalType": "bytes32", + "name": "_collateralKey", + "type": "bytes32" + }, + { + "internalType": "uint256", + "name": "_minCratio", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "_minCollateral", + "type": "uint256" + } + ], + "payable": false, + "stateMutability": "nonpayable", + "type": "constructor" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "bytes32", + "name": "name", + "type": "bytes32" + }, + { + "indexed": false, + "internalType": "address", + "name": "destination", + "type": "address" + } + ], + "name": "CacheUpdated", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "bool", + "name": "canOpenLoans", + "type": "bool" + } + ], + "name": "CanOpenLoansUpdated", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "account", + "type": "address" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "id", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "amountDeposited", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "collateralAfter", + "type": "uint256" + } + ], + "name": "CollateralDeposited", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "account", + "type": "address" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "id", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "amountWithdrawn", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "collateralAfter", + "type": "uint256" + } + ], + "name": "CollateralWithdrawn", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "uint256", + "name": "issueFeeRate", + "type": "uint256" + } + ], + "name": "IssueFeeRateUpdated", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "account", + "type": "address" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "id", + "type": "uint256" + } + ], + "name": "LoanClosed", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "account", + "type": "address" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "id", + "type": "uint256" + }, + { + "indexed": true, + "internalType": "address", + "name": "liquidator", + "type": "address" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "amountLiquidated", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "collateralLiquidated", + "type": "uint256" + } + ], + "name": "LoanClosedByLiquidation", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "account", + "type": "address" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "id", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "amountRepaid", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "collateralAfter", + "type": "uint256" + } + ], + "name": "LoanClosedByRepayment", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "account", + "type": "address" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "id", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "amount", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "collateral", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "bytes32", + "name": "currency", + "type": "bytes32" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "issuanceFee", + "type": "uint256" + } + ], + "name": "LoanCreated", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "account", + "type": "address" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "id", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "amount", + "type": "uint256" + } + ], + "name": "LoanDrawnDown", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "account", + "type": "address" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "id", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "address", + "name": "liquidator", + "type": "address" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "amountLiquidated", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "collateralLiquidated", + "type": "uint256" + } + ], + "name": "LoanPartiallyLiquidated", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "account", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "repayer", + "type": "address" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "id", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "amountRepaid", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "amountAfter", + "type": "uint256" + } + ], + "name": "LoanRepaymentMade", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "uint256", + "name": "minCollateral", + "type": "uint256" + } + ], + "name": "MinCollateralUpdated", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "address", + "name": "oldOwner", + "type": "address" + }, + { + "indexed": false, + "internalType": "address", + "name": "newOwner", + "type": "address" + } + ], + "name": "OwnerChanged", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "address", + "name": "newOwner", + "type": "address" + } + ], + "name": "OwnerNominated", + "type": "event" + }, + { + "constant": false, + "inputs": [], + "name": "acceptOwnership", + "outputs": [], + "payable": false, + "stateMutability": "nonpayable", + "type": "function" + }, + { + "constant": false, + "inputs": [ + { + "internalType": "address", + "name": "rewardsContract", + "type": "address" + }, + { + "internalType": "bytes32", + "name": "synth", + "type": "bytes32" + } + ], + "name": "addRewardsContracts", + "outputs": [], + "payable": false, + "stateMutability": "nonpayable", + "type": "function" + }, + { + "constant": false, + "inputs": [ + { + "internalType": "bytes32[]", + "name": "_synthNamesInResolver", + "type": "bytes32[]" + }, + { + "internalType": "bytes32[]", + "name": "_synthKeys", + "type": "bytes32[]" + } + ], + "name": "addSynths", + "outputs": [], + "payable": false, + "stateMutability": "nonpayable", + "type": "function" + }, + { + "constant": true, + "inputs": [ + { + "internalType": "bytes32[]", + "name": "_synthNamesInResolver", + "type": "bytes32[]" + }, + { + "internalType": "bytes32[]", + "name": "_synthKeys", + "type": "bytes32[]" + } + ], + "name": "areSynthsAndCurrenciesSet", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "payable": false, + "stateMutability": "view", + "type": "function" + }, + { + "constant": true, + "inputs": [], + "name": "canOpenLoans", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "payable": false, + "stateMutability": "view", + "type": "function" + }, + { + "constant": false, + "inputs": [ + { + "internalType": "uint256", + "name": "amount", + "type": "uint256" + } + ], + "name": "claim", + "outputs": [], + "payable": false, + "stateMutability": "nonpayable", + "type": "function" + }, + { + "constant": false, + "inputs": [ + { + "internalType": "uint256", + "name": "id", + "type": "uint256" + } + ], + "name": "close", + "outputs": [ + { + "internalType": "uint256", + "name": "amount", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "collateral", + "type": "uint256" + } + ], + "payable": false, + "stateMutability": "nonpayable", + "type": "function" + }, + { + "constant": true, + "inputs": [], + "name": "collateralKey", + "outputs": [ + { + "internalType": "bytes32", + "name": "", + "type": "bytes32" + } + ], + "payable": false, + "stateMutability": "view", + "type": "function" + }, + { + "constant": true, + "inputs": [ + { + "internalType": "uint256", + "name": "id", + "type": "uint256" + } + ], + "name": "collateralRatio", + "outputs": [ + { + "internalType": "uint256", + "name": "cratio", + "type": "uint256" + } + ], + "payable": false, + "stateMutability": "view", + "type": "function" + }, + { + "constant": false, + "inputs": [ + { + "internalType": "address", + "name": "borrower", + "type": "address" + }, + { + "internalType": "uint256", + "name": "id", + "type": "uint256" + } + ], + "name": "deposit", + "outputs": [ + { + "internalType": "uint256", + "name": "principal", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "collateral", + "type": "uint256" + } + ], + "payable": true, + "stateMutability": "payable", + "type": "function" + }, + { + "constant": false, + "inputs": [ + { + "internalType": "uint256", + "name": "id", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "amount", + "type": "uint256" + } + ], + "name": "draw", + "outputs": [ + { + "internalType": "uint256", + "name": "principal", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "collateral", + "type": "uint256" + } + ], + "payable": false, + "stateMutability": "nonpayable", + "type": "function" + }, + { + "constant": true, + "inputs": [], + "name": "isResolverCached", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "payable": false, + "stateMutability": "view", + "type": "function" + }, + { + "constant": true, + "inputs": [], + "name": "issueFeeRate", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "payable": false, + "stateMutability": "view", + "type": "function" + }, + { + "constant": false, + "inputs": [ + { + "internalType": "address", + "name": "borrower", + "type": "address" + }, + { + "internalType": "uint256", + "name": "id", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "amount", + "type": "uint256" + } + ], + "name": "liquidate", + "outputs": [], + "payable": false, + "stateMutability": "nonpayable", + "type": "function" + }, + { + "constant": true, + "inputs": [ + { + "internalType": "uint256", + "name": "id", + "type": "uint256" + } + ], + "name": "liquidationAmount", + "outputs": [ + { + "internalType": "uint256", + "name": "liqAmount", + "type": "uint256" + } + ], + "payable": false, + "stateMutability": "view", + "type": "function" + }, + { + "constant": true, + "inputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "name": "loans", + "outputs": [ + { + "internalType": "uint256", + "name": "id", + "type": "uint256" + }, + { + "internalType": "address payable", + "name": "account", + "type": "address" + }, + { + "internalType": "uint256", + "name": "collateral", + "type": "uint256" + }, + { + "internalType": "bytes32", + "name": "currency", + "type": "bytes32" + }, + { + "internalType": "uint256", + "name": "amount", + "type": "uint256" + }, + { + "internalType": "bool", + "name": "short", + "type": "bool" + }, + { + "internalType": "uint256", + "name": "accruedInterest", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "interestIndex", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "lastInteraction", + "type": "uint256" + } + ], + "payable": false, + "stateMutability": "view", + "type": "function" + }, + { + "constant": true, + "inputs": [], + "name": "manager", + "outputs": [ + { + "internalType": "contract ICollateralManager", + "name": "", + "type": "address" + } + ], + "payable": false, + "stateMutability": "view", + "type": "function" + }, + { + "constant": true, + "inputs": [ + { + "internalType": "uint256", + "name": "amount", + "type": "uint256" + }, + { + "internalType": "bytes32", + "name": "currency", + "type": "bytes32" + } + ], + "name": "maxLoan", + "outputs": [ + { + "internalType": "uint256", + "name": "max", + "type": "uint256" + } + ], + "payable": false, + "stateMutability": "view", + "type": "function" + }, + { + "constant": true, + "inputs": [], + "name": "minCollateral", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "payable": false, + "stateMutability": "view", + "type": "function" + }, + { + "constant": true, + "inputs": [], + "name": "minCratio", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "payable": false, + "stateMutability": "view", + "type": "function" + }, + { + "constant": false, + "inputs": [ + { + "internalType": "address", + "name": "_owner", + "type": "address" + } + ], + "name": "nominateNewOwner", + "outputs": [], + "payable": false, + "stateMutability": "nonpayable", + "type": "function" + }, + { + "constant": true, + "inputs": [], + "name": "nominatedOwner", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "payable": false, + "stateMutability": "view", + "type": "function" + }, + { + "constant": false, + "inputs": [ + { + "internalType": "uint256", + "name": "amount", + "type": "uint256" + }, + { + "internalType": "bytes32", + "name": "currency", + "type": "bytes32" + } + ], + "name": "open", + "outputs": [ + { + "internalType": "uint256", + "name": "id", + "type": "uint256" + } + ], + "payable": true, + "stateMutability": "payable", + "type": "function" + }, + { + "constant": true, + "inputs": [], + "name": "owner", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "payable": false, + "stateMutability": "view", + "type": "function" + }, + { + "constant": true, + "inputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "name": "pendingWithdrawals", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "payable": false, + "stateMutability": "view", + "type": "function" + }, + { + "constant": false, + "inputs": [], + "name": "rebuildCache", + "outputs": [], + "payable": false, + "stateMutability": "nonpayable", + "type": "function" + }, + { + "constant": false, + "inputs": [ + { + "internalType": "address", + "name": "borrower", + "type": "address" + }, + { + "internalType": "uint256", + "name": "id", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "amount", + "type": "uint256" + } + ], + "name": "repay", + "outputs": [ + { + "internalType": "uint256", + "name": "principal", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "collateral", + "type": "uint256" + } + ], + "payable": false, + "stateMutability": "nonpayable", + "type": "function" + }, + { + "constant": true, + "inputs": [], + "name": "resolver", + "outputs": [ + { + "internalType": "contract AddressResolver", + "name": "", + "type": "address" + } + ], + "payable": false, + "stateMutability": "view", + "type": "function" + }, + { + "constant": true, + "inputs": [], + "name": "resolverAddressesRequired", + "outputs": [ + { + "internalType": "bytes32[]", + "name": "addresses", + "type": "bytes32[]" + } + ], + "payable": false, + "stateMutability": "view", + "type": "function" + }, + { + "constant": false, + "inputs": [ + { + "internalType": "bool", + "name": "_canOpenLoans", + "type": "bool" + } + ], + "name": "setCanOpenLoans", + "outputs": [], + "payable": false, + "stateMutability": "nonpayable", + "type": "function" + }, + { + "constant": false, + "inputs": [ + { + "internalType": "uint256", + "name": "_issueFeeRate", + "type": "uint256" + } + ], + "name": "setIssueFeeRate", + "outputs": [], + "payable": false, + "stateMutability": "nonpayable", + "type": "function" + }, + { + "constant": false, + "inputs": [ + { + "internalType": "uint256", + "name": "_minCollateral", + "type": "uint256" + } + ], + "name": "setMinCollateral", + "outputs": [], + "payable": false, + "stateMutability": "nonpayable", + "type": "function" + }, + { + "constant": true, + "inputs": [ + { + "internalType": "bytes32", + "name": "", + "type": "bytes32" + } + ], + "name": "shortingRewards", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "payable": false, + "stateMutability": "view", + "type": "function" + }, + { + "constant": true, + "inputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "name": "synths", + "outputs": [ + { + "internalType": "bytes32", + "name": "", + "type": "bytes32" + } + ], + "payable": false, + "stateMutability": "view", + "type": "function" + }, + { + "constant": true, + "inputs": [ + { + "internalType": "bytes32", + "name": "", + "type": "bytes32" + } + ], + "name": "synthsByKey", + "outputs": [ + { + "internalType": "bytes32", + "name": "", + "type": "bytes32" + } + ], + "payable": false, + "stateMutability": "view", + "type": "function" + }, + { + "constant": false, + "inputs": [ + { + "internalType": "uint256", + "name": "id", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "amount", + "type": "uint256" + } + ], + "name": "withdraw", + "outputs": [ + { + "internalType": "uint256", + "name": "principal", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "collateral", + "type": "uint256" + } + ], + "payable": false, + "stateMutability": "nonpayable", + "type": "function" + } + ], + "source": { + "keccak256": "0xd83c3667bcacd4d33cf0fc649cee03d85e7d3914abb7ce6c9898e6ee1d66da81", + "urls": [ + "bzz-raw://77bd631dcee01051f611ea2927feb37c87f5532ce90537b2dc9b8952b85c3576", + "dweb:/ipfs/QmWHiciYDoG7sNnyA7kUzeSmQy4EVV8ktrDNVHDuQhq7tx" + ] + }, + "metadata": { + "compiler": { + "version": "0.5.16+commit.9c3226ce" + }, + "language": "Solidity", + "settings": { + "compilationTarget": { + "CollateralEth.sol": "CollateralEth" + }, + "evmVersion": "istanbul", + "libraries": {}, + "optimizer": { + "enabled": true, + "runs": 200 + }, + "remappings": [] + }, + "sources": { + "CollateralEth.sol": { + "keccak256": "0xd83c3667bcacd4d33cf0fc649cee03d85e7d3914abb7ce6c9898e6ee1d66da81", + "urls": [ + "bzz-raw://77bd631dcee01051f611ea2927feb37c87f5532ce90537b2dc9b8952b85c3576", + "dweb:/ipfs/QmWHiciYDoG7sNnyA7kUzeSmQy4EVV8ktrDNVHDuQhq7tx" + ] + } + }, + "version": 1 + } } } } diff --git a/publish/deployed/mainnet-ovm/params.json b/publish/deployed/mainnet-ovm/params.json index a7bb2d1ae4..ee856ec252 100644 --- a/publish/deployed/mainnet-ovm/params.json +++ b/publish/deployed/mainnet-ovm/params.json @@ -18,7 +18,7 @@ "SHORTS": ["sBTC", "sETH", "sLINK"], "MAX_DEBT": "75000000000000000000000000", "MAX_SKEW_RATE": "200000000000000000", - "BASE_BORROW_RATE": "158443823", + "BASE_BORROW_RATE": "950662938", "BASE_SHORT_RATE": "158443823" } }, @@ -32,5 +32,14 @@ "INTERACTION_DELAY": "0", "COLLAPSE_FEE_RATE": "0" } + }, + { + "name": "COLLATERAL_ETH", + "value": { + "SYNTHS": ["sUSD"], + "MIN_CRATIO": "1200000000000000000", + "MIN_COLLATERAL": "100000000000000000", + "ISSUE_FEE_RATE": "0" + } } ] diff --git a/publish/deployed/mainnet-ovm/versions.json b/publish/deployed/mainnet-ovm/versions.json index 6f06739da9..b1a2558460 100644 --- a/publish/deployed/mainnet-ovm/versions.json +++ b/publish/deployed/mainnet-ovm/versions.json @@ -393,8 +393,9 @@ }, "Synthetix": { "address": "0xff4287311138ad3BD051F84524B2eA3A682944a5", - "status": "current", - "keccak256": "0xbbe79669c2586e5f0a7f35a0577d21b881e0e083b9fc297fa16d6c1de064c749" + "status": "replaced", + "keccak256": "0xbbe79669c2586e5f0a7f35a0577d21b881e0e083b9fc297fa16d6c1de064c749", + "replaced_in": "v2.56.1" }, "DebtCache": { "address": "0xe1f47188168837485527A5B44E7FA7ca55F29C65", @@ -589,5 +590,25 @@ "keccak256": "0x582cac91f50cfefc154d67097081d0dd0d4877333df51c3cfe19485f80ef7e74" } } + }, + "v2.56.1": { + "tag": "v2.56.1", + "fulltag": "v2.56.1", + "release": "Alhena", + "network": "mainnet", + "date": "2021-12-15T18:16:59-05:00", + "commit": "324105690195d2c0925ffbfffe5d0bba8e98fad7", + "contracts": { + "Synthetix": { + "address": "0x20eBfbdD14c9D8093E9AC33e736Ac61bbaC90092", + "status": "current", + "keccak256": "0x87c92d55af4f4b01932ec25770439770836ed60b9652a3f4ab1ec6e0d82d302a" + }, + "CollateralEth": { + "address": "0x308AD16ef90fe7caCb85B784A603CB6E71b1A41a", + "status": "current", + "keccak256": "0xd83c3667bcacd4d33cf0fc649cee03d85e7d3914abb7ce6c9898e6ee1d66da81" + } + } } } diff --git a/publish/deployed/mainnet/config.json b/publish/deployed/mainnet/config.json index 1e92b0c606..d2ea2979fb 100644 --- a/publish/deployed/mainnet/config.json +++ b/publish/deployed/mainnet/config.json @@ -319,5 +319,14 @@ }, "OwnerRelayOnEthereum": { "deploy": false + }, + "TokenStatesETHBTC": { + "deploy": false + }, + "ProxysETHBTC": { + "deploy": false + }, + "SynthsETHBTC": { + "deploy": false } } diff --git a/publish/deployed/mainnet/deployment.json b/publish/deployed/mainnet/deployment.json index d7f42cf641..2faa3ddc2f 100644 --- a/publish/deployed/mainnet/deployment.json +++ b/publish/deployed/mainnet/deployment.json @@ -989,6 +989,33 @@ "timestamp": "2021-11-23T22:26:10.000Z", "txn": "https://etherscan.io/tx/0xcb2daf5c04fde995ba13c6ca4e3eb9edd794f3031f0380ce653abed742ae4834", "network": "mainnet" + }, + "TokenStatesETHBTC": { + "name": "TokenStatesETHBTC", + "address": "0x042A7A0022A7695454ac5Be77a4860e50c9683fC", + "source": "TokenState", + "link": "https://etherscan.io/address/0x042A7A0022A7695454ac5Be77a4860e50c9683fC", + "timestamp": "2021-12-15T22:05:17.310Z", + "txn": "", + "network": "mainnet" + }, + "ProxysETHBTC": { + "name": "ProxysETHBTC", + "address": "0x104eDF1da359506548BFc7c25bA1E28C16a70235", + "source": "ProxyERC20", + "link": "https://etherscan.io/address/0x104eDF1da359506548BFc7c25bA1E28C16a70235", + "timestamp": "2021-12-15T22:05:20.000Z", + "txn": "https://etherscan.io/tx/0xef2ff387f4ccca14ff4b02d9226420cdd4766873d99f8733a92ec78e8fea2dae", + "network": "mainnet" + }, + "SynthsETHBTC": { + "name": "SynthsETHBTC", + "address": "0xcc3aab773e2171b2E257Ee17001400eE378aa52B", + "source": "MultiCollateralSynth", + "link": "https://etherscan.io/address/0xcc3aab773e2171b2E257Ee17001400eE378aa52B", + "timestamp": "2021-12-15T22:13:15.518Z", + "txn": "", + "network": "mainnet" } }, "sources": { @@ -8146,8 +8173,7 @@ ], "payable": false, "stateMutability": "nonpayable", - "type": "constructor", - "signature": "constructor" + "type": "constructor" }, { "anonymous": false, @@ -8160,8 +8186,7 @@ } ], "name": "AssociatedContractUpdated", - "type": "event", - "signature": "0x73f20cff579e8a4086fa607db83867595f1b6a798e718c0bfa0b94a404128e03" + "type": "event" }, { "anonymous": false, @@ -8180,8 +8205,7 @@ } ], "name": "OwnerChanged", - "type": "event", - "signature": "0xb532073b38c83145e3e5135377a08bf9aab55bc0fd7c1179cd4fb995d2a5159c" + "type": "event" }, { "anonymous": false, @@ -8194,8 +8218,7 @@ } ], "name": "OwnerNominated", - "type": "event", - "signature": "0x906a1c6bd7e3091ea86693dd029a831c19049ce77f1dce2ce0bab1cacbabce22" + "type": "event" }, { "constant": false, @@ -8204,8 +8227,7 @@ "outputs": [], "payable": false, "stateMutability": "nonpayable", - "type": "function", - "signature": "0x79ba5097" + "type": "function" }, { "constant": true, @@ -8231,8 +8253,7 @@ ], "payable": false, "stateMutability": "view", - "type": "function", - "signature": "0xdd62ed3e" + "type": "function" }, { "constant": true, @@ -8247,8 +8268,7 @@ ], "payable": false, "stateMutability": "view", - "type": "function", - "signature": "0xaefc4ccb" + "type": "function" }, { "constant": true, @@ -8269,8 +8289,7 @@ ], "payable": false, "stateMutability": "view", - "type": "function", - "signature": "0x70a08231" + "type": "function" }, { "constant": false, @@ -8285,8 +8304,7 @@ "outputs": [], "payable": false, "stateMutability": "nonpayable", - "type": "function", - "signature": "0x1627540c" + "type": "function" }, { "constant": true, @@ -8301,8 +8319,7 @@ ], "payable": false, "stateMutability": "view", - "type": "function", - "signature": "0x53a47bb7" + "type": "function" }, { "constant": true, @@ -8317,8 +8334,7 @@ ], "payable": false, "stateMutability": "view", - "type": "function", - "signature": "0x8da5cb5b" + "type": "function" }, { "constant": false, @@ -8343,8 +8359,7 @@ "outputs": [], "payable": false, "stateMutability": "nonpayable", - "type": "function", - "signature": "0xda46098c" + "type": "function" }, { "constant": false, @@ -8359,8 +8374,7 @@ "outputs": [], "payable": false, "stateMutability": "nonpayable", - "type": "function", - "signature": "0x52f445ca" + "type": "function" }, { "constant": false, @@ -8380,8 +8394,7 @@ "outputs": [], "payable": false, "stateMutability": "nonpayable", - "type": "function", - "signature": "0xb46310f6" + "type": "function" } ], "source": { @@ -16305,7 +16318,7 @@ } }, "ProxyERC20": { - "bytecode": "608060405234801561001057600080fd5b50604051610f7f380380610f7f8339818101604052602081101561003357600080fd5b505180806001600160a01b038116610092576040805162461bcd60e51b815260206004820152601960248201527f4f776e657220616464726573732063616e6e6f74206265203000000000000000604482015290519081900360640190fd5b600080546001600160a01b0319166001600160a01b038316908117825560408051928352602083019190915280517fb532073b38c83145e3e5135377a08bf9aab55bc0fd7c1179cd4fb995d2a5159c9281900390910190a1505050610e83806100fc6000396000f3fe6080604052600436106100f35760003560e01c8063776d1a011161008a57806395d89b411161005957806395d89b4114610473578063a9059cbb14610488578063d4b83992146104c1578063dd62ed3e146104d6576100f3565b8063776d1a011461038157806379ba5097146103b45780638da5cb5b146103c9578063907dff97146103de576100f3565b806323b872dd116100c657806323b872dd146102af578063313ce567146102f257806353a47bb71461031d57806370a082311461034e576100f3565b806306fdde031461017c578063095ea7b3146102065780631627540c1461025357806318160ddd14610288575b60025460408051635e33fc1960e11b815233600482015290516001600160a01b039092169163bc67f8329160248082019260009290919082900301818387803b15801561013f57600080fd5b505af1158015610153573d6000803e3d6000fd5b5050505060405136600082376000803683346002545af13d6000833e80610178573d82fd5b3d82f35b34801561018857600080fd5b50610191610511565b6040805160208082528351818301528351919283929083019185019080838360005b838110156101cb5781810151838201526020016101b3565b50505050905090810190601f1680156101f85780820380516001836020036101000a031916815260200191505b509250505060405180910390f35b34801561021257600080fd5b5061023f6004803603604081101561022957600080fd5b506001600160a01b038135169060200135610648565b604080519115158252519081900360200190f35b34801561025f57600080fd5b506102866004803603602081101561027657600080fd5b50356001600160a01b0316610736565b005b34801561029457600080fd5b5061029d610792565b60408051918252519081900360200190f35b3480156102bb57600080fd5b5061023f600480360360608110156102d257600080fd5b506001600160a01b03813581169160208101359091169060400135610808565b3480156102fe57600080fd5b506103076108ff565b6040805160ff9092168252519081900360200190f35b34801561032957600080fd5b50610332610944565b604080516001600160a01b039092168252519081900360200190f35b34801561035a57600080fd5b5061029d6004803603602081101561037157600080fd5b50356001600160a01b0316610953565b34801561038d57600080fd5b50610286600480360360208110156103a457600080fd5b50356001600160a01b03166109d6565b3480156103c057600080fd5b50610286610a32565b3480156103d557600080fd5b50610332610aee565b3480156103ea57600080fd5b50610286600480360360c081101561040157600080fd5b81019060208101813564010000000081111561041c57600080fd5b82018360208201111561042e57600080fd5b8035906020019184600183028401116401000000008311171561045057600080fd5b919350915080359060208101359060408101359060608101359060800135610afd565b34801561047f57600080fd5b50610191610c06565b34801561049457600080fd5b5061023f600480360360408110156104ab57600080fd5b506001600160a01b038135169060200135610c4b565b3480156104cd57600080fd5b50610332610d04565b3480156104e257600080fd5b5061029d600480360360408110156104f957600080fd5b506001600160a01b0381358116916020013516610d13565b600254604080516306fdde0360e01b815290516060926001600160a01b0316916306fdde03916004808301926000929190829003018186803b15801561055657600080fd5b505afa15801561056a573d6000803e3d6000fd5b505050506040513d6000823e601f3d908101601f19168201604052602081101561059357600080fd5b81019080805160405193929190846401000000008211156105b357600080fd5b9083019060208201858111156105c857600080fd5b82516401000000008111828201881017156105e257600080fd5b82525081516020918201929091019080838360005b8381101561060f5781810151838201526020016105f7565b50505050905090810190601f16801561063c5780820380516001836020036101000a031916815260200191505b50604052505050905090565b60025460408051635e33fc1960e11b815233600482015290516000926001600160a01b03169163bc67f832916024808301928692919082900301818387803b15801561069357600080fd5b505af11580156106a7573d6000803e3d6000fd5b50506002546040805163095ea7b360e01b81526001600160a01b03888116600483015260248201889052915191909216935063095ea7b3925060448083019260209291908290030181600087803b15801561070157600080fd5b505af1158015610715573d6000803e3d6000fd5b505050506040513d602081101561072b57600080fd5b506001949350505050565b61073e610d9f565b600180546001600160a01b0383166001600160a01b0319909116811790915560408051918252517f906a1c6bd7e3091ea86693dd029a831c19049ce77f1dce2ce0bab1cacbabce229181900360200190a150565b600254604080516318160ddd60e01b815290516000926001600160a01b0316916318160ddd916004808301926020929190829003018186803b1580156107d757600080fd5b505afa1580156107eb573d6000803e3d6000fd5b505050506040513d602081101561080157600080fd5b5051905090565b60025460408051635e33fc1960e11b815233600482015290516000926001600160a01b03169163bc67f832916024808301928692919082900301818387803b15801561085357600080fd5b505af1158015610867573d6000803e3d6000fd5b5050600254604080516323b872dd60e01b81526001600160a01b03898116600483015288811660248301526044820188905291519190921693506323b872dd925060648083019260209291908290030181600087803b1580156108c957600080fd5b505af11580156108dd573d6000803e3d6000fd5b505050506040513d60208110156108f357600080fd5b50600195945050505050565b6002546040805163313ce56760e01b815290516000926001600160a01b03169163313ce567916004808301926020929190829003018186803b1580156107d757600080fd5b6001546001600160a01b031681565b600254604080516370a0823160e01b81526001600160a01b038481166004830152915160009392909216916370a0823191602480820192602092909190829003018186803b1580156109a457600080fd5b505afa1580156109b8573d6000803e3d6000fd5b505050506040513d60208110156109ce57600080fd5b505192915050565b6109de610d9f565b600280546001600160a01b0383166001600160a01b0319909116811790915560408051918252517f814250a3b8c79fcbe2ead2c131c952a278491c8f4322a79fe84b5040a810373e9181900360200190a150565b6001546001600160a01b03163314610a7b5760405162461bcd60e51b8152600401808060200182810382526035815260200180610deb6035913960400191505060405180910390fd5b600054600154604080516001600160a01b03938416815292909116602083015280517fb532073b38c83145e3e5135377a08bf9aab55bc0fd7c1179cd4fb995d2a5159c9281900390910190a160018054600080546001600160a01b03199081166001600160a01b03841617909155169055565b6000546001600160a01b031681565b6002546001600160a01b03163314610b53576040805162461bcd60e51b8152602060048201526014602482015273135d5cdd081899481c1c9bde1e481d185c99d95d60621b604482015290519081900360640190fd5b604080516020601f89018190048102820181019092528781528791606091908a908490819084018382808284376000920191909152509293508992505081159050610bbd5760018114610bc85760028114610bd45760038114610be15760048114610bef57610bfa565b8260208301a0610bfa565b868360208401a1610bfa565b85878460208501a2610bfa565b8486888560208601a3610bfa565b838587898660208701a45b50505050505050505050565b600254604080516395d89b4160e01b815290516060926001600160a01b0316916395d89b41916004808301926000929190829003018186803b15801561055657600080fd5b60025460408051635e33fc1960e11b815233600482015290516000926001600160a01b03169163bc67f832916024808301928692919082900301818387803b158015610c9657600080fd5b505af1158015610caa573d6000803e3d6000fd5b50506002546040805163a9059cbb60e01b81526001600160a01b03888116600483015260248201889052915191909216935063a9059cbb925060448083019260209291908290030181600087803b15801561070157600080fd5b6002546001600160a01b031681565b60025460408051636eb1769f60e11b81526001600160a01b03858116600483015284811660248301529151600093929092169163dd62ed3e91604480820192602092909190829003018186803b158015610d6c57600080fd5b505afa158015610d80573d6000803e3d6000fd5b505050506040513d6020811015610d9657600080fd5b50519392505050565b6000546001600160a01b03163314610de85760405162461bcd60e51b815260040180806020018281038252602f815260200180610e20602f913960400191505060405180910390fd5b56fe596f75206d757374206265206e6f6d696e61746564206265666f726520796f752063616e20616363657074206f776e6572736869704f6e6c792074686520636f6e7472616374206f776e6572206d617920706572666f726d207468697320616374696f6ea265627a7a72315820d9fe4f5dd8e7296e50ed921f77c64c5a5801472804aae64777e9f58ae10b8d0664736f6c63430005100032", + "bytecode": "608060405234801561001057600080fd5b50604051610f7f380380610f7f8339818101604052602081101561003357600080fd5b505180806001600160a01b038116610092576040805162461bcd60e51b815260206004820152601960248201527f4f776e657220616464726573732063616e6e6f74206265203000000000000000604482015290519081900360640190fd5b600080546001600160a01b0319166001600160a01b038316908117825560408051928352602083019190915280517fb532073b38c83145e3e5135377a08bf9aab55bc0fd7c1179cd4fb995d2a5159c9281900390910190a1505050610e83806100fc6000396000f3fe6080604052600436106100f35760003560e01c8063776d1a011161008a57806395d89b411161005957806395d89b4114610473578063a9059cbb14610488578063d4b83992146104c1578063dd62ed3e146104d6576100f3565b8063776d1a011461038157806379ba5097146103b45780638da5cb5b146103c9578063907dff97146103de576100f3565b806323b872dd116100c657806323b872dd146102af578063313ce567146102f257806353a47bb71461031d57806370a082311461034e576100f3565b806306fdde031461017c578063095ea7b3146102065780631627540c1461025357806318160ddd14610288575b60025460408051635e33fc1960e11b815233600482015290516001600160a01b039092169163bc67f8329160248082019260009290919082900301818387803b15801561013f57600080fd5b505af1158015610153573d6000803e3d6000fd5b5050505060405136600082376000803683346002545af13d6000833e80610178573d82fd5b3d82f35b34801561018857600080fd5b50610191610511565b6040805160208082528351818301528351919283929083019185019080838360005b838110156101cb5781810151838201526020016101b3565b50505050905090810190601f1680156101f85780820380516001836020036101000a031916815260200191505b509250505060405180910390f35b34801561021257600080fd5b5061023f6004803603604081101561022957600080fd5b506001600160a01b038135169060200135610648565b604080519115158252519081900360200190f35b34801561025f57600080fd5b506102866004803603602081101561027657600080fd5b50356001600160a01b0316610736565b005b34801561029457600080fd5b5061029d610792565b60408051918252519081900360200190f35b3480156102bb57600080fd5b5061023f600480360360608110156102d257600080fd5b506001600160a01b03813581169160208101359091169060400135610808565b3480156102fe57600080fd5b506103076108ff565b6040805160ff9092168252519081900360200190f35b34801561032957600080fd5b50610332610944565b604080516001600160a01b039092168252519081900360200190f35b34801561035a57600080fd5b5061029d6004803603602081101561037157600080fd5b50356001600160a01b0316610953565b34801561038d57600080fd5b50610286600480360360208110156103a457600080fd5b50356001600160a01b03166109d6565b3480156103c057600080fd5b50610286610a32565b3480156103d557600080fd5b50610332610aee565b3480156103ea57600080fd5b50610286600480360360c081101561040157600080fd5b81019060208101813564010000000081111561041c57600080fd5b82018360208201111561042e57600080fd5b8035906020019184600183028401116401000000008311171561045057600080fd5b919350915080359060208101359060408101359060608101359060800135610afd565b34801561047f57600080fd5b50610191610c06565b34801561049457600080fd5b5061023f600480360360408110156104ab57600080fd5b506001600160a01b038135169060200135610c4b565b3480156104cd57600080fd5b50610332610d04565b3480156104e257600080fd5b5061029d600480360360408110156104f957600080fd5b506001600160a01b0381358116916020013516610d13565b600254604080516306fdde0360e01b815290516060926001600160a01b0316916306fdde03916004808301926000929190829003018186803b15801561055657600080fd5b505afa15801561056a573d6000803e3d6000fd5b505050506040513d6000823e601f3d908101601f19168201604052602081101561059357600080fd5b81019080805160405193929190846401000000008211156105b357600080fd5b9083019060208201858111156105c857600080fd5b82516401000000008111828201881017156105e257600080fd5b82525081516020918201929091019080838360005b8381101561060f5781810151838201526020016105f7565b50505050905090810190601f16801561063c5780820380516001836020036101000a031916815260200191505b50604052505050905090565b60025460408051635e33fc1960e11b815233600482015290516000926001600160a01b03169163bc67f832916024808301928692919082900301818387803b15801561069357600080fd5b505af11580156106a7573d6000803e3d6000fd5b50506002546040805163095ea7b360e01b81526001600160a01b03888116600483015260248201889052915191909216935063095ea7b3925060448083019260209291908290030181600087803b15801561070157600080fd5b505af1158015610715573d6000803e3d6000fd5b505050506040513d602081101561072b57600080fd5b506001949350505050565b61073e610d9f565b600180546001600160a01b0383166001600160a01b0319909116811790915560408051918252517f906a1c6bd7e3091ea86693dd029a831c19049ce77f1dce2ce0bab1cacbabce229181900360200190a150565b600254604080516318160ddd60e01b815290516000926001600160a01b0316916318160ddd916004808301926020929190829003018186803b1580156107d757600080fd5b505afa1580156107eb573d6000803e3d6000fd5b505050506040513d602081101561080157600080fd5b5051905090565b60025460408051635e33fc1960e11b815233600482015290516000926001600160a01b03169163bc67f832916024808301928692919082900301818387803b15801561085357600080fd5b505af1158015610867573d6000803e3d6000fd5b5050600254604080516323b872dd60e01b81526001600160a01b03898116600483015288811660248301526044820188905291519190921693506323b872dd925060648083019260209291908290030181600087803b1580156108c957600080fd5b505af11580156108dd573d6000803e3d6000fd5b505050506040513d60208110156108f357600080fd5b50600195945050505050565b6002546040805163313ce56760e01b815290516000926001600160a01b03169163313ce567916004808301926020929190829003018186803b1580156107d757600080fd5b6001546001600160a01b031681565b600254604080516370a0823160e01b81526001600160a01b038481166004830152915160009392909216916370a0823191602480820192602092909190829003018186803b1580156109a457600080fd5b505afa1580156109b8573d6000803e3d6000fd5b505050506040513d60208110156109ce57600080fd5b505192915050565b6109de610d9f565b600280546001600160a01b0383166001600160a01b0319909116811790915560408051918252517f814250a3b8c79fcbe2ead2c131c952a278491c8f4322a79fe84b5040a810373e9181900360200190a150565b6001546001600160a01b03163314610a7b5760405162461bcd60e51b8152600401808060200182810382526035815260200180610deb6035913960400191505060405180910390fd5b600054600154604080516001600160a01b03938416815292909116602083015280517fb532073b38c83145e3e5135377a08bf9aab55bc0fd7c1179cd4fb995d2a5159c9281900390910190a160018054600080546001600160a01b03199081166001600160a01b03841617909155169055565b6000546001600160a01b031681565b6002546001600160a01b03163314610b53576040805162461bcd60e51b8152602060048201526014602482015273135d5cdd081899481c1c9bde1e481d185c99d95d60621b604482015290519081900360640190fd5b604080516020601f89018190048102820181019092528781528791606091908a908490819084018382808284376000920191909152509293508992505081159050610bbd5760018114610bc85760028114610bd45760038114610be15760048114610bef57610bfa565b8260208301a0610bfa565b868360208401a1610bfa565b85878460208501a2610bfa565b8486888560208601a3610bfa565b838587898660208701a45b50505050505050505050565b600254604080516395d89b4160e01b815290516060926001600160a01b0316916395d89b41916004808301926000929190829003018186803b15801561055657600080fd5b60025460408051635e33fc1960e11b815233600482015290516000926001600160a01b03169163bc67f832916024808301928692919082900301818387803b158015610c9657600080fd5b505af1158015610caa573d6000803e3d6000fd5b50506002546040805163a9059cbb60e01b81526001600160a01b03888116600483015260248201889052915191909216935063a9059cbb925060448083019260209291908290030181600087803b15801561070157600080fd5b6002546001600160a01b031681565b60025460408051636eb1769f60e11b81526001600160a01b03858116600483015284811660248301529151600093929092169163dd62ed3e91604480820192602092909190829003018186803b158015610d6c57600080fd5b505afa158015610d80573d6000803e3d6000fd5b505050506040513d6020811015610d9657600080fd5b50519392505050565b6000546001600160a01b03163314610de85760405162461bcd60e51b815260040180806020018281038252602f815260200180610e20602f913960400191505060405180910390fd5b56fe596f75206d757374206265206e6f6d696e61746564206265666f726520796f752063616e20616363657074206f776e6572736869704f6e6c792074686520636f6e7472616374206f776e6572206d617920706572666f726d207468697320616374696f6ea265627a7a72315820bc16ca473a169ecfb5918c5dcce84659bb9529548bc8336990487ca08ab50b1a64736f6c63430005100032", "abi": [ { "inputs": [ @@ -16317,8 +16330,7 @@ ], "payable": false, "stateMutability": "nonpayable", - "type": "constructor", - "signature": "constructor" + "type": "constructor" }, { "anonymous": false, @@ -16343,8 +16355,7 @@ } ], "name": "Approval", - "type": "event", - "signature": "0x8c5be1e5ebec7d5bd14f71427d1e84f3dd0314c0f7b2291e5b200ac8c7c3b925" + "type": "event" }, { "anonymous": false, @@ -16363,8 +16374,7 @@ } ], "name": "OwnerChanged", - "type": "event", - "signature": "0xb532073b38c83145e3e5135377a08bf9aab55bc0fd7c1179cd4fb995d2a5159c" + "type": "event" }, { "anonymous": false, @@ -16377,8 +16387,7 @@ } ], "name": "OwnerNominated", - "type": "event", - "signature": "0x906a1c6bd7e3091ea86693dd029a831c19049ce77f1dce2ce0bab1cacbabce22" + "type": "event" }, { "anonymous": false, @@ -16391,8 +16400,7 @@ } ], "name": "TargetUpdated", - "type": "event", - "signature": "0x814250a3b8c79fcbe2ead2c131c952a278491c8f4322a79fe84b5040a810373e" + "type": "event" }, { "anonymous": false, @@ -16417,8 +16425,7 @@ } ], "name": "Transfer", - "type": "event", - "signature": "0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef" + "type": "event" }, { "payable": true, @@ -16463,8 +16470,7 @@ "outputs": [], "payable": false, "stateMutability": "nonpayable", - "type": "function", - "signature": "0x907dff97" + "type": "function" }, { "constant": false, @@ -16473,8 +16479,7 @@ "outputs": [], "payable": false, "stateMutability": "nonpayable", - "type": "function", - "signature": "0x79ba5097" + "type": "function" }, { "constant": true, @@ -16500,8 +16505,7 @@ ], "payable": false, "stateMutability": "view", - "type": "function", - "signature": "0xdd62ed3e" + "type": "function" }, { "constant": false, @@ -16527,8 +16531,7 @@ ], "payable": false, "stateMutability": "nonpayable", - "type": "function", - "signature": "0x095ea7b3" + "type": "function" }, { "constant": true, @@ -16549,8 +16552,7 @@ ], "payable": false, "stateMutability": "view", - "type": "function", - "signature": "0x70a08231" + "type": "function" }, { "constant": true, @@ -16565,8 +16567,7 @@ ], "payable": false, "stateMutability": "view", - "type": "function", - "signature": "0x313ce567" + "type": "function" }, { "constant": true, @@ -16581,8 +16582,7 @@ ], "payable": false, "stateMutability": "view", - "type": "function", - "signature": "0x06fdde03" + "type": "function" }, { "constant": false, @@ -16597,8 +16597,7 @@ "outputs": [], "payable": false, "stateMutability": "nonpayable", - "type": "function", - "signature": "0x1627540c" + "type": "function" }, { "constant": true, @@ -16613,8 +16612,7 @@ ], "payable": false, "stateMutability": "view", - "type": "function", - "signature": "0x53a47bb7" + "type": "function" }, { "constant": true, @@ -16629,8 +16627,7 @@ ], "payable": false, "stateMutability": "view", - "type": "function", - "signature": "0x8da5cb5b" + "type": "function" }, { "constant": false, @@ -16645,8 +16642,7 @@ "outputs": [], "payable": false, "stateMutability": "nonpayable", - "type": "function", - "signature": "0x776d1a01" + "type": "function" }, { "constant": true, @@ -16661,8 +16657,7 @@ ], "payable": false, "stateMutability": "view", - "type": "function", - "signature": "0x95d89b41" + "type": "function" }, { "constant": true, @@ -16677,8 +16672,7 @@ ], "payable": false, "stateMutability": "view", - "type": "function", - "signature": "0xd4b83992" + "type": "function" }, { "constant": true, @@ -16693,8 +16687,7 @@ ], "payable": false, "stateMutability": "view", - "type": "function", - "signature": "0x18160ddd" + "type": "function" }, { "constant": false, @@ -16720,8 +16713,7 @@ ], "payable": false, "stateMutability": "nonpayable", - "type": "function", - "signature": "0xa9059cbb" + "type": "function" }, { "constant": false, @@ -16752,15 +16744,14 @@ ], "payable": false, "stateMutability": "nonpayable", - "type": "function", - "signature": "0x23b872dd" + "type": "function" } ], "source": { - "keccak256": "0xfb133acce922041ba6b061ee7a8ce32c7908b0cbb37f343dd9fd81d532999bd8", + "keccak256": "0xa4731706c3292ed368ded616be2fcf28877d8a853cbce27be7d78e6a57636e9f", "urls": [ - "bzz-raw://9ea8edaed10b21ff232e019b44be802821ef5c62b47888b81d19c7232280ccc8", - "dweb:/ipfs/QmNvmS5845T4XjAsUms9WLHDjGxDRRuHo5RBawFyH3GeVN" + "bzz-raw://f099f912010a38e63b1f3c6208bc7b605e59b9dd42bde020cb603dc4f73ce754", + "dweb:/ipfs/QmcP1qHd6wTcPh7xBMvWTbeWE3Ms8vmCSRsyKwkymiTNuW" ] }, "metadata": { @@ -16782,10 +16773,10 @@ }, "sources": { "ProxyERC20.sol": { - "keccak256": "0xfb133acce922041ba6b061ee7a8ce32c7908b0cbb37f343dd9fd81d532999bd8", + "keccak256": "0xa4731706c3292ed368ded616be2fcf28877d8a853cbce27be7d78e6a57636e9f", "urls": [ - "bzz-raw://9ea8edaed10b21ff232e019b44be802821ef5c62b47888b81d19c7232280ccc8", - "dweb:/ipfs/QmNvmS5845T4XjAsUms9WLHDjGxDRRuHo5RBawFyH3GeVN" + "bzz-raw://f099f912010a38e63b1f3c6208bc7b605e59b9dd42bde020cb603dc4f73ce754", + "dweb:/ipfs/QmcP1qHd6wTcPh7xBMvWTbeWE3Ms8vmCSRsyKwkymiTNuW" ] } }, diff --git a/publish/deployed/mainnet/feeds.json b/publish/deployed/mainnet/feeds.json index c5bbe64b60..84c9d27fd5 100644 --- a/publish/deployed/mainnet/feeds.json +++ b/publish/deployed/mainnet/feeds.json @@ -31,6 +31,10 @@ "asset": "DEFI", "feed": "0xa8E875F94138B0C5b51d1e1d5dE35bbDdd28EA87" }, + "ETHBTC": { + "asset": "ETHBTC", + "feed": "0xAc559F25B1619171CbC396a50854A3240b6A4e99" + }, "EUR": { "asset": "EUR", "feed": "0xb49f677943BC038e9857d61E7d053CaA2C1734C1" diff --git a/publish/deployed/mainnet/synths.json b/publish/deployed/mainnet/synths.json index 4d080168ff..2990a2a199 100644 --- a/publish/deployed/mainnet/synths.json +++ b/publish/deployed/mainnet/synths.json @@ -64,6 +64,11 @@ "asset": "DOT", "subclass": "MultiCollateralSynth" }, + { + "name": "sETHBTC", + "asset": "ETHBTC", + "subclass": "MultiCollateralSynth" + }, { "name": "sDEFI", "asset": "DEFI", diff --git a/publish/deployed/mainnet/versions.json b/publish/deployed/mainnet/versions.json index 21b7af55eb..d55965b4c7 100644 --- a/publish/deployed/mainnet/versions.json +++ b/publish/deployed/mainnet/versions.json @@ -3890,5 +3890,30 @@ "keccak256": "0x582cac91f50cfefc154d67097081d0dd0d4877333df51c3cfe19485f80ef7e74" } } + }, + "v2.56.1": { + "tag": "v2.56.1", + "fulltag": "v2.56.1", + "release": "Alhena", + "network": "mainnet", + "date": "2021-12-15T18:16:59-05:00", + "commit": "324105690195d2c0925ffbfffe5d0bba8e98fad7", + "contracts": { + "TokenStatesETHBTC": { + "address": "0x042A7A0022A7695454ac5Be77a4860e50c9683fC", + "status": "current", + "keccak256": "0x10a63d041fa9899b429b12dddcf047b55c23fe62019c08abbba9fa1f603738d3" + }, + "ProxysETHBTC": { + "address": "0x104eDF1da359506548BFc7c25bA1E28C16a70235", + "status": "current", + "keccak256": "0xa4731706c3292ed368ded616be2fcf28877d8a853cbce27be7d78e6a57636e9f" + }, + "SynthsETHBTC": { + "address": "0xcc3aab773e2171b2E257Ee17001400eE378aa52B", + "status": "current", + "keccak256": "0x77d6d75f0faee02ff7a6bae37e0aa47e6afb849484a1f4ea6794131ffc5c77b8" + } + } } } diff --git a/publish/releases.json b/publish/releases.json index ffba79b704..5d97117820 100644 --- a/publish/releases.json +++ b/publish/releases.json @@ -413,6 +413,12 @@ "sources": ["DebtCache", "Issuer"], "released": "both" }, + { + "sip": 188, + "layer": "base", + "sources": [], + "released": "base" + }, { "sip": 192, "layer": "both", @@ -422,7 +428,19 @@ { "sip": 194, "layer": "ovm", - "sources": ["Synthetix"] + "sources": ["Synthetix"], + "released": "ovm" + }, + { + "sip": 195, + "layer": "ovm", + "sources": ["CollateralEth"], + "released": "ovm" + }, + { + "sip": 196, + "layer": "both", + "sources": ["ExchangeRates"] } ], "releases": [ @@ -736,22 +754,22 @@ }, { "name": "Alhena", - "released": false, + "released": true, "version": { "major": 2, "minor": 56 }, - "sips": [] + "sips": [188] }, { "name": "Alhena (Optimism)", "ovm": true, - "released": false, + "released": true, "version": { "major": 2, "minor": 56 }, - "sips": [] + "sips": [194, 195] }, { "name": "Peacock", diff --git a/publish/src/commands/deploy/configure-loans.js b/publish/src/commands/deploy/configure-loans.js index 38324c8966..998d2cd443 100644 --- a/publish/src/commands/deploy/configure-loans.js +++ b/publish/src/commands/deploy/configure-loans.js @@ -34,11 +34,11 @@ module.exports = async ({ console.log(gray(`\n------ INITIALISING MULTI COLLATERAL ------\n`)); - if (CollateralShort && CollateralManager) { - let CollateralsArg = [CollateralShort].map(addressOf); - if (CollateralEth && CollateralErc20) { - CollateralsArg = [CollateralEth, CollateralErc20, CollateralShort].map(addressOf); - } + if (CollateralManager) { + const CollateralsArg = [CollateralShort, CollateralEth, CollateralErc20] + .filter(contract => !!contract) + .map(addressOf); + await runStep({ contract: 'CollateralManager', target: CollateralManager, diff --git a/publish/src/commands/deploy/configure-system-settings.js b/publish/src/commands/deploy/configure-system-settings.js index 8213638635..e182bc66e0 100644 --- a/publish/src/commands/deploy/configure-system-settings.js +++ b/publish/src/commands/deploy/configure-system-settings.js @@ -332,14 +332,16 @@ module.exports = async ({ comment: 'Set the fee rate for minting sETH from ETH in the EtherWrapper (SIP-112)', }); + // Disable checking this as now the current value is set to 0 + const etherWrapperBurnFeeRate = await getDeployParameter('ETHER_WRAPPER_BURN_FEE_RATE'); await runStep({ contract: 'SystemSettings', target: SystemSettings, read: 'etherWrapperBurnFeeRate', readTarget: previousSystemSettings, - expected: input => input !== '0', // only change if zero + expected: input => etherWrapperBurnFeeRate === '0' || input !== '0', // only change if the value to set is above zero and the value onchain is 0 write: 'setEtherWrapperBurnFeeRate', - writeArg: await getDeployParameter('ETHER_WRAPPER_BURN_FEE_RATE'), + writeArg: etherWrapperBurnFeeRate, comment: 'Set the fee rate for burning sETH for ETH in the EtherWrapper (SIP-112)', }); diff --git a/publish/src/commands/deploy/deploy-core.js b/publish/src/commands/deploy/deploy-core.js index b1f190d137..b611aea6dc 100644 --- a/publish/src/commands/deploy/deploy-core.js +++ b/publish/src/commands/deploy/deploy-core.js @@ -14,7 +14,6 @@ module.exports = async ({ currentSynthetixSupply, currentWeekOfInflation, deployer, - oracleAddress, useOvm, }) => { console.log(gray(`\n------ DEPLOY LIBRARIES ------\n`)); @@ -63,7 +62,7 @@ module.exports = async ({ await deployer.deployContract({ name: 'ExchangeRates', source: useOvm ? 'ExchangeRates' : 'ExchangeRatesWithDexPricing', - args: [account, oracleAddress, addressOf(readProxyForResolver), [], []], + args: [account, addressOf(readProxyForResolver)], }); await deployer.deployContract({ diff --git a/publish/src/commands/deploy/index.js b/publish/src/commands/deploy/index.js index 686982fa01..b24cd46e27 100644 --- a/publish/src/commands/deploy/index.js +++ b/publish/src/commands/deploy/index.js @@ -62,7 +62,6 @@ const deploy = async ({ ignoreSafetyChecks, manageNonces, network = DEFAULTS.network, - oracleExrates, privateKey, providerUrl, skipFeedChecks = false, @@ -212,7 +211,6 @@ const deploy = async ({ currentSynthetixSupply, currentLastMintEvent, currentWeekOfInflation, - oracleAddress, systemSuspended, } = await systemAndParameterCheck({ account, @@ -229,7 +227,6 @@ const deploy = async ({ maxPriorityFeePerGas, getDeployParameter, network, - oracleExrates, providerUrl, skipFeedChecks, standaloneFeeds, @@ -276,7 +273,6 @@ const deploy = async ({ currentSynthetixSupply, currentWeekOfInflation, deployer, - oracleAddress, useOvm, }); diff --git a/publish/src/commands/deploy/rebuild-resolver-caches.js b/publish/src/commands/deploy/rebuild-resolver-caches.js index 5194315d21..24a69526bf 100644 --- a/publish/src/commands/deploy/rebuild-resolver-caches.js +++ b/publish/src/commands/deploy/rebuild-resolver-caches.js @@ -66,7 +66,7 @@ module.exports = async ({ ]); // interface doesn't matter as long as it responds to MixinResolver } } catch (err) { - if (/eth_getLogs are limited to a 10,000 blocks range/.test(err.message)) { + if (/limited to a 10,000 blocks range/.test(err.message)) { console.log( yellow.bold( 'Warning: Cannot fetch logs on this network. Known limitation on OVM mainnet - cannot search back greater than 10k blocks' @@ -219,7 +219,7 @@ module.exports = async ({ } } - const addressesChunkSize = useOvm ? 5 : 20; + const addressesChunkSize = 20; let batchCounter = 1; for (let i = 0; i < contractsToRebuildCache.length; i += addressesChunkSize) { const chunk = contractsToRebuildCache.slice(i, i + addressesChunkSize); diff --git a/publish/src/commands/deploy/system-and-parameter-check.js b/publish/src/commands/deploy/system-and-parameter-check.js index ac835a43c2..c14394ab25 100644 --- a/publish/src/commands/deploy/system-and-parameter-check.js +++ b/publish/src/commands/deploy/system-and-parameter-check.js @@ -33,7 +33,6 @@ module.exports = async ({ maxPriorityFeePerGas, getDeployParameter, network, - oracleExrates, providerUrl, skipFeedChecks, standaloneFeeds, @@ -43,7 +42,6 @@ module.exports = async ({ yes, buildPath, }) => { - let oracleAddress; let currentSynthetixSupply; let oldExrates; let currentLastMintEvent; @@ -92,12 +90,8 @@ module.exports = async ({ try { oldExrates = deployer.getExistingContract({ contract: 'ExchangeRates' }); - if (!oracleExrates) { - oracleAddress = await oldExrates.oracle(); - } } catch (err) { if (freshDeploy) { - oracleAddress = oracleExrates || account; oldExrates = undefined; // unset to signify that a fresh one will be deployed } else { console.error( @@ -127,12 +121,10 @@ module.exports = async ({ } } - for (const address of [account, oracleAddress]) { - if (!isAddress(address)) { - console.error(red('Invalid address detected (please check your inputs):', address)); - process.exitCode = 1; - process.exit(); - } + if (!isAddress(account)) { + console.error(red('Invalid address detected (please check your inputs):', account)); + process.exitCode = 1; + process.exit(); } const newSynthsToAdd = synths @@ -206,7 +198,6 @@ module.exports = async ({ : yellow('⚠ NO'), 'Deployer account:': account, 'Synthetix totalSupply': `${Math.round(formatUnits(currentSynthetixSupply) / 1e6)}m`, - 'ExchangeRates Oracle': oracleAddress, 'Last Mint Event': `${currentLastMintEvent} (${new Date(currentLastMintEvent * 1000)})`, 'Current Weeks Of Inflation': currentWeekOfInflation, 'Aggregated Prices': aggregatedPriceResults, @@ -240,7 +231,6 @@ module.exports = async ({ currentLastMintEvent, currentWeekOfInflation, oldExrates, - oracleAddress, systemSuspended, }; }; diff --git a/publish/src/commands/owner.js b/publish/src/commands/owner.js index 606bb3af9f..f66bce6bf6 100644 --- a/publish/src/commands/owner.js +++ b/publish/src/commands/owner.js @@ -40,6 +40,7 @@ const owner = async ({ useOvm, useFork, providerUrl, + skipOwnership = false, throwOnNotNominatedOwner = false, }) => { ensureNetwork(network); @@ -239,91 +240,95 @@ const owner = async ({ } } - console.log(gray('Looking for contracts whose ownership we should accept')); const warnings = []; - // prevent dupes if some contracts are in there twice (looking at you ProxyERC20 and ProxyERC20sUSD) - const appendedOwnerCache = {}; - for (const contract of Object.keys(config)) { - if (!deployment.targets[contract]) { - const msg = yellow(`WARNING: contract ${contract} not found in deployment file`); - console.log(msg); - warnings.push(msg); - continue; - } - const { address, source } = deployment.targets[contract]; - const { abi } = deployment.sources[source]; - const deployedContract = new ethers.Contract(address, abi, provider); - - // ignore contracts that don't support Owned - if (!deployedContract.functions.owner) { - continue; - } - const currentOwner = (await deployedContract.owner()).toLowerCase(); - const nominatedOwner = (await deployedContract.nominatedOwner()).toLowerCase(); - - if (currentOwner === newOwner.toLowerCase()) { - console.log(gray(`${newOwner} is already the owner of ${contract} ${address}`)); - } else if (nominatedOwner === newOwner.toLowerCase()) { - const encodedData = deployedContract.interface.encodeFunctionData('acceptOwnership', []); - - if (address in appendedOwnerCache) { - console.log(gray('Skipping as this action is already in the batch')); + if (!skipOwnership) { + console.log(gray('Looking for contracts whose ownership we should accept')); + // prevent dupes if some contracts are in there twice (looking at you ProxyERC20 and ProxyERC20sUSD) + const appendedOwnerCache = {}; + for (const contract of Object.keys(config)) { + if (!deployment.targets[contract]) { + const msg = yellow(`WARNING: contract ${contract} not found in deployment file`); + console.log(msg); + warnings.push(msg); continue; - } else { - appendedOwnerCache[address] = true; } + const { address, source } = deployment.targets[contract]; + const { abi } = deployment.sources[source]; + const deployedContract = new ethers.Contract(address, abi, provider); - if (safeBatchSubmitter && !useFork) { - console.log( - gray(`Attempting to append`, yellow(`${contract}.acceptOwnership()`), `to the batch`) - ); - const { appended } = await safeBatchSubmitter.appendTransaction({ - to: address, - data: encodedData, - }); - if (!appended) { - console.log(gray('Skipping adding to the batch as already in pending queue')); + // ignore contracts that don't support Owned + if (!deployedContract.functions.owner) { + continue; + } + const currentOwner = (await deployedContract.owner()).toLowerCase(); + const nominatedOwner = (await deployedContract.nominatedOwner()).toLowerCase(); + + if (currentOwner === newOwner.toLowerCase()) { + console.log(gray(`${newOwner} is already the owner of ${contract} ${address}`)); + } else if (nominatedOwner === newOwner.toLowerCase()) { + const encodedData = deployedContract.interface.encodeFunctionData('acceptOwnership', []); + + if (address in appendedOwnerCache) { + console.log(gray('Skipping as this action is already in the batch')); + continue; + } else { + appendedOwnerCache[address] = true; } - } else if (relayers) { - // Relayer - console.log( - gray('Adding'), - yellow(`${contract}.acceptOwnership()`), - gray('to the relayer actions') - ); - relayers.actions.push({ target: address, data: encodedData }); - } else { - try { - await confirmOrEnd(gray(`Confirm: Submit`, yellow(`${contract}.acceptOwnership()`), `?`)); - const params = await assignGasOptions({ - tx: { - to: address, - data: encodedData, - }, - provider, - maxFeePerGas, - maxPriorityFeePerGas, + if (safeBatchSubmitter && !useFork) { + console.log( + gray(`Attempting to append`, yellow(`${contract}.acceptOwnership()`), `to the batch`) + ); + const { appended } = await safeBatchSubmitter.appendTransaction({ + to: address, + data: encodedData, }); - - if (gasLimit) { - params.gasLimit = ethers.BigNumber.from(gasLimit); + if (!appended) { + console.log(gray('Skipping adding to the batch as already in pending queue')); + } + } else if (relayers) { + // Relayer + console.log( + gray('Adding'), + yellow(`${contract}.acceptOwnership()`), + gray('to the relayer actions') + ); + relayers.actions.push({ target: address, data: encodedData }); + } else { + try { + await confirmOrEnd( + gray(`Confirm: Submit`, yellow(`${contract}.acceptOwnership()`), `?`) + ); + + const params = await assignGasOptions({ + tx: { + to: address, + data: encodedData, + }, + provider, + maxFeePerGas, + maxPriorityFeePerGas, + }); + + if (gasLimit) { + params.gasLimit = ethers.BigNumber.from(gasLimit); + } + + const tx = await signer.sendTransaction(params); + const receipt = await tx.wait(); + + logTx(receipt); + } catch (err) { + throw Error(`Transaction failed to submit.\n${err}`); } - - const tx = await signer.sendTransaction(params); - const receipt = await tx.wait(); - - logTx(receipt); - } catch (err) { - throw Error(`Transaction failed to submit.\n${err}`); } - } - } else { - const msg = `Cannot acceptOwnership on ${contract} as nominatedOwner: ${nominatedOwner} isn't the newOwner ${newOwner} you specified. Have you run the nominate command yet?`; - if (throwOnNotNominatedOwner && contract !== 'DappMaintenance') { - throw Error(msg); } else { - console.log(cyan(msg)); + const msg = `Cannot acceptOwnership on ${contract} as nominatedOwner: ${nominatedOwner} isn't the newOwner ${newOwner} you specified. Have you run the nominate command yet?`; + if (throwOnNotNominatedOwner && contract !== 'DappMaintenance') { + throw Error(msg); + } else { + console.log(cyan(msg)); + } } } } @@ -468,6 +473,7 @@ module.exports = { .option('--max-priority-fee-per-gas ', 'Priority gas fee price in GWEI', '1') .option('-l, --gas-limit ', 'Gas limit', parseInt, DEFAULTS.gasLimit) .option('-n, --network ', 'The network to run off.', x => x.toLowerCase(), 'kovan') + .option('-s, --skip-ownership', 'Skip ownership checks.') .option('-y, --yes', 'Dont prompt, just reply yes.') .option('-z, --use-ovm', 'Target deployment for the OVM (Optimism).') .option( diff --git a/test/contracts/BaseSynthetix.js b/test/contracts/BaseSynthetix.js index 366d9206c3..18959c8ae1 100644 --- a/test/contracts/BaseSynthetix.js +++ b/test/contracts/BaseSynthetix.js @@ -10,11 +10,13 @@ require('./common'); // import common test scaffolding const { setupContract, setupAllContracts } = require('./setup'); -const { currentTime, fastForward, toUnit } = require('../utils')(); +const { fastForward, toUnit } = require('../utils')(); const { ensureOnlyExpectedMutativeFunctions, onlyGivenAddressCanInvoke, + setupPriceAggregators, + updateAggregatorRates, updateRatesWithDefaults, setStatus, } = require('./helpers'); @@ -33,8 +35,6 @@ contract('BaseSynthetix', async accounts => { exchangeRates, debtCache, escrow, - oracle, - timestamp, addressResolver, systemSettings, systemStatus; @@ -68,9 +68,7 @@ contract('BaseSynthetix', async accounts => { ], })); - // Send a price update to guarantee we're not stale. - oracle = account1; - timestamp = await currentTime(); + await setupPriceAggregators(exchangeRates, owner, [sAUD, sEUR, sETH]); }); addSnapshotBeforeRestoreAfterEach(); @@ -406,7 +404,7 @@ contract('BaseSynthetix', async accounts => { }); describe('when a user has exchanged into sETH', () => { beforeEach(async () => { - await updateRatesWithDefaults({ exchangeRates, oracle, debtCache }); + await updateRatesWithDefaults({ exchangeRates, owner, debtCache }); await baseSynthetix.issueSynths(toUnit('100'), { from: owner }); await baseSynthetix.exchange(sUSD, toUnit('10'), sETH, { from: owner }); @@ -434,13 +432,10 @@ contract('BaseSynthetix', async accounts => { // fast forward to get past initial SNX setting await fastForward((await exchangeRates.rateStalePeriod()).add(web3.utils.toBN('300'))); - timestamp = await currentTime(); - - await exchangeRates.updateRates( + await updateAggregatorRates( + exchangeRates, [sAUD, sEUR, sETH], - ['0.5', '1.25', '100'].map(toUnit), - timestamp, - { from: oracle } + ['0.5', '1.25', '100'].map(toUnit) ); await debtCache.takeDebtSnapshot(); }); @@ -449,9 +444,7 @@ contract('BaseSynthetix', async accounts => { }); describe('when SNX is also set', () => { beforeEach(async () => { - timestamp = await currentTime(); - - await exchangeRates.updateRates([SNX], ['1'].map(toUnit), timestamp, { from: oracle }); + await updateAggregatorRates(exchangeRates, [SNX], ['1'].map(toUnit)); }); it('then no stale rates', async () => { assert.equal(await baseSynthetix.anySynthOrSNXRateIsInvalid(), false); @@ -461,11 +454,7 @@ contract('BaseSynthetix', async accounts => { beforeEach(async () => { await fastForward((await exchangeRates.rateStalePeriod()).add(web3.utils.toBN('300'))); - timestamp = await currentTime(); - - await exchangeRates.updateRates([SNX, sAUD], ['0.1', '0.78'].map(toUnit), timestamp, { - from: oracle, - }); + await updateAggregatorRates(exchangeRates, [SNX, sAUD], ['0.1', '0.78'].map(toUnit)); }); it('then anySynthOrSNXRateIsInvalid() returns true', async () => { @@ -522,7 +511,7 @@ contract('BaseSynthetix', async accounts => { beforeEach(async () => { // Ensure all synths have rates to allow issuance - await updateRatesWithDefaults({ exchangeRates, oracle, debtCache }); + await updateRatesWithDefaults({ exchangeRates, owner, debtCache }); }); it('should transfer using the ERC20 transfer function @gasprofile', async () => { @@ -707,28 +696,21 @@ contract('BaseSynthetix', async accounts => { it('should not allow transfer if the exchange rate for SNX is stale', async () => { await ensureTransferReverts(); - const timestamp = await currentTime(); - // now give some synth rates - await exchangeRates.updateRates([sAUD, sEUR], ['0.5', '1.25'].map(toUnit), timestamp, { - from: oracle, - }); + + await updateAggregatorRates(exchangeRates, [sAUD, sEUR], ['0.5', '1.25'].map(toUnit)); await debtCache.takeDebtSnapshot(); await ensureTransferReverts(); // the remainder of the synths have prices - await exchangeRates.updateRates([sETH], ['100'].map(toUnit), timestamp, { - from: oracle, - }); + await updateAggregatorRates(exchangeRates, [sETH], ['100'].map(toUnit)); await debtCache.takeDebtSnapshot(); await ensureTransferReverts(); // now give SNX rate - await exchangeRates.updateRates([SNX], ['1'].map(toUnit), timestamp, { - from: oracle, - }); + await updateAggregatorRates(exchangeRates, [SNX], ['1'].map(toUnit)); // now SNX transfer should work await baseSynthetix.transfer(account2, value, { from: account1 }); @@ -740,28 +722,20 @@ contract('BaseSynthetix', async accounts => { it('should not allow transfer if the exchange rate for any synth is stale', async () => { await ensureTransferReverts(); - const timestamp = await currentTime(); - // now give SNX rate - await exchangeRates.updateRates([SNX], ['1'].map(toUnit), timestamp, { - from: oracle, - }); + await updateAggregatorRates(exchangeRates, [SNX], ['1'].map(toUnit)); await debtCache.takeDebtSnapshot(); await ensureTransferReverts(); // now give some synth rates - await exchangeRates.updateRates([sAUD, sEUR], ['0.5', '1.25'].map(toUnit), timestamp, { - from: oracle, - }); + await updateAggregatorRates(exchangeRates, [sAUD, sEUR], ['0.5', '1.25'].map(toUnit)); await debtCache.takeDebtSnapshot(); await ensureTransferReverts(); // now give the remainder of synths rates - await exchangeRates.updateRates([sETH], ['100'].map(toUnit), timestamp, { - from: oracle, - }); + await updateAggregatorRates(exchangeRates, [sETH], ['100'].map(toUnit)); await debtCache.takeDebtSnapshot(); // now SNX transfer should work @@ -850,8 +824,7 @@ contract('BaseSynthetix', async accounts => { await systemSettings.setExchangeDynamicFeeRounds('0', { from: owner }); // Set sEUR for purposes of this test - const timestamp1 = await currentTime(); - await exchangeRates.updateRates([sEUR], [toUnit('0.75')], timestamp1, { from: oracle }); + await updateAggregatorRates(exchangeRates, [sEUR], [toUnit('0.75')]); await debtCache.takeDebtSnapshot(); const issuedSynthetixs = web3.utils.toBN('200000'); @@ -879,8 +852,7 @@ contract('BaseSynthetix', async accounts => { }); // Increase the value of sEUR relative to synthetix - const timestamp2 = await currentTime(); - await exchangeRates.updateRates([sEUR], [toUnit('2.10')], timestamp2, { from: oracle }); + await updateAggregatorRates(exchangeRates, [sEUR], [toUnit('2.10')]); await debtCache.takeDebtSnapshot(); // Ensure that the new synthetix account1 receives cannot be transferred out. @@ -898,10 +870,9 @@ contract('BaseSynthetix', async accounts => { await systemSettings.setPriceDeviationThresholdFactor(toUnit('5'), { from: owner }); // Set sAUD for purposes of this test - const timestamp1 = await currentTime(); const aud2usdrate = toUnit('2'); - await exchangeRates.updateRates([sAUD], [aud2usdrate], timestamp1, { from: oracle }); + await updateAggregatorRates(exchangeRates, [sAUD], [aud2usdrate]); await debtCache.takeDebtSnapshot(); const issuedSynthetixs = web3.utils.toBN('200000'); @@ -923,9 +894,8 @@ contract('BaseSynthetix', async accounts => { await baseSynthetix.exchange(sUSD, issuedSynths, sAUD, { from: account1 }); // Increase the value of sAUD relative to synthetix - const timestamp2 = await currentTime(); const newAUDExchangeRate = toUnit('1'); - await exchangeRates.updateRates([sAUD], [newAUDExchangeRate], timestamp2, { from: oracle }); + await updateAggregatorRates(exchangeRates, [sAUD], [newAUDExchangeRate]); await debtCache.takeDebtSnapshot(); const transferable2 = await baseSynthetix.transferableSynthetix(account1); diff --git a/test/contracts/CollateralErc20.js b/test/contracts/CollateralErc20.js index 6121bdacad..5e1b4146b6 100644 --- a/test/contracts/CollateralErc20.js +++ b/test/contracts/CollateralErc20.js @@ -8,11 +8,16 @@ const BN = require('bn.js'); const PublicEST8Decimals = artifacts.require('PublicEST8Decimals'); -const { fastForward, toUnit, currentTime } = require('../utils')(); +const { fastForward, toUnit } = require('../utils')(); const { setupAllContracts, setupContract } = require('./setup'); -const { ensureOnlyExpectedMutativeFunctions, setStatus } = require('./helpers'); +const { + ensureOnlyExpectedMutativeFunctions, + setStatus, + setupPriceAggregators, + updateAggregatorRates, +} = require('./helpers'); const { toBytes32, @@ -47,7 +52,7 @@ contract('CollateralErc20', async accounts => { let id; let proxy, tokenState; - const [deployerAccount, owner, oracle, , account1, account2] = accounts; + const [deployerAccount, owner, , , account1, account2] = accounts; let cerc20, managerState, @@ -85,17 +90,7 @@ contract('CollateralErc20', async accounts => { }; const updateRatesWithDefaults = async () => { - const timestamp = await currentTime(); - - await exchangeRates.updateRates([sETH], ['100'].map(toUnit), timestamp, { - from: oracle, - }); - - const sBTC = toBytes32('sBTC'); - - await exchangeRates.updateRates([sBTC], ['10000'].map(toUnit), timestamp, { - from: oracle, - }); + await updateAggregatorRates(exchangeRates, [sETH, sBTC], [100, 10000].map(toUnit)); }; const fastForwardAndUpdateRates = async seconds => { @@ -147,6 +142,8 @@ contract('CollateralErc20', async accounts => { ], })); + await setupPriceAggregators(exchangeRates, owner, [sBTC, sETH]); + managerState = await CollateralManagerState.new(owner, ZERO_ADDRESS, { from: deployerAccount }); const maxDebt = toUnit(10000000); @@ -304,28 +301,20 @@ contract('CollateralErc20', async accounts => { }); it('when the price falls by 25% our c ratio is 150%', async () => { - await exchangeRates.updateRates([sBTC], ['7500'].map(toUnit), await currentTime(), { - from: oracle, - }); + await updateAggregatorRates(exchangeRates, [sBTC], [7500].map(toUnit)); const ratio = await cerc20.collateralRatio(id); assert.bnEqual(ratio, toUnit(1.5)); }); it('when the price increases by 100% our c ratio is 400%', async () => { - await exchangeRates.updateRates([sBTC], ['20000'].map(toUnit), await currentTime(), { - from: oracle, - }); - + await updateAggregatorRates(exchangeRates, [sBTC], [20000].map(toUnit)); const ratio = await cerc20.collateralRatio(id); assert.bnEqual(ratio, toUnit(4)); }); it('when the price fallsby 50% our cratio is 100%', async () => { - await exchangeRates.updateRates([sBTC], ['5000'].map(toUnit), await currentTime(), { - from: oracle, - }); - + await updateAggregatorRates(exchangeRates, [sBTC], [5000].map(toUnit)); const ratio = await cerc20.collateralRatio(id); assert.bnEqual(ratio, toUnit(1)); }); @@ -346,10 +335,7 @@ contract('CollateralErc20', async accounts => { }); it('price changes should not change the cratio', async () => { - await exchangeRates.updateRates([sBTC], ['75'].map(toUnit), await currentTime(), { - from: oracle, - }); - + await updateAggregatorRates(exchangeRates, [sBTC], [75].map(toUnit)); const ratio = await cerc20.collateralRatio(id); assert.bnEqual(ratio, toUnit(2)); }); @@ -909,10 +895,7 @@ contract('CollateralErc20', async accounts => { let liquidationAmount; beforeEach(async () => { - const timestamp = await currentTime(); - await exchangeRates.updateRates([sBTC], ['7000'].map(toUnit), timestamp, { - from: oracle, - }); + await updateAggregatorRates(exchangeRates, [sBTC], [7000].map(toUnit)); await issuesUSDToAccount(toUnit(5000), account2); @@ -962,10 +945,7 @@ contract('CollateralErc20', async accounts => { describe('when a loan needs to be completely liquidated', async () => { beforeEach(async () => { - const timestamp = await currentTime(); - await exchangeRates.updateRates([sBTC], ['5000'].map(toUnit), timestamp, { - from: oracle, - }); + await updateAggregatorRates(exchangeRates, [sBTC], [5000].map(toUnit)); loan = await cerc20.loans(id); diff --git a/test/contracts/CollateralEth.js b/test/contracts/CollateralEth.js index 122337bf79..99635dba55 100644 --- a/test/contracts/CollateralEth.js +++ b/test/contracts/CollateralEth.js @@ -6,11 +6,16 @@ const { assert, addSnapshotBeforeRestoreAfterEach } = require('./common'); const BN = require('bn.js'); -const { fastForward, getEthBalance, toUnit, fromUnit, currentTime } = require('../utils')(); +const { fastForward, getEthBalance, toUnit, fromUnit } = require('../utils')(); const { setupAllContracts } = require('./setup'); -const { ensureOnlyExpectedMutativeFunctions, setStatus } = require('./helpers'); +const { + ensureOnlyExpectedMutativeFunctions, + setStatus, + setupPriceAggregators, + updateAggregatorRates, +} = require('./helpers'); const { toBytes32 } = require('../..'); @@ -20,6 +25,7 @@ contract('CollateralEth', async accounts => { const sUSD = toBytes32('sUSD'); const sETH = toBytes32('sETH'); + const sBTC = toBytes32('sBTC'); const oneETH = toUnit(1); const twoETH = toUnit(2); @@ -36,7 +42,7 @@ contract('CollateralEth', async accounts => { let loan; let id; - const [, owner, oracle, account1, account2] = accounts; + const [, owner, , account1, account2] = accounts; let ceth, managerState, @@ -101,6 +107,8 @@ contract('CollateralEth', async accounts => { ], })); + await setupPriceAggregators(exchangeRates, owner, [sBTC, sETH]); + await managerState.setAssociatedContract(manager.address, { from: owner }); FEE_ADDRESS = await feePool.FEE_ADDRESS(); @@ -139,17 +147,7 @@ contract('CollateralEth', async accounts => { }; const updateRatesWithDefaults = async () => { - const timestamp = await currentTime(); - - await exchangeRates.updateRates([sETH], ['100'].map(toUnit), timestamp, { - from: oracle, - }); - - const sBTC = toBytes32('sBTC'); - - await exchangeRates.updateRates([sBTC], ['10000'].map(toUnit), timestamp, { - from: oracle, - }); + await updateAggregatorRates(exchangeRates, [sETH, sBTC], [100, 10000].map(toUnit)); }; const fastForwardAndUpdateRates = async seconds => { @@ -219,28 +217,19 @@ contract('CollateralEth', async accounts => { }); it('when the price falls by 25% our c ratio is 150%', async () => { - await exchangeRates.updateRates([sETH], ['75'].map(toUnit), await currentTime(), { - from: oracle, - }); - + await updateAggregatorRates(exchangeRates, [sETH], [toUnit(75)]); const ratio = await ceth.collateralRatio(id); assert.bnEqual(ratio, toUnit(1.5)); }); it('when the price increases by 100% our c ratio is 400%', async () => { - await exchangeRates.updateRates([sETH], ['200'].map(toUnit), await currentTime(), { - from: oracle, - }); - + await updateAggregatorRates(exchangeRates, [sETH], [toUnit(200)]); const ratio = await ceth.collateralRatio(id); assert.bnEqual(ratio, toUnit(4)); }); it('when the price falls by 50% our cratio is 100%', async () => { - await exchangeRates.updateRates([sETH], ['50'].map(toUnit), await currentTime(), { - from: oracle, - }); - + await updateAggregatorRates(exchangeRates, [sETH], [toUnit(50)]); const ratio = await ceth.collateralRatio(id); assert.bnEqual(ratio, toUnit(1)); }); @@ -261,10 +250,7 @@ contract('CollateralEth', async accounts => { }); it('price changes should not change the cratio', async () => { - await exchangeRates.updateRates([sETH], ['75'].map(toUnit), await currentTime(), { - from: oracle, - }); - + await updateAggregatorRates(exchangeRates, [sETH], [toUnit(75)]); const ratio = await ceth.collateralRatio(id); assert.bnEqual(ratio, toUnit(2)); }); @@ -279,7 +265,7 @@ contract('CollateralEth', async accounts => { assert.bnClose(sUSDAmount, toUnit('200'), '100'); // $260 worth of eth should allow $200 (0.02) of sBTC to be issued. - const sBTCAmount = await ceth.maxLoan(toUnit('2.6'), toBytes32('sBTC')); + const sBTCAmount = await ceth.maxLoan(toUnit('2.6'), sBTC); assert.bnEqual(sBTCAmount, toUnit('0.02')); }); @@ -791,11 +777,7 @@ contract('CollateralEth', async accounts => { let liquidationAmount; beforeEach(async () => { - const timestamp = await currentTime(); - await exchangeRates.updateRates([sETH], ['90'].map(toUnit), timestamp, { - from: oracle, - }); - + await updateAggregatorRates(exchangeRates, [sETH], [toUnit(90)]); await issuesUSDToAccount(toUnit(1000), account2); liquidatorEthBalBefore = new BN(await getEthBalance(account2)); @@ -856,11 +838,7 @@ contract('CollateralEth', async accounts => { let liquidatorEthBalBefore; beforeEach(async () => { - const timestamp = await currentTime(); - await exchangeRates.updateRates([sETH], ['50'].map(toUnit), timestamp, { - from: oracle, - }); - + await updateAggregatorRates(exchangeRates, [sETH], [toUnit(50)]); loan = await ceth.loans(id); await issuesUSDToAccount(toUnit(1000), account2); diff --git a/test/contracts/CollateralManager.js b/test/contracts/CollateralManager.js index 9b6db8a531..ffa1634309 100644 --- a/test/contracts/CollateralManager.js +++ b/test/contracts/CollateralManager.js @@ -4,11 +4,16 @@ const { contract } = require('hardhat'); const { assert, addSnapshotBeforeRestoreAfterEach } = require('./common'); -const { toUnit, currentTime, fastForward } = require('../utils')(); +const { toUnit, fastForward } = require('../utils')(); const { setupAllContracts, setupContract, mockToken } = require('./setup'); -const { ensureOnlyExpectedMutativeFunctions, onlyGivenAddressCanInvoke } = require('./helpers'); +const { + ensureOnlyExpectedMutativeFunctions, + onlyGivenAddressCanInvoke, + setupPriceAggregators, + updateAggregatorRates, +} = require('./helpers'); const { toBytes32, @@ -16,7 +21,7 @@ const { } = require('../..'); contract('CollateralManager', async accounts => { - const [, owner, oracle, , account1] = accounts; + const [, owner, , , account1] = accounts; const sETH = toBytes32('sETH'); const sUSD = toBytes32('sUSD'); @@ -58,17 +63,7 @@ contract('CollateralManager', async accounts => { }; const updateRatesWithDefaults = async () => { - const timestamp = await currentTime(); - - await exchangeRates.updateRates([sETH], ['100'].map(toUnit), timestamp, { - from: oracle, - }); - - const sBTC = toBytes32('sBTC'); - - await exchangeRates.updateRates([sBTC], ['10000'].map(toUnit), timestamp, { - from: oracle, - }); + await updateAggregatorRates(exchangeRates, [sETH, sBTC], [100, 10000].map(toUnit)); }; const fastForwardAndUpdateRates = async seconds => { @@ -127,6 +122,8 @@ contract('CollateralManager', async accounts => { ], })); + await setupPriceAggregators(exchangeRates, owner, [sBTC, sETH]); + maxDebt = toUnit(50000000); await managerState.setAssociatedContract(manager.address, { from: owner }); diff --git a/test/contracts/CollateralShort.js b/test/contracts/CollateralShort.js index 013a51e3e5..24bb6dd32d 100644 --- a/test/contracts/CollateralShort.js +++ b/test/contracts/CollateralShort.js @@ -4,11 +4,16 @@ const { contract } = require('hardhat'); const { assert, addSnapshotBeforeRestoreAfterEach } = require('./common'); -const { fastForward, toUnit, fromUnit, currentTime } = require('../utils')(); +const { fastForward, toUnit, fromUnit } = require('../utils')(); const { setupAllContracts } = require('./setup'); -const { ensureOnlyExpectedMutativeFunctions, setExchangeFeeRateForSynths } = require('./helpers'); +const { + ensureOnlyExpectedMutativeFunctions, + setExchangeFeeRateForSynths, + setupPriceAggregators, + updateAggregatorRates, +} = require('./helpers'); const { toBytes32, @@ -22,7 +27,7 @@ contract('CollateralShort', async accounts => { const sETH = toBytes32('sETH'); const sBTC = toBytes32('sBTC'); - const [, owner, oracle, , account1, account2] = accounts; + const [, owner, , , account1, account2] = accounts; let short, managerState, @@ -57,21 +62,10 @@ contract('CollateralShort', async accounts => { }; const updateRatesWithDefaults = async () => { - let timestamp; - for (let i = 0; i < EXCHANGE_DYNAMIC_FEE_ROUNDS; i++) { - timestamp = await currentTime(); - await exchangeRates.updateRates([sETH], ['100'].map(toUnit), timestamp, { - from: oracle, - }); - } - const sBTC = toBytes32('sBTC'); for (let i = 0; i < EXCHANGE_DYNAMIC_FEE_ROUNDS; i++) { - timestamp = await currentTime(); - await exchangeRates.updateRates([sBTC], ['10000'].map(toUnit), timestamp, { - from: oracle, - }); + await updateAggregatorRates(exchangeRates, [sETH, sBTC], [100, 10000].map(toUnit)); } }; @@ -111,6 +105,8 @@ contract('CollateralShort', async accounts => { ], })); + await setupPriceAggregators(exchangeRates, owner, [sBTC, sETH]); + await managerState.setAssociatedContract(manager.address, { from: owner }); FEE_ADDRESS = await feePool.FEE_ADDRESS(); @@ -514,10 +510,7 @@ contract('CollateralShort', async accounts => { await fastForwardAndUpdateRates(3600); - const timestamp = await currentTime(); - await exchangeRates.updateRates([sETH], ['50'].map(toUnit), timestamp, { - from: oracle, - }); + await updateAggregatorRates(exchangeRates, [sETH], [toUnit(50)]); // simulate buying sETH for 50 susd. await sUSDSynth.transfer(owner, toUnit(50), { from: account1 }); @@ -539,10 +532,7 @@ contract('CollateralShort', async accounts => { await fastForwardAndUpdateRates(3600); - const timestamp = await currentTime(); - await exchangeRates.updateRates([sETH], ['150'].map(toUnit), timestamp, { - from: oracle, - }); + await updateAggregatorRates(exchangeRates, [sETH], [toUnit(150)]); // simulate buying sETH for 150 susd. await sUSDSynth.transfer(owner, toUnit(150), { from: account1 }); @@ -574,10 +564,7 @@ contract('CollateralShort', async accounts => { }); it('liquidation should be capped to only fix the c ratio', async () => { - const timestamp = await currentTime(); - await exchangeRates.updateRates([sETH], ['110'].map(toUnit), timestamp, { - from: oracle, - }); + await updateAggregatorRates(exchangeRates, [sETH], [toUnit(110)]); // When the ETH price increases 10% to $110, the short // which started at 130% should allow 0.18 ETH @@ -628,11 +615,7 @@ contract('CollateralShort', async accounts => { await fastForwardAndUpdateRates(3600); - const timestamp = await currentTime(); - await exchangeRates.updateRates([sETH], ['150'].map(toUnit), timestamp, { - from: oracle, - }); - + await updateAggregatorRates(exchangeRates, [sETH], [toUnit(150)]); await debtCache.takeDebtSnapshot(); result = await debtCache.cachedDebt(); assert.bnEqual(result, toUnit(111100)); @@ -673,10 +656,7 @@ contract('CollateralShort', async accounts => { await fastForwardAndUpdateRates(3600); - const timestamp = await currentTime(); - await exchangeRates.updateRates([sETH], ['150'].map(toUnit), timestamp, { - from: oracle, - }); + await updateAggregatorRates(exchangeRates, [sETH], [toUnit(150)]); // 111100 + 50 - (2 * 50) = 111,050 @@ -720,10 +700,7 @@ contract('CollateralShort', async accounts => { await fastForwardAndUpdateRates(3600); - const timestamp = await currentTime(); - await exchangeRates.updateRates([sETH], ['50'].map(toUnit), timestamp, { - from: oracle, - }); + await updateAggregatorRates(exchangeRates, [sETH], [toUnit(50)]); // 111100 - 50 + (2 * 50) = 111,150 diff --git a/test/contracts/CollateralUtil.js b/test/contracts/CollateralUtil.js index b3c1c848a8..767d17a8a6 100644 --- a/test/contracts/CollateralUtil.js +++ b/test/contracts/CollateralUtil.js @@ -4,11 +4,15 @@ const { contract, web3 } = require('hardhat'); const { assert, addSnapshotBeforeRestoreAfterEach } = require('./common'); -const { toUnit, currentTime } = require('../utils')(); +const { toUnit } = require('../utils')(); const { setupAllContracts, setupContract, mockToken } = require('./setup'); -const { ensureOnlyExpectedMutativeFunctions } = require('./helpers'); +const { + ensureOnlyExpectedMutativeFunctions, + setupPriceAggregators, + updateAggregatorRates, +} = require('./helpers'); const { toBytes32 } = require('../..'); @@ -27,7 +31,7 @@ contract('CollateralUtil', async accounts => { const name = 'Some name'; const symbol = 'TOKEN'; - const [, owner, oracle, , account1] = accounts; + const [, owner, , , account1] = accounts; let cerc20, managerState, @@ -63,20 +67,6 @@ contract('CollateralUtil', async accounts => { await renBTC.transfer(receiver, issueAmount, { from: owner }); }; - const updateRatesWithDefaults = async () => { - const timestamp = await currentTime(); - - await exchangeRates.updateRates([sETH], ['100'].map(toUnit), timestamp, { - from: oracle, - }); - - const sBTC = toBytes32('sBTC'); - - await exchangeRates.updateRates([sBTC], ['10000'].map(toUnit), timestamp, { - from: oracle, - }); - }; - const deployCollateral = async ({ owner, manager, @@ -125,6 +115,8 @@ contract('CollateralUtil', async accounts => { ], })); + await setupPriceAggregators(exchangeRates, owner, [sBTC, sETH]); + await managerState.setAssociatedContract(manager.address, { from: owner }); ({ token: renBTC } = await mockToken({ @@ -186,7 +178,7 @@ contract('CollateralUtil', async accounts => { addSnapshotBeforeRestoreAfterEach(); beforeEach(async () => { - await updateRatesWithDefaults(); + await updateAggregatorRates(exchangeRates, [sETH, sBTC], [100, 10000].map(toUnit)); await issuesUSDToAccount(toUnit(1000), owner); await issuesBTCtoAccount(toUnit(10), owner); @@ -224,9 +216,7 @@ contract('CollateralUtil', async accounts => { }); it('when we start at 200%, we can take a 25% reduction in collateral prices', async () => { - await exchangeRates.updateRates([sBTC], ['7500'].map(toUnit), await currentTime(), { - from: oracle, - }); + await updateAggregatorRates(exchangeRates, [sBTC], [toUnit(7500)]); amountToLiquidate = await cerc20.liquidationAmount(id); @@ -234,9 +224,7 @@ contract('CollateralUtil', async accounts => { }); it('when we start at 200%, a price shock of 30% in the collateral requires 25% of the loan to be liquidated', async () => { - await exchangeRates.updateRates([sBTC], ['7000'].map(toUnit), await currentTime(), { - from: oracle, - }); + await updateAggregatorRates(exchangeRates, [sBTC], [toUnit(7000)]); amountToLiquidate = await cerc20.liquidationAmount(id); @@ -244,9 +232,7 @@ contract('CollateralUtil', async accounts => { }); it('when we start at 200%, a price shock of 40% in the collateral requires 75% of the loan to be liquidated', async () => { - await exchangeRates.updateRates([sBTC], ['6000'].map(toUnit), await currentTime(), { - from: oracle, - }); + await updateAggregatorRates(exchangeRates, [sBTC], [toUnit(6000)]); amountToLiquidate = await cerc20.liquidationAmount(id); @@ -254,10 +240,7 @@ contract('CollateralUtil', async accounts => { }); it('when we start at 200%, a price shock of 45% in the collateral requires 100% of the loan to be liquidated', async () => { - await exchangeRates.updateRates([sBTC], ['5500'].map(toUnit), await currentTime(), { - from: oracle, - }); - + await updateAggregatorRates(exchangeRates, [sBTC], [toUnit(5500)]); amountToLiquidate = await cerc20.liquidationAmount(id); assert.bnClose(amountToLiquidate, toUnit(5000), '10000'); @@ -279,9 +262,7 @@ contract('CollateralUtil', async accounts => { }); it('when BTC is @ $20000 and we are liquidating 1000 sUSD, then redeem 0.055 BTC', async () => { - await exchangeRates.updateRates([sBTC], ['20000'].map(toUnit), await currentTime(), { - from: oracle, - }); + await updateAggregatorRates(exchangeRates, [sBTC], [toUnit(20000)]); collateralRedeemed = await util.collateralRedeemed(sUSD, oneThousandsUSD, collateralKey); @@ -289,9 +270,7 @@ contract('CollateralUtil', async accounts => { }); it('when BTC is @ $7000 and we are liquidating 2500 sUSD, then redeem 0.36666 ETH', async () => { - await exchangeRates.updateRates([sBTC], ['7000'].map(toUnit), await currentTime(), { - from: oracle, - }); + await updateAggregatorRates(exchangeRates, [sBTC], [toUnit(7000)]); collateralRedeemed = await util.collateralRedeemed(sUSD, toUnit(2500), collateralKey); @@ -303,9 +282,7 @@ contract('CollateralUtil', async accounts => { assert.bnEqual(collateralRedeemed, toUnit(1.1)); - await exchangeRates.updateRates([sBTC], ['1000'].map(toUnit), await currentTime(), { - from: oracle, - }); + await updateAggregatorRates(exchangeRates, [sBTC], [toUnit(1000)]); collateralRedeemed = await util.collateralRedeemed(sBTC, toUnit(1), collateralKey); diff --git a/test/contracts/DebtCache.js b/test/contracts/DebtCache.js index 003608713b..d1b69db97e 100644 --- a/test/contracts/DebtCache.js +++ b/test/contracts/DebtCache.js @@ -15,6 +15,8 @@ const { onlyGivenAddressCanInvoke, ensureOnlyExpectedMutativeFunctions, setStatus, + setupPriceAggregators, + updateAggregatorRates, } = require('./helpers'); const { @@ -35,7 +37,7 @@ contract('DebtCache', async accounts => { ].map(toBytes32); const synthKeys = [sUSD, sAUD, sEUR, sETH, SNX]; - const [deployerAccount, owner, oracle, account1, account2] = accounts; + const [deployerAccount, owner, , account1, account2] = accounts; const oneETH = toUnit('1.0'); const twoETH = toUnit('2.0'); @@ -49,7 +51,6 @@ contract('DebtCache', async accounts => { sETHContract, sEURContract, sAUDContract, - timestamp, debtCache, issuer, synths, @@ -279,23 +280,21 @@ contract('DebtCache', async accounts => { 'WETH', ], })); + + await setupPriceAggregators(exchangeRates, owner, [sAUD, sEUR, sETH, ETH, iETH]); }); addSnapshotBeforeRestoreAfterEach(); beforeEach(async () => { for (let i = 0; i < EXCHANGE_DYNAMIC_FEE_ROUNDS; i++) { - timestamp = await currentTime(); - - await exchangeRates.updateRates( + await updateAggregatorRates( + exchangeRates, [sAUD, sEUR, SNX, sETH, ETH, iETH], - ['0.5', '1.25', '10', '200', '200', '200'].map(toUnit), - timestamp, - { from: oracle } + ['0.5', '1.25', '10', '200', '200', '200'].map(toUnit) ); } - // set a 0.3% default exchange fee rate const exchangeFeeRate = toUnit('0.003'); await setExchangeFeeRateForSynths({ owner, @@ -397,11 +396,10 @@ contract('DebtCache', async accounts => { // set default issuance ratio of 0.2 await systemSettings.setIssuanceRatio(toUnit('0.2'), { from: owner }); // set up initial prices - await exchangeRates.updateRates( + await updateAggregatorRates( + exchangeRates, [sAUD, sEUR, sETH], - ['0.5', '2', '100'].map(toUnit), - await currentTime(), - { from: oracle } + ['0.5', '2', '100'].map(toUnit) ); await debtCache.takeDebtSnapshot(); @@ -453,9 +451,7 @@ contract('DebtCache', async accounts => { assert.bnEqual(result[0], toUnit(550)); assert.isFalse(result[1]); - await exchangeRates.updateRates([sAUD, sEUR], ['1', '3'].map(toUnit), await currentTime(), { - from: oracle, - }); + await updateAggregatorRates(exchangeRates, [sAUD, sEUR], ['1', '3'].map(toUnit)); await debtCache.takeDebtSnapshot(); assert.bnEqual((await debtCache.cacheInfo()).debt, toUnit(700)); result = await debtCache.currentDebt(); @@ -477,13 +473,10 @@ contract('DebtCache', async accounts => { }); it('updates the cached values for all individual synths', async () => { - await exchangeRates.updateRates( + await updateAggregatorRates( + exchangeRates, [sAUD, sEUR, sETH], - ['1', '3', '200'].map(toUnit), - await currentTime(), - { - from: oracle, - } + ['1', '3', '200'].map(toUnit) ); await debtCache.takeDebtSnapshot(); let debts = await debtCache.currentSynthDebts([sUSD, sEUR, sAUD, sETH]); @@ -511,11 +504,10 @@ contract('DebtCache', async accounts => { assert.isTrue((await debtCache.cacheInfo()).isInvalid); // Revalidate the cache once rates are no longer stale - await exchangeRates.updateRates( + await updateAggregatorRates( + exchangeRates, [sAUD, sEUR, SNX, sETH, ETH, iETH], - ['0.5', '2', '100', '200', '200', '200'].map(toUnit), - await currentTime(), - { from: oracle } + ['0.5', '2', '100', '200', '200', '200'].map(toUnit) ); const tx2 = await debtCache.takeDebtSnapshot(); assert.isFalse((await debtCache.cacheInfo()).isInvalid); @@ -532,11 +524,10 @@ contract('DebtCache', async accounts => { await fastForward(snapshotStaleTime + 10); // ensure no actual rates are stale. - await exchangeRates.updateRates( + await updateAggregatorRates( + exchangeRates, [sAUD, sEUR, sETH, SNX], - ['0.5', '2', '100', '1'].map(toUnit), - await currentTime(), - { from: oracle } + ['0.5', '2', '100', '1'].map(toUnit) ); const info = await debtCache.cacheInfo(); @@ -600,13 +591,11 @@ contract('DebtCache', async accounts => { const snapshotStaleTime = await systemSettings.debtSnapshotStaleTime(); await fastForward(snapshotStaleTime + 10); // ensure no actual rates are stale. - await exchangeRates.updateRates( + await updateAggregatorRates( + exchangeRates, [sAUD, sEUR, sETH, SNX], - ['0.5', '2', '100', '1'].map(toUnit), - await currentTime(), - { from: oracle } + ['0.5', '2', '100', '1'].map(toUnit) ); - await assert.revert( synthetix.issueSynths(toUnit('10'), { from: account1 }), 'A synth or SNX rate is invalid' @@ -674,13 +663,10 @@ contract('DebtCache', async accounts => { it('allows resynchronisation of subsets of synths', async () => { await debtCache.takeDebtSnapshot(); - await exchangeRates.updateRates( + await updateAggregatorRates( + exchangeRates, [sAUD, sEUR, sETH], - ['1', '3', '200'].map(toUnit), - await currentTime(), - { - from: oracle, - } + ['1', '3', '200'].map(toUnit) ); // First try a single currency, ensuring that the others have not been altered. @@ -714,11 +700,10 @@ contract('DebtCache', async accounts => { assert.isTrue((await debtCache.cacheInfo()).isInvalid); // But even if we update all rates, we can't revalidate the cache using the partial update function - await exchangeRates.updateRates( + await updateAggregatorRates( + exchangeRates, [sAUD, sEUR, sETH], - ['0.5', '2', '100'].map(toUnit), - await currentTime(), - { from: oracle } + ['0.5', '2', '100'].map(toUnit) ); const tx2 = await debtCache.updateCachedSynthDebts([sAUD, sEUR, sETH]); assert.isTrue((await debtCache.cacheInfo()).isInvalid); @@ -729,13 +714,10 @@ contract('DebtCache', async accounts => { it('properly emits events', async () => { await debtCache.takeDebtSnapshot(); - await exchangeRates.updateRates( + await updateAggregatorRates( + exchangeRates, [sAUD, sEUR, sETH], - ['1', '3', '200'].map(toUnit), - await currentTime(), - { - from: oracle, - } + ['1', '3', '200'].map(toUnit) ); const tx = await debtCache.updateCachedSynthDebts([sAUD]); @@ -1006,9 +988,7 @@ contract('DebtCache', async accounts => { const debts = await debtCache.cachedSynthDebts([sAUD, sEUR]); - await exchangeRates.updateRates([sAUD, sEUR], ['1', '1'].map(toUnit), await currentTime(), { - from: oracle, - }); + await updateAggregatorRates(exchangeRates, [sAUD, sEUR], ['1', '1'].map(toUnit)); await synthetix.exchange(sEUR, toUnit(10), sAUD, { from: account1 }); const postDebts = await debtCache.cachedSynthDebts([sAUD, sEUR]); @@ -1043,9 +1023,7 @@ contract('DebtCache', async accounts => { // set a high price deviation threshold factor to be sure it doesn't trigger here await systemSettings.setPriceDeviationThresholdFactor(toUnit('99'), { from: owner }); - await exchangeRates.updateRates([sAUD, sEUR], ['2', '1'].map(toUnit), await currentTime(), { - from: oracle, - }); + await updateAggregatorRates(exchangeRates, [sAUD, sEUR], ['2', '1'].map(toUnit)); await fastForward(100); diff --git a/test/contracts/Depot.js b/test/contracts/Depot.js index 1fafcec800..00ce2420c8 100644 --- a/test/contracts/Depot.js +++ b/test/contracts/Depot.js @@ -5,7 +5,6 @@ const { contract, web3 } = require('hardhat'); const { assert, addSnapshotBeforeRestoreAfterEach } = require('./common'); const { - currentTime, fastForward, getEthBalance, toUnit, @@ -17,6 +16,8 @@ const { onlyGivenAddressCanInvoke, ensureOnlyExpectedMutativeFunctions, setStatus, + setupPriceAggregators, + updateAggregatorRates, } = require('./helpers'); const { mockToken, setupAllContracts } = require('./setup'); @@ -26,7 +27,7 @@ const { toBytes32 } = require('../..'); contract('Depot', async accounts => { let synthetix, synth, depot, addressResolver, systemStatus, exchangeRates, ethRate, snxRate; - const [, owner, oracle, fundsWallet, address1, address2, address3] = accounts; + const [, owner, , fundsWallet, address1, address2, address3] = accounts; const [SNX, ETH] = ['SNX', 'ETH'].map(toBytes32); @@ -71,19 +72,16 @@ contract('Depot', async accounts => { 'Issuer', ], })); + + await setupPriceAggregators(exchangeRates, owner, [ETH]); }); addSnapshotBeforeRestoreAfterEach(); beforeEach(async () => { - const timestamp = await currentTime(); - snxRate = toUnit('0.1'); ethRate = toUnit('172'); - - await exchangeRates.updateRates([SNX, ETH], [snxRate, ethRate], timestamp, { - from: oracle, - }); + await updateAggregatorRates(exchangeRates, [SNX, ETH], [snxRate, ethRate]); }); it('should set constructor params on deployment', async () => { @@ -778,10 +776,7 @@ contract('Depot', async accounts => { ); }); it('when the purchaser supplies a rate and the rate is changed in by the oracle', async () => { - const timestamp = await currentTime(); - await exchangeRates.updateRates([SNX, ETH], ['0.1', '134'].map(toUnit), timestamp, { - from: oracle, - }); + await updateAggregatorRates(exchangeRates, [SNX, ETH], ['0.1', '134'].map(toUnit)); await assert.revert( depot.exchangeEtherForSynthsAtRate(ethRate, payload), 'Guaranteed rate would not be received' @@ -830,10 +825,7 @@ contract('Depot', async accounts => { ); }); it('when the purchaser supplies a rate and the rate is changed in by the oracle', async () => { - const timestamp = await currentTime(); - await exchangeRates.updateRates([SNX, ETH], ['0.1', '134'].map(toUnit), timestamp, { - from: oracle, - }); + await updateAggregatorRates(exchangeRates, [SNX, ETH], ['0.1', '134'].map(toUnit)); await assert.revert( depot.exchangeEtherForSNXAtRate(ethRate, snxRate, ethToSendFromPurchaser), 'Guaranteed ether rate would not be received' @@ -894,10 +886,7 @@ contract('Depot', async accounts => { ); }); it('when the purchaser supplies a rate and the rate is changed in by the oracle', async () => { - const timestamp = await currentTime(); - await exchangeRates.updateRates([SNX], ['0.05'].map(toUnit), timestamp, { - from: oracle, - }); + await updateAggregatorRates(exchangeRates, [SNX], ['0.05'].map(toUnit)); await assert.revert( depot.exchangeSynthsForSNXAtRate(synthsToSend, snxRate, fromPurchaser), 'Guaranteed rate would not be received' diff --git a/test/contracts/EtherWrapper.js b/test/contracts/EtherWrapper.js index 70e1d612f0..c5b6b105a9 100644 --- a/test/contracts/EtherWrapper.js +++ b/test/contracts/EtherWrapper.js @@ -4,12 +4,14 @@ const { contract } = require('hardhat'); const { assert, addSnapshotBeforeRestoreAfterEach } = require('./common'); -const { currentTime, toUnit, multiplyDecimal } = require('../utils')(); +const { toUnit, multiplyDecimal } = require('../utils')(); const { ensureOnlyExpectedMutativeFunctions, getDecodedLogs, decodedEventEqual, + setupPriceAggregators, + updateAggregatorRates, } = require('./helpers'); const { setupAllContracts } = require('./setup'); @@ -23,7 +25,7 @@ contract('EtherWrapper', async accounts => { const ONE = toBN('1'); - const [, owner, oracle, , account1] = accounts; + const [, owner, , , account1] = accounts; let systemSettings, feePool, @@ -35,8 +37,7 @@ contract('EtherWrapper', async accounts => { sUSDSynth, sETHSynth, etherWrapper, - weth, - timestamp; + weth; const calculateETHToUSD = async feesInETH => { // Ask the Depot how many sUSD I will get for this ETH @@ -93,12 +94,10 @@ contract('EtherWrapper', async accounts => { await systemSettings.setEtherWrapperBurnFeeRate(toUnit('0.005'), { from: owner }); FEE_ADDRESS = await feePool.FEE_ADDRESS(); - timestamp = await currentTime(); + await setupPriceAggregators(exchangeRates, owner, [sETH, ETH]); // Depot requires ETH rates - await exchangeRates.updateRates([sETH, ETH], ['1500', '1500'].map(toUnit), timestamp, { - from: oracle, - }); + await updateAggregatorRates(exchangeRates, [sETH, ETH], ['1500', '1500'].map(toUnit)); }); addSnapshotBeforeRestoreAfterEach(); diff --git a/test/contracts/ExchangeRates.js b/test/contracts/ExchangeRates.js index 87ca3afd35..c5a78a4cfb 100644 --- a/test/contracts/ExchangeRates.js +++ b/test/contracts/ExchangeRates.js @@ -17,9 +17,11 @@ const { ensureOnlyExpectedMutativeFunctions, onlyGivenAddressCanInvoke, convertToDecimals, + setupPriceAggregators, + updateAggregatorRates, } = require('./helpers'); -const { setupContract, setupAllContracts } = require('./setup'); +const { setupAllContracts } = require('./setup'); const { toBytes32, @@ -31,33 +33,9 @@ const { toBN } = require('web3-utils'); const MockAggregator = artifacts.require('MockAggregatorV2V3'); -const getRandomCurrencyKey = () => - Math.random() - .toString(36) - .substring(2, 6) - .toUpperCase(); - -const createRandomKeysAndRates = quantity => { - const uniqueCurrencyKeys = {}; - for (let i = 0; i < quantity; i++) { - const rate = Math.random() * 100; - const key = toBytes32(getRandomCurrencyKey()); - uniqueCurrencyKeys[key] = web3.utils.toWei(rate.toFixed(18), 'ether'); - } - - const rates = []; - const currencyKeys = []; - Object.entries(uniqueCurrencyKeys).forEach(([key, rate]) => { - currencyKeys.push(key); - rates.push(rate); - }); - - return { currencyKeys, rates }; -}; - contract('Exchange Rates', async accounts => { const [deployerAccount, owner, oracle, dexPriceAggregator, accountOne, accountTwo] = accounts; - const [SNX, sJPY, sETH, sXTZ, sBNB, sUSD, sEUR, sAUD, fastGasPrice] = [ + const [SNX, sJPY, sETH, sXTZ, sBNB, sUSD, sEUR, sAUD, GOLD, fastGasPrice] = [ 'SNX', 'sJPY', 'sETH', @@ -66,6 +44,7 @@ contract('Exchange Rates', async accounts => { 'sUSD', 'sEUR', 'sAUD', + 'GOLD', 'fastGasPrice', ].map(toBytes32); let instance; @@ -73,20 +52,10 @@ contract('Exchange Rates', async accounts => { let aggregatorJPY; let aggregatorXTZ; let aggregatorFastGasPrice; - let initialTime; - let timeSent; - let resolver; let mockFlagsInterface; const itIncludesCorrectMutativeFunctions = contract => { - const baseFunctions = [ - 'addAggregator', - 'deleteRate', - 'removeAggregator', - 'setOracle', - 'updateRates', - 'mutativeEffectiveValueAndRatesAtRound', - ]; + const baseFunctions = ['addAggregator', 'removeAggregator']; const withDexPricingFunctions = baseFunctions.concat(['setDexPriceAggregator']); it('only expected functions should be mutative', () => { @@ -103,497 +72,11 @@ contract('Exchange Rates', async accounts => { describe('constructor', () => { it('should set constructor params on deployment', async () => { assert.equal(await instance.owner(), owner); - assert.equal(await instance.oracle(), oracle); - - assert.etherEqual(await instance.rateForCurrency(sUSD), '1'); - assert.etherEqual(await instance.rateForCurrency(SNX), '0.2'); - - // Ensure that when the rate isn't found, 0 is returned as the exchange rate. - assert.etherEqual(await instance.rateForCurrency(toBytes32('OTHER')), '0'); - - const lastUpdatedTimeSUSD = await instance.lastRateUpdateTimes.call(sUSD); - assert.isAtLeast(lastUpdatedTimeSUSD.toNumber(), initialTime); - - const lastUpdatedTimeOTHER = await instance.lastRateUpdateTimes.call(toBytes32('OTHER')); - assert.equal(lastUpdatedTimeOTHER.toNumber(), 0); - - const lastUpdatedTimeSNX = await instance.lastRateUpdateTimes.call(SNX); - assert.isAtLeast(lastUpdatedTimeSNX.toNumber(), initialTime); - - const sUSDRate = await instance.rateForCurrency(sUSD); - assert.bnEqual(sUSDRate, toUnit('1')); - }); - - it('two different currencies in same array should mean that the second one overrides', async () => { - const creationTime = await currentTime(); - const firstAmount = '4.33'; - const secondAmount = firstAmount + 10; - const instance = await setupContract({ - accounts, - contract, - args: [ - owner, - oracle, - resolver.address, - [toBytes32('CARTER'), toBytes32('CARTOON')], - [web3.utils.toWei(firstAmount, 'ether'), web3.utils.toWei(secondAmount, 'ether')], - ], - }); - - assert.etherEqual(await instance.rateForCurrency(toBytes32('CARTER')), firstAmount); - assert.etherEqual(await instance.rateForCurrency(toBytes32('CARTOON')), secondAmount); - - const lastUpdatedTime = await instance.lastRateUpdateTimes.call(toBytes32('CARTER')); - assert.isAtLeast(lastUpdatedTime.toNumber(), creationTime); - }); - - it('should revert when number of currency keys > new rates length on create', async () => { - await assert.revert( - setupContract({ - accounts, - contract, - args: [ - owner, - oracle, - resolver.address, - [SNX, toBytes32('GOLD')], - [web3.utils.toWei('0.2', 'ether')], - ], - }), - 'Currency key length and rate length must match' - ); - }); - - it('should limit to 32 bytes if currency key > 32 bytes on create', async () => { - const creationTime = await currentTime(); - const amount = '4.33'; - const instance = await setupContract({ - accounts, - contract, - args: [ - owner, - oracle, - resolver.address, - [toBytes32('ABCDEFGHIJKLMNOPQRSTUVXYZ1234567')], - [web3.utils.toWei(amount, 'ether')], - ], - }); - - assert.etherEqual( - await instance.rateForCurrency(toBytes32('ABCDEFGHIJKLMNOPQRSTUVXYZ1234567')), - amount - ); - assert.etherNotEqual( - await instance.rateForCurrency(toBytes32('ABCDEFGHIJKLMNOPQRSTUVXYZ123456')), - amount - ); - - const lastUpdatedTime = await instance.lastRateUpdateTimes.call( - toBytes32('ABCDEFGHIJKLMNOPQRSTUVXYZ1234567') - ); - assert.isAtLeast(lastUpdatedTime.toNumber(), creationTime); - }); - - it("shouldn't be able to set exchange rate to 0 on create", async () => { - await assert.revert( - setupContract({ - accounts, - contract, - args: [owner, oracle, resolver.address, [SNX], ['0']], - }), - 'Zero is not a valid rate, please call deleteRate instead' - ); - }); - - it('should be able to handle lots of currencies on creation', async () => { - const creationTime = await currentTime(); - const numberOfCurrencies = 80; - const { currencyKeys, rates } = createRandomKeysAndRates(numberOfCurrencies); - - const instance = await setupContract({ - accounts, - contract, - args: [owner, oracle, resolver.address, currencyKeys, rates], - }); - - for (let i = 0; i < currencyKeys.length; i++) { - assert.bnEqual(await instance.rateForCurrency(currencyKeys[i]), rates[i]); - const lastUpdatedTime = await instance.lastRateUpdateTimes.call(currencyKeys[i]); - assert.isAtLeast(lastUpdatedTime.toNumber(), creationTime); - } - }); - }); - }; - - // Oracle rates - - const itUpdatesRates = () => { - describe('updateRates()', () => { - it('should be able to update rates of only one currency without affecting other rates', async () => { - await fastForward(1); - - await instance.updateRates( - [toBytes32('lABC'), toBytes32('lDEF'), toBytes32('lGHI')], - [ - web3.utils.toWei('1.3', 'ether'), - web3.utils.toWei('2.4', 'ether'), - web3.utils.toWei('3.5', 'ether'), - ], - timeSent, - { from: oracle } - ); - - await fastForward(10); - const updatedTime = timeSent + 10; - - const updatedRate = '64.33'; - await instance.updateRates( - [toBytes32('lABC')], - [web3.utils.toWei(updatedRate, 'ether')], - updatedTime, - { from: oracle } - ); - - const updatedTimelDEF = await instance.lastRateUpdateTimes.call(toBytes32('lDEF')); - const updatedTimelGHI = await instance.lastRateUpdateTimes.call(toBytes32('lGHI')); - - assert.etherEqual(await instance.rateForCurrency(toBytes32('lABC')), updatedRate); - assert.etherEqual(await instance.rateForCurrency(toBytes32('lDEF')), '2.4'); - assert.etherEqual(await instance.rateForCurrency(toBytes32('lGHI')), '3.5'); - - const lastUpdatedTimeLABC = await instance.lastRateUpdateTimes.call(toBytes32('lABC')); - assert.equal(lastUpdatedTimeLABC.toNumber(), updatedTime); - const lastUpdatedTimeLDEF = await instance.lastRateUpdateTimes.call(toBytes32('lDEF')); - assert.equal(lastUpdatedTimeLDEF.toNumber(), updatedTimelDEF.toNumber()); - const lastUpdatedTimeLGHI = await instance.lastRateUpdateTimes.call(toBytes32('lGHI')); - assert.equal(lastUpdatedTimeLGHI.toNumber(), updatedTimelGHI.toNumber()); - }); - - it('should be able to update rates of all currencies', async () => { - await fastForward(1); - - await instance.updateRates( - [toBytes32('lABC'), toBytes32('lDEF'), toBytes32('lGHI')], - [ - web3.utils.toWei('1.3', 'ether'), - web3.utils.toWei('2.4', 'ether'), - web3.utils.toWei('3.5', 'ether'), - ], - timeSent, - { from: oracle } - ); - - await fastForward(5); - const updatedTime = timeSent + 5; - - const updatedRate1 = '64.33'; - const updatedRate2 = '2.54'; - const updatedRate3 = '10.99'; - await instance.updateRates( - [toBytes32('lABC'), toBytes32('lDEF'), toBytes32('lGHI')], - [ - web3.utils.toWei(updatedRate1, 'ether'), - web3.utils.toWei(updatedRate2, 'ether'), - web3.utils.toWei(updatedRate3, 'ether'), - ], - updatedTime, - { from: oracle } - ); - - assert.etherEqual(await instance.rateForCurrency(toBytes32('lABC')), updatedRate1); - assert.etherEqual(await instance.rateForCurrency(toBytes32('lDEF')), updatedRate2); - assert.etherEqual(await instance.rateForCurrency(toBytes32('lGHI')), updatedRate3); - - const lastUpdatedTimeLABC = await instance.lastRateUpdateTimes.call(toBytes32('lABC')); - assert.equal(lastUpdatedTimeLABC.toNumber(), updatedTime); - const lastUpdatedTimeLDEF = await instance.lastRateUpdateTimes.call(toBytes32('lDEF')); - assert.equal(lastUpdatedTimeLDEF.toNumber(), updatedTime); - const lastUpdatedTimeLGHI = await instance.lastRateUpdateTimes.call(toBytes32('lGHI')); - assert.equal(lastUpdatedTimeLGHI.toNumber(), updatedTime); - }); - - it('should revert when trying to set sUSD price', async () => { - await fastForward(1); - - await assert.revert( - instance.updateRates([sUSD], [web3.utils.toWei('1.0', 'ether')], timeSent, { - from: oracle, - }), - "Rate of sUSD cannot be updated, it's always UNIT" - ); - }); - - it('should emit RatesUpdated event when rate updated', async () => { - const rates = [ - web3.utils.toWei('1.3', 'ether'), - web3.utils.toWei('2.4', 'ether'), - web3.utils.toWei('3.5', 'ether'), - ]; - - const keys = ['lABC', 'lDEF', 'lGHI']; - const currencyKeys = keys.map(toBytes32); - const txn = await instance.updateRates(currencyKeys, rates, await currentTime(), { - from: oracle, - }); - - assert.eventEqual(txn, 'RatesUpdated', { - currencyKeys, - newRates: rates, - }); - }); - - it('should be able to handle lots of currency updates', async () => { - const numberOfCurrencies = 150; - const { currencyKeys, rates } = createRandomKeysAndRates(numberOfCurrencies); - - const updatedTime = await currentTime(); - await instance.updateRates(currencyKeys, rates, updatedTime, { from: oracle }); - - for (let i = 0; i < currencyKeys.length; i++) { - assert.equal(await instance.rateForCurrency(currencyKeys[i]), rates[i]); - const lastUpdatedTime = await instance.lastRateUpdateTimes.call(currencyKeys[i]); - assert.equal(lastUpdatedTime.toNumber(), updatedTime); - } - }); - - it('should revert when currency keys length != new rates length on update', async () => { - await assert.revert( - instance.updateRates( - [sUSD, SNX, toBytes32('GOLD')], - [web3.utils.toWei('1', 'ether'), web3.utils.toWei('0.2', 'ether')], - await currentTime(), - { from: oracle } - ), - 'Currency key array length must match rates array length' - ); - }); - - it('should not be able to set exchange rate to 0 on update', async () => { - await assert.revert( - instance.updateRates( - [toBytes32('ZERO')], - [web3.utils.toWei('0', 'ether')], - await currentTime(), - { from: oracle } - ), - 'Zero is not a valid rate, please call deleteRate instead' - ); - }); - - it('only oracle can update exchange rates', async () => { - await onlyGivenAddressCanInvoke({ - fnc: instance.updateRates, - args: [ - [toBytes32('GOLD'), toBytes32('FOOL')], - [web3.utils.toWei('10', 'ether'), web3.utils.toWei('0.9', 'ether')], - timeSent, - ], - address: oracle, - accounts, - skipPassCheck: true, - reason: 'Only the oracle can perform this action', - }); - - assert.etherNotEqual(await instance.rateForCurrency(toBytes32('GOLD')), '10'); - assert.etherNotEqual(await instance.rateForCurrency(toBytes32('FOOL')), '0.9'); - - const updatedTime = await currentTime(); - - await instance.updateRates( - [toBytes32('GOLD'), toBytes32('FOOL')], - [web3.utils.toWei('10', 'ether'), web3.utils.toWei('0.9', 'ether')], - updatedTime, - { from: oracle } - ); - assert.etherEqual(await instance.rateForCurrency(toBytes32('GOLD')), '10'); - assert.etherEqual(await instance.rateForCurrency(toBytes32('FOOL')), '0.9'); - - const lastUpdatedTimeGOLD = await instance.lastRateUpdateTimes.call(toBytes32('GOLD')); - assert.equal(lastUpdatedTimeGOLD.toNumber(), updatedTime); - const lastUpdatedTimeFOOL = await instance.lastRateUpdateTimes.call(toBytes32('FOOL')); - assert.equal(lastUpdatedTimeFOOL.toNumber(), updatedTime); - }); - - it('should not be able to update rates if they are too far in the future', async () => { - const timeTooFarInFuture = (await currentTime()) + 10 * 61; - await assert.revert( - instance.updateRates( - [toBytes32('GOLD')], - [web3.utils.toWei('1', 'ether')], - timeTooFarInFuture, - { from: oracle } - ), - 'Time is too far into the future' - ); - }); - }); - }; - - const itSetsOracle = () => { - describe('setOracle()', () => { - it("only the owner should be able to change the oracle's address", async () => { - await onlyGivenAddressCanInvoke({ - fnc: instance.setOracle, - args: [oracle], - address: owner, - accounts, - skipPassCheck: true, - }); - - await instance.setOracle(accountOne, { from: owner }); - - assert.equal(await instance.oracle.call(), accountOne); - assert.notEqual(await instance.oracle.call(), oracle); - }); - - it('should emit event on successful oracle address update', async () => { - // Ensure oracle is set to oracle address originally - await instance.setOracle(oracle, { from: owner }); - assert.equal(await instance.oracle.call(), oracle); - - const txn = await instance.setOracle(accountOne, { from: owner }); - assert.eventEqual(txn, 'OracleUpdated', { - newOracle: accountOne, - }); - }); - }); - }; - - const itDeletesRates = () => { - describe('deleteRate()', () => { - it('should be able to remove specific rate', async () => { - const foolsRate = '0.002'; - const encodedRateGOLD = toBytes32('GOLD'); - - await instance.updateRates( - [encodedRateGOLD, toBytes32('FOOL')], - [web3.utils.toWei('10.123', 'ether'), web3.utils.toWei(foolsRate, 'ether')], - timeSent, - { from: oracle } - ); - - const beforeRate = await instance.rateForCurrency(encodedRateGOLD); - const beforeRateUpdatedTime = await instance.lastRateUpdateTimes.call(encodedRateGOLD); - - await instance.deleteRate(encodedRateGOLD, { from: oracle }); - - const afterRate = await instance.rateForCurrency(encodedRateGOLD); - const afterRateUpdatedTime = await instance.lastRateUpdateTimes.call(encodedRateGOLD); - assert.notEqual(afterRate, beforeRate); - assert.equal(afterRate, '0'); - assert.notEqual(afterRateUpdatedTime, beforeRateUpdatedTime); - assert.equal(afterRateUpdatedTime, '0'); - - // Other rates are unaffected - assert.etherEqual(await instance.rateForCurrency(toBytes32('FOOL')), foolsRate); - }); - - it('only oracle can delete a rate', async () => { - // Assume that the contract is already set up with a valid oracle account called 'oracle' - - const encodedRateName = toBytes32('COOL'); - await instance.updateRates( - [encodedRateName], - [web3.utils.toWei('10.123', 'ether')], - await currentTime(), - { from: oracle } - ); - - await onlyGivenAddressCanInvoke({ - fnc: instance.deleteRate, - args: [encodedRateName], - accounts, - address: oracle, - reason: 'Only the oracle can perform this action', - }); - }); - - it("deleting rate that doesn't exist causes revert", async () => { - // This key shouldn't exist but let's do the best we can to ensure that it doesn't - const encodedCurrencyKey = toBytes32('7NEQ'); - const currentRate = await instance.rateForCurrency(encodedCurrencyKey); - if (currentRate > 0) { - await instance.deleteRate(encodedCurrencyKey, { from: oracle }); - } - - // Ensure rate deletion attempt results in revert - await assert.revert( - instance.deleteRate(encodedCurrencyKey, { from: oracle }), - 'Rate is zero' - ); - assert.etherEqual(await instance.rateForCurrency(encodedCurrencyKey), '0'); }); - it('should emit RateDeleted event when rate deleted', async () => { - const updatedTime = await currentTime(); - const rate = 'GOLD'; - const encodedRate = toBytes32(rate); - await instance.updateRates( - [encodedRate], - [web3.utils.toWei('10.123', 'ether')], - updatedTime, - { - from: oracle, - } - ); - - const txn = await instance.deleteRate(encodedRate, { from: oracle }); - assert.eventEqual(txn, 'RateDeleted', { currencyKey: encodedRate }); - }); - }); - }; - - const itReturnsRates = () => { - describe('getting rates', () => { - it('should be able to get exchange rate with key', async () => { - const updatedTime = await currentTime(); - const encodedRate = toBytes32('GOLD'); - const rateValueEncodedStr = web3.utils.toWei('10.123', 'ether'); - await instance.updateRates([encodedRate], [rateValueEncodedStr], updatedTime, { - from: oracle, - }); - - const rate = await instance.rateForCurrency(encodedRate); - assert.equal(rate, rateValueEncodedStr); - }); - - it('all users should be able to get exchange rate with key', async () => { - const updatedTime = await currentTime(); - const encodedRate = toBytes32('FETC'); - const rateValueEncodedStr = web3.utils.toWei('910.6661293879', 'ether'); - await instance.updateRates([encodedRate], [rateValueEncodedStr], updatedTime, { - from: oracle, - }); - - await instance.rateForCurrency(encodedRate, { from: accountOne }); - await instance.rateForCurrency(encodedRate, { from: accountTwo }); - await instance.rateForCurrency(encodedRate, { from: oracle }); - await instance.rateForCurrency(encodedRate, { from: owner }); - await instance.rateForCurrency(encodedRate, { from: deployerAccount }); - }); - - it('Fetching non-existent rate returns 0', async () => { - const encodedRateKey = toBytes32('GOLD'); - const currentRate = await instance.rateForCurrency(encodedRateKey); - if (currentRate > 0) { - await instance.deleteRate(encodedRateKey, { from: oracle }); - } - - const rate = await instance.rateForCurrency(encodedRateKey); - assert.equal(rate.toString(), '0'); - }); - - it('should be able to get the latest exchange rate and updated time', async () => { - const updatedTime = await currentTime(); - const encodedRate = toBytes32('GOLD'); - const rateValueEncodedStr = web3.utils.toWei('10.123', 'ether'); - await instance.updateRates([encodedRate], [rateValueEncodedStr], updatedTime, { - from: oracle, - }); - - const rateAndTime = await instance.rateAndUpdatedTime(encodedRate); - assert.equal(rateAndTime.rate, rateValueEncodedStr); - assert.bnEqual(rateAndTime.time, updatedTime); + it('returns correct values for sUSD after deployment ', async () => { + assert.bnEqual(await instance.rateForCurrency(sUSD), toUnit('1')); + assert.equal(await instance.lastRateUpdateTimes(sUSD), 0); }); }); }; @@ -621,62 +104,18 @@ contract('Exchange Rates', async accounts => { assert.equal(rateIsStale, false); }); - it('check if a single rate is stale', async () => { - // Set up rates for test - await systemSettings.setRateStalePeriod(30, { from: owner }); - const updatedTime = await currentTime(); - await instance.updateRates( - [toBytes32('ABC')], - [web3.utils.toWei('2', 'ether')], - updatedTime, - { - from: oracle, - } - ); - await fastForward(31); - - const rateIsStale = await instance.rateIsStale(toBytes32('ABC')); - assert.equal(rateIsStale, true); - }); - - it('check if a single rate is not stale', async () => { - // Set up rates for test - await systemSettings.setRateStalePeriod(30, { from: owner }); - const updatedTime = await currentTime(); - await instance.updateRates( - [toBytes32('ABC')], - [web3.utils.toWei('2', 'ether')], - updatedTime, - { - from: oracle, - } - ); - await fastForward(28); - - const rateIsStale = await instance.rateIsStale(toBytes32('ABC')); - assert.equal(rateIsStale, false); - }); - - it('ensure rate is considered stale if not set', async () => { + it('ensure stale if not set', async () => { // Set up rates for test await systemSettings.setRateStalePeriod(30, { from: owner }); - const encodedRateKey = toBytes32('GOLD'); - const currentRate = await instance.rateForCurrency(encodedRateKey); - if (currentRate > 0) { - await instance.deleteRate(encodedRateKey, { from: oracle }); - } - - const rateIsStale = await instance.rateIsStale(encodedRateKey); - assert.equal(rateIsStale, true); + assert.equal(await instance.rateIsStale(toBytes32('GOLD')), true); }); it('make sure anyone can check if rate is stale', async () => { - const rateKey = toBytes32('ABC'); - await instance.rateIsStale(rateKey, { from: oracle }); - await instance.rateIsStale(rateKey, { from: owner }); - await instance.rateIsStale(rateKey, { from: deployerAccount }); - await instance.rateIsStale(rateKey, { from: accountOne }); - await instance.rateIsStale(rateKey, { from: accountTwo }); + await instance.rateIsStale(sUSD, { from: oracle }); + await instance.rateIsStale(sUSD, { from: owner }); + await instance.rateIsStale(sUSD, { from: deployerAccount }); + await instance.rateIsStale(sUSD, { from: accountOne }); + await instance.rateIsStale(sUSD, { from: accountTwo }); }); }); }; @@ -684,29 +123,18 @@ contract('Exchange Rates', async accounts => { const itCalculatesInvalidRates = () => { describe('anyRateIsInvalid()', () => { describe('stale scenarios', () => { - it('should never allow sUSD to go stale via anyRateIsInvalid', async () => { - const keysArray = [SNX, toBytes32('GOLD')]; - - await instance.updateRates( - keysArray, - [web3.utils.toWei('0.1', 'ether'), web3.utils.toWei('0.2', 'ether')], - await currentTime(), - { from: oracle } - ); - assert.equal(await instance.anyRateIsInvalid(keysArray), false); + it('anyRateIsInvalid conforms to rateStalePeriod', async () => { + await setupAggregators([SNX, GOLD]); - await fastForward(await instance.rateStalePeriod()); + await updateRates([SNX, GOLD], [toUnit(0.1), toUnit(0.2)]); - await instance.updateRates( - [SNX, toBytes32('GOLD')], - [web3.utils.toWei('0.1', 'ether'), web3.utils.toWei('0.2', 'ether')], - await currentTime(), - { from: oracle } - ); + assert.equal(await instance.anyRateIsInvalid([SNX, GOLD]), false); - // Even though sUSD hasn't been updated since the stale rate period has expired, - // we expect that sUSD remains "not stale" - assert.equal(await instance.anyRateIsInvalid(keysArray), false); + await fastForward(await instance.rateStalePeriod()); + assert.equal(await instance.anyRateIsInvalid([SNX, GOLD]), true); + + await updateRates([SNX, GOLD], [toUnit(0.1), toUnit(0.2)]); + assert.equal(await instance.anyRateIsInvalid([SNX, GOLD]), false); }); it('should be able to confirm no rates are stale from a subset', async () => { @@ -742,20 +170,19 @@ contract('Exchange Rates', async accounts => { web3.utils.toWei('10', 'ether'), web3.utils.toWei('11', 'ether'), ]; + + await setupAggregators([...encodedRateKeys1, ...encodedRateKeys2, ...encodedRateKeys3]); + const updatedTime1 = await currentTime(); - await instance.updateRates(encodedRateKeys1, encodedRateValues1, updatedTime1, { - from: oracle, - }); + await updateRates(encodedRateKeys1, encodedRateValues1, updatedTime1); + await fastForward(5); const updatedTime2 = await currentTime(); - await instance.updateRates(encodedRateKeys2, encodedRateValues2, updatedTime2, { - from: oracle, - }); + await updateRates(encodedRateKeys2, encodedRateValues2, updatedTime2); + await fastForward(5); const updatedTime3 = await currentTime(); - await instance.updateRates(encodedRateKeys3, encodedRateValues3, updatedTime3, { - from: oracle, - }); + await updateRates(encodedRateKeys3, encodedRateValues3, updatedTime3); await fastForward(12); const rateIsInvalid = await instance.anyRateIsInvalid([ @@ -789,21 +216,18 @@ contract('Exchange Rates', async accounts => { web3.utils.toWei('8', 'ether'), ]; + await setupAggregators([...encodedRateKeys1, ...encodedRateKeys2, ...encodedRateKeys3]); + const updatedTime2 = await currentTime(); - await instance.updateRates(encodedRateKeys2, encodedRateValues2, updatedTime2, { - from: oracle, - }); + await updateRates(encodedRateKeys2, encodedRateValues2, updatedTime2); await fastForward(20); const updatedTime1 = await currentTime(); - await instance.updateRates(encodedRateKeys1, encodedRateValues1, updatedTime1, { - from: oracle, - }); + await updateRates(encodedRateKeys1, encodedRateValues1, updatedTime1); await fastForward(15); + const updatedTime3 = await currentTime(); - await instance.updateRates(encodedRateKeys3, encodedRateValues3, updatedTime3, { - from: oracle, - }); + await updateRates(encodedRateKeys3, encodedRateValues3, updatedTime3); await fastForward(6); const rateIsInvalid = await instance.anyRateIsInvalid([ @@ -816,23 +240,18 @@ contract('Exchange Rates', async accounts => { it('should be able to confirm a single rate (from a set of 1) is stale', async () => { // Set up rates for test await systemSettings.setRateStalePeriod(40, { from: owner }); - const updatedTime = await currentTime(); - await instance.updateRates( - [toBytes32('ABC')], - [web3.utils.toWei('2', 'ether')], - updatedTime, - { - from: oracle, - } - ); + const key = toBytes32('ABC'); + await setupAggregators([key]); + await updateRates([key], [web3.utils.toWei('2', 'ether')]); await fastForward(41); - const rateIsInvalid = await instance.anyRateIsInvalid([toBytes32('ABC')]); + const rateIsInvalid = await instance.anyRateIsInvalid([key]); assert.equal(rateIsInvalid, true); }); it('make sure anyone can check if any rates are stale', async () => { const rateKey = toBytes32('ABC'); + await setupAggregators([rateKey]); await instance.anyRateIsInvalid([rateKey], { from: oracle }); await instance.anyRateIsInvalid([rateKey], { from: owner }); await instance.anyRateIsInvalid([rateKey], { from: deployerAccount }); @@ -856,14 +275,13 @@ contract('Exchange Rates', async accounts => { web3.utils.toWei('4', 'ether'), ]; - const updatedTime1 = await currentTime(); - await instance.updateRates(encodedRateKeys1, encodedRateValues1, updatedTime1, { - from: oracle, - }); - const rateIsInvalid = await instance.anyRateIsInvalid([ - ...encodedRateKeys1, - toBytes32('RST'), - ]); + const staleKey = toBytes32('RST'); + const allKeys = [...encodedRateKeys1, staleKey]; + + await setupAggregators(allKeys); + await updateRates(encodedRateKeys1, encodedRateValues1); + + const rateIsInvalid = await instance.anyRateIsInvalid(allKeys); assert.equal(rateIsInvalid, true); }); }); @@ -875,20 +293,13 @@ contract('Exchange Rates', async accounts => { from: owner, }); }); - describe('when a regular and aggregated synth have rates', () => { + describe('when aggregated synth has rates', () => { beforeEach(async () => { const timestamp = await currentTime(); - await instance.updateRates([toBytes32('sGOLD')], [web3.utils.toWei('1')], timestamp, { - from: oracle, - }); await aggregatorJPY.setLatestAnswer(convertToDecimals(100, 8), timestamp); }); - it('then rateIsInvalid for both is false', async () => { - const rateIsInvalid = await instance.anyRateIsInvalid([ - toBytes32('sGOLD'), - sJPY, - sUSD, - ]); + it('then rateIsInvalid is false', async () => { + const rateIsInvalid = await instance.anyRateIsInvalid([sJPY, sUSD]); assert.equal(rateIsInvalid, false); }); @@ -902,12 +313,8 @@ contract('Exchange Rates', async accounts => { }); }); - it('then rateIsInvalid for both is still false', async () => { - const rateIsInvalid = await instance.anyRateIsInvalid([ - toBytes32('sGOLD'), - sJPY, - sUSD, - ]); + it('then rateIsInvalid is still false', async () => { + const rateIsInvalid = await instance.anyRateIsInvalid([sJPY, sUSD]); assert.equal(rateIsInvalid, false); }); @@ -915,12 +322,8 @@ contract('Exchange Rates', async accounts => { beforeEach(async () => { await mockFlagsInterface.flagAggregator(aggregatorJPY.address); }); - it('then rateIsInvalid for both is true', async () => { - const rateIsInvalid = await instance.anyRateIsInvalid([ - toBytes32('sGOLD'), - sJPY, - sUSD, - ]); + it('then rateIsInvalid is true', async () => { + const rateIsInvalid = await instance.anyRateIsInvalid([sJPY, sUSD]); assert.equal(rateIsInvalid, true); }); }); @@ -937,21 +340,13 @@ contract('Exchange Rates', async accounts => { const abc = toBytes32('lABC'); const timeSent = await currentTime(); const listOfKeys = [abc, toBytes32('lDEF'), toBytes32('lGHI')]; - await instance.updateRates( - listOfKeys.slice(0, 2), - [web3.utils.toWei('1.3', 'ether'), web3.utils.toWei('2.4', 'ether')], - timeSent, - { from: oracle } - ); + await setupAggregators(listOfKeys); + + await updateRates(listOfKeys.slice(0, 2), [toUnit('1.3'), toUnit('2.4')], timeSent); await fastForward(100); const newTimeSent = await currentTime(); - await instance.updateRates( - listOfKeys.slice(2), - [web3.utils.toWei('3.5', 'ether')], - newTimeSent, - { from: oracle } - ); + await updateRates(listOfKeys.slice(2), [toUnit('3.5')], newTimeSent); const lastUpdateTimes = await instance.lastRateUpdateTimesForCurrencies(listOfKeys); assert.notEqual(timeSent, newTimeSent); @@ -965,18 +360,14 @@ contract('Exchange Rates', async accounts => { const abc = toBytes32('lABC'); const def = toBytes32('lDEF'); const ghi = toBytes32('lGHI'); + await setupAggregators([abc, def, ghi]); + const timeSent = await currentTime(); - await instance.updateRates( - [abc, def], - [web3.utils.toWei('1.3', 'ether'), web3.utils.toWei('2.4', 'ether')], - timeSent, - { from: oracle } - ); + await updateRates([abc, def], [toUnit('1.3'), toUnit('2.4')], timeSent); + await fastForward(10000); const timeSent2 = await currentTime(); - await instance.updateRates([ghi], [web3.utils.toWei('2.4', 'ether')], timeSent2, { - from: oracle, - }); + await updateRates([ghi], [toUnit('2.4')], timeSent2); const [firstTS, secondTS] = await Promise.all([ instance.lastRateUpdateTimes(abc), @@ -990,21 +381,14 @@ contract('Exchange Rates', async accounts => { const itCalculatesEffectiveValue = () => { describe('effectiveValue() and effectiveValueAndRates()', () => { - let timestamp; - beforeEach(async () => { - timestamp = await currentTime(); - }); - describe('when a price is sent to the oracle', () => { beforeEach(async () => { // Send a price update to guarantee we're not depending on values from outside this test. - await instance.updateRates( - ['sAUD', 'sEUR', 'SNX'].map(toBytes32), - ['0.5', '1.25', '0.1'].map(toUnit), - timestamp, - { from: oracle } - ); + const keys = [sAUD, sEUR, SNX]; + await setupAggregators(keys); + await updateRates(keys, ['0.5', '1.25', '0.1'].map(toUnit)); }); + it('should correctly calculate an exchange rate in effectiveValue()', async () => { // 1 sUSD should be worth 2 sAUD. assert.bnEqual(await instance.effectiveValue(sUSD, toUnit('1'), sAUD), toUnit('2')); @@ -1020,12 +404,8 @@ contract('Exchange Rates', async accounts => { // Add stale period to the time to ensure we go stale. await fastForward((await instance.rateStalePeriod()) + 1); - timestamp = await currentTime(); - // Update all rates except sUSD. - await instance.updateRates([sEUR, SNX], ['1.25', '0.1'].map(toUnit), timestamp, { - from: oracle, - }); + await updateRates([sEUR, SNX], ['1.25', '0.1'].map(toUnit)); const amountOfSynthetixs = toUnit('10'); const amountOfEur = toUnit('0.8'); @@ -1035,11 +415,11 @@ contract('Exchange Rates', async accounts => { }); it('should return 0 when relying on a non-existant dest exchange rate in effectiveValue()', async () => { - assert.equal(await instance.effectiveValue(SNX, toUnit('10'), toBytes32('XYZ')), '0'); + assert.equal(await instance.effectiveValue(SNX, toUnit('10'), toBytes32('XYZ')), 0); }); - it('should return 0 when relying on a non-existing src rate in effectiveValue', async () => { - assert.equal(await instance.effectiveValue(toBytes32('XYZ'), toUnit('10'), SNX), '0'); + it('should revert when relying on a non-existing src rate in effectiveValue', async () => { + assert.equal(await instance.effectiveValue(toBytes32('XYZ'), toUnit('10'), SNX), 0); }); it('effectiveValueAndRates() should return rates as well with sUSD on one side', async () => { @@ -1081,8 +461,6 @@ contract('Exchange Rates', async accounts => { }); }; - // Aggregator rates and flags - const itReadsFromAggregator = () => { describe('when the flags interface is set', () => { beforeEach(async () => { @@ -1411,45 +789,27 @@ contract('Exchange Rates', async accounts => { await assert.invalidOpcode(instance.aggregatorKeys(1)); }); }); - describe('when the ratesAndInvalidForCurrencies is queried', () => { - let response; - beforeEach(async () => { - response = await instance.ratesAndInvalidForCurrencies([sJPY, sXTZ, sUSD]); - }); - - it('then the rates are invalid again', () => { - assert.equal(response[1], true); - }); - - it('and JPY is 0 while the other is fine', () => { - assert.equal(response[0][0], '0'); - assert.bnEqual(response[0][1], toUnit(newRateXTZ.toString())); - }); + it('when the ratesAndInvalidForCurrencies is queried it returns 0', async () => { + assert.deepEqual( + await instance.ratesAndInvalidForCurrencies([sJPY, sXTZ, sUSD]), + [[0, toUnit(newRateXTZ), toUnit(1)], true] + ); }); describe('when rateAndInvalid is queried', () => { - let responseJPY; - let responseXTZ; - let responseUSD; - beforeEach(async () => { - responseJPY = await instance.rateAndInvalid(sJPY); - responseXTZ = await instance.rateAndInvalid(sXTZ); - responseUSD = await instance.rateAndInvalid(sUSD); + it('then JPY returns true', async () => { + assert.deepEqual(await instance.rateAndInvalid(sJPY), [0, true]); }); - it('then the rates are invalid again', () => { - assert.equal(responseJPY[1], true); + it('other rates are fine', async () => { + const responseXTZ = await instance.rateAndInvalid(sXTZ); + const responseUSD = await instance.rateAndInvalid(sUSD); + assert.equal(responseXTZ[1], false); assert.equal(responseUSD[1], false); - }); - - it('and JPY is 0 while the other is fine', () => { - assert.bnEqual(responseJPY[0], toUnit('0')); assert.bnEqual(responseXTZ[0], toUnit(newRateXTZ.toString())); assert.bnEqual(responseUSD[0], toUnit('1')); }); }); - - describe('when sJPY has a non-aggregated rate', () => {}); }); }); }); @@ -1512,293 +872,6 @@ contract('Exchange Rates', async accounts => { }); }); - describe('when a price already exists for sJPY', () => { - const oldPrice = toUnit(100); - let timeOldSent; - beforeEach(async () => { - timeOldSent = await currentTime(); - - await instance.updateRates([sJPY], [oldPrice], timeOldSent, { - from: oracle, - }); - }); - describe('when the ratesAndInvalidForCurrencies is queried with sJPY', () => { - let response; - beforeEach(async () => { - response = await instance.ratesAndInvalidForCurrencies([sJPY, sUSD]); - }); - - it('then the rates are NOT invalid', () => { - assert.equal(response[1], false); - }); - - it('and equal to the value', () => { - assert.bnEqual(response[0][0], oldPrice); - }); - }); - describe('when rateAndInvalid is queried with sJPY', () => { - let response; - beforeEach(async () => { - response = await instance.rateAndInvalid(sJPY); - }); - - it('then the rate is NOT invalid', () => { - assert.equal(response[1], false); - }); - - it('and equal to the value', () => { - assert.bnEqual(response[0], oldPrice); - }); - }); - - describe('when the price is inspected for sJPY', () => { - it('then the price is returned as expected', async () => { - const result = await instance.rateForCurrency(sJPY, { - from: accountOne, - }); - assert.equal(result.toString(), oldPrice); - }); - it('then the timestamp is returned as expected', async () => { - const result = await instance.lastRateUpdateTimes(sJPY, { - from: accountOne, - }); - assert.equal(result.toNumber(), timeOldSent); - }); - }); - - describe('when sJPY added as an aggregator (replacing existing)', () => { - beforeEach(async () => { - await instance.addAggregator(sJPY, aggregatorJPY.address, { - from: owner, - }); - }); - describe('when the price is fetched for sJPY', () => { - it('0 is returned', async () => { - const result = await instance.rateForCurrency(sJPY, { - from: accountOne, - }); - assert.equal(result.toNumber(), 0); - }); - }); - describe('when the timestamp is fetched for sJPY', () => { - it('0 is returned', async () => { - const result = await instance.lastRateUpdateTimes(sJPY, { - from: accountOne, - }); - assert.equal(result.toNumber(), 0); - }); - }); - describe('when the ratesAndInvalidForCurrencies is queried with sJPY', () => { - let response; - beforeEach(async () => { - response = await instance.ratesAndInvalidForCurrencies([sJPY]); - }); - - it('then the rates are invalid', () => { - assert.equal(response[1], true); - }); - - it('with no value', () => { - assert.bnEqual(response[0][0], '0'); - }); - }); - describe('when the rateAndInvalid is queried with sJPY', () => { - let response; - beforeEach(async () => { - response = await instance.rateAndInvalid(sJPY); - }); - - it('then the rate is invalid', () => { - assert.equal(response[1], true); - }); - - it('with no value', () => { - assert.bnEqual(response[0], '0'); - }); - }); - - describe('when the aggregator price is set to set a specific number (with support for 8 decimals)', () => { - const newRate = 9.55; - let timestamp; - beforeEach(async () => { - await fastForward(50); - timestamp = await currentTime(); - // Need to set twice in order to increase the roundId in aggregator - // to be greater than the one in the cache - await aggregatorJPY.setLatestAnswer(convertToDecimals(newRate, 8), timestamp); - await aggregatorJPY.setLatestAnswer(convertToDecimals(newRate, 8), timestamp); - }); - - describe('when the price is fetched for sJPY', () => { - it('the new aggregator rate is returned instead of the old price', async () => { - const result = await instance.rateForCurrency(sJPY, { - from: accountOne, - }); - assert.bnEqual(result, toUnit(newRate.toString())); - }); - it('and the timestamp is the new one', async () => { - const result = await instance.lastRateUpdateTimes(sJPY, { - from: accountOne, - }); - assert.bnEqual(result.toNumber(), timestamp); - }); - }); - - describe('when the ratesAndInvalidForCurrencies is queried with sJPY', () => { - let response; - beforeEach(async () => { - response = await instance.ratesAndInvalidForCurrencies([sJPY, sUSD]); - }); - - it('then the rates are NOT invalid', () => { - assert.equal(response[1], false); - }); - - it('and equal to the value', () => { - assert.bnEqual(response[0][0], toUnit(newRate.toString())); - }); - }); - - describe('when rateAndInvalid is queried with sJPY', () => { - let response; - beforeEach(async () => { - response = await instance.rateAndInvalid(sJPY); - }); - - it('then the rates are NOT invalid', () => { - assert.equal(response[1], false); - }); - - it('and equal to the value', () => { - assert.bnEqual(response[0], toUnit(newRate.toString())); - }); - }); - - describe('when the aggregator is removed for sJPY', () => { - beforeEach(async () => { - await instance.removeAggregator(sJPY, { - from: owner, - }); - }); - describe('when a user queries the first entry in aggregatorKeys', () => { - it('then they are empty', async () => { - await assert.invalidOpcode(instance.aggregatorKeys(0)); - }); - }); - describe('when the price is inspected for sJPY', () => { - it('then the old price is returned', async () => { - const result = await instance.rateForCurrency(sJPY, { - from: accountOne, - }); - assert.equal(result.toString(), oldPrice); - }); - it('and the timestamp is returned as expected', async () => { - const result = await instance.lastRateUpdateTimes(sJPY, { - from: accountOne, - }); - assert.equal(result.toNumber(), timeOldSent); - }); - }); - describe('when the ratesAndInvalidForCurrencies is queried with sJPY', () => { - let response; - beforeEach(async () => { - response = await instance.ratesAndInvalidForCurrencies([sJPY, sUSD]); - }); - - it('then the rates are NOT invalid', () => { - assert.equal(response[1], false); - }); - - it('and equal to the old value', () => { - assert.bnEqual(response[0][0], oldPrice); - }); - }); - - describe('when the rateAndInvalid is queried with sJPY', () => { - let response; - beforeEach(async () => { - response = await instance.rateAndInvalid(sJPY); - }); - - it('then the rates are NOT invalid', () => { - assert.equal(response[1], false); - }); - - it('and equal to the old value', () => { - assert.bnEqual(response[0], oldPrice); - }); - }); - }); - }); - }); - - describe('when sXTZ added as an aggregator', () => { - beforeEach(async () => { - await instance.addAggregator(sXTZ, aggregatorXTZ.address, { - from: owner, - }); - }); - describe('when the ratesAndInvalidForCurrencies is queried with sJPY and sXTZ', () => { - let response; - beforeEach(async () => { - response = await instance.ratesAndInvalidForCurrencies([sJPY, sXTZ, sUSD]); - }); - - it('then the rates are invalid', () => { - assert.equal(response[1], true); - }); - - it('with sXTZ having no value', () => { - assert.bnEqual(response[0][0], oldPrice); - assert.bnEqual(response[0][1], '0'); - }); - }); - describe('when the rateAndInvalid is queried with sJPY and sXTZ', () => { - let responseJPY; - let responseXTZ; - beforeEach(async () => { - responseJPY = await instance.rateAndInvalid(sJPY); - responseXTZ = await instance.rateAndInvalid(sXTZ); - }); - - it('then the XTZ rate is invalid', () => { - assert.equal(responseJPY[1], false); - assert.equal(responseXTZ[1], true); - }); - - it('with sXTZ having no value', () => { - assert.bnEqual(responseJPY[0], oldPrice); - assert.bnEqual(responseXTZ[0], '0'); - }); - }); - - describe('when the aggregator price is set to set for sXTZ', () => { - const newRate = 99; - let timestamp; - beforeEach(async () => { - await fastForward(50); - timestamp = await currentTime(); - await aggregatorXTZ.setLatestAnswer(convertToDecimals(newRate, 8), timestamp); - }); - - describe('when the ratesAndInvalidForCurrencies is queried with sJPY and sXTZ', () => { - let response; - beforeEach(async () => { - response = await instance.ratesAndInvalidForCurrencies([sJPY, sXTZ, sUSD]); - }); - - it('then the rates are NOT invalid', () => { - assert.equal(response[1], false); - }); - - it('and equal to the values', () => { - assert.bnEqual(response[0][0], oldPrice); - assert.bnEqual(response[0][1], toUnit(newRate.toString())); - }); - }); - }); - }); - }); describe('warning flags and invalid rates', () => { it('sUSD is never flagged / invalid.', async () => { assert.isFalse(await instance.rateIsFlagged(sUSD)); @@ -1860,24 +933,31 @@ contract('Exchange Rates', async accounts => { }); describe('roundIds for historical rates', () => { - it('getCurrentRoundId() by default is 0 for all synths except sUSD which is 1', async () => { - // Note: rates that were set in the truffle migration will be at 1, so we need to check - // other synths - assert.equal(await instance.getCurrentRoundId(sJPY), '0'); - assert.equal(await instance.getCurrentRoundId(sBNB), '0'); - assert.equal(await instance.getCurrentRoundId(sUSD), '1'); + it('getCurrentRoundId() returns 0 for unknown currencies', async () => { + assert.equal(await instance.getCurrentRoundId(sJPY), 0); + assert.equal(await instance.getCurrentRoundId(sBNB), 0); + }); + + it('getCurrentRoundId() is 0 for currencies with no updates', async () => { + await setupAggregators([sJPY, sBNB]); + assert.equal(await instance.getCurrentRoundId(sJPY), 0); + assert.equal(await instance.getCurrentRoundId(sBNB), 0); + }); + + it('getCurrentRoundId() is 0 for sUSD', async () => { + assert.equal(await instance.getCurrentRoundId(sUSD), 0); }); it('ratesAndUpdatedTimeForCurrencyLastNRounds() shows first entry for sUSD', async () => { - const timeOfsUSDRateSetOnInit = await instance.lastRateUpdateTimes(sUSD); - assert.deepEqual(await instance.ratesAndUpdatedTimeForCurrencyLastNRounds(sUSD, '3', '0'), [ + assert.deepEqual(await instance.ratesAndUpdatedTimeForCurrencyLastNRounds(sUSD, '3'), [ [toUnit('1'), '0', '0'], - [timeOfsUSDRateSetOnInit, '0', '0'], + [0, 0, 0], ]); }); - it('ratesAndUpdatedTimeForCurrencyLastNRounds() returns 0s for other currency keys', async () => { + it('ratesAndUpdatedTimeForCurrencyLastNRounds() returns 0s for other currencies without updates', async () => { const fiveZeros = new Array(5).fill('0'); - assert.deepEqual(await instance.ratesAndUpdatedTimeForCurrencyLastNRounds(sAUD, '5', '0'), [ + await setupAggregators([sJPY]); + assert.deepEqual(await instance.ratesAndUpdatedTimeForCurrencyLastNRounds(sJPY, '5'), [ fiveZeros, fiveZeros, ]); @@ -1898,145 +978,83 @@ contract('Exchange Rates', async accounts => { } }); - describe('and the sBNB rate (non-aggregator) has been set three times directly also', () => { - let timestamp; - - beforeEach(async () => { - for (let i = 0; i < 3; i++) { - timestamp = 10000; - await instance.updateRates([sBNB], [toUnit((1000 + i).toString())], timestamp + i, { - from: oracle, - }); - } - }); - describe('getCurrentRoundId())', () => { - describe('when invoked for an aggregator', () => { - it('getCurrentRound() returns the last entry', async () => { - await assert.equal((await instance.getCurrentRoundId(sJPY)).toString(), '3'); - }); - }); - describe('when invoked for a regular price', () => { - it('getCurrentRound() returns the last entry', async () => { - await assert.equal((await instance.getCurrentRoundId(sBNB)).toString(), '3'); - }); + describe('getCurrentRoundId())', () => { + describe('when invoked for an aggregator', () => { + it('getCurrentRound() returns the last entry', async () => { + assert.equal((await instance.getCurrentRoundId(sJPY)).toString(), '3'); }); }); - describe('rateAndTimestampAtRound()', () => { - it('when invoked for no price, returns no rate and no tme', async () => { + }); + describe('rateAndTimestampAtRound()', () => { + it('when invoked for no price returns 0', async () => { + assert.deepEqual(await instance.rateAndTimestampAtRound(toBytes32('TEST'), '0'), [ + 0, + 0, + ]); + }); + it('when invoked for an aggregator', async () => { + const assertRound = async ({ roundId }) => { const { rate, time } = await instance.rateAndTimestampAtRound( - toBytes32('TEST'), - '0' + sJPY, + roundId.toString() + ); + assert.bnEqual(rate, toUnit((100 + roundId - 1).toString())); + assert.bnEqual(time, toBN(1000 + roundId - 1)); + }; + await assertRound({ roundId: 1 }); + await assertRound({ roundId: 2 }); + await assertRound({ roundId: 3 }); + }); + }); + + describe('ratesAndUpdatedTimeForCurrencyLastNRounds()', () => { + describe('when invoked for a non-existant currency', () => { + it('then it returns zeros', async () => { + const fiveZeros = new Array(5).fill('0'); + assert.deepEqual( + await instance.ratesAndUpdatedTimeForCurrencyLastNRounds(sAUD, '5'), + [fiveZeros, fiveZeros] ); - assert.equal(rate, '0'); - assert.equal(time, '0'); - }); - it('when invoked for an aggregator', async () => { - const assertRound = async ({ roundId }) => { - const { rate, time } = await instance.rateAndTimestampAtRound( - sJPY, - roundId.toString() - ); - assert.bnEqual(rate, toUnit((100 + roundId - 1).toString())); - assert.bnEqual(time, toBN(1000 + roundId - 1)); - }; - await assertRound({ roundId: 1 }); - await assertRound({ roundId: 2 }); - await assertRound({ roundId: 3 }); - }); - it('when invoked for a regular price', async () => { - const assertRound = async ({ roundId }) => { - const { rate, time } = await instance.rateAndTimestampAtRound( - sBNB, - roundId.toString() - ); - assert.bnEqual(rate, toUnit((1000 + roundId - 1).toString())); - assert.bnEqual(time, toBN(10000 + roundId - 1)); - }; - await assertRound({ roundId: 1 }); - await assertRound({ roundId: 2 }); - await assertRound({ roundId: 3 }); }); }); - - describe('ratesAndUpdatedTimeForCurrencyLastNRounds()', () => { - describe('when invoked for a non-existant currency', () => { - it('then it returns 0s', async () => { - const fiveZeros = new Array(5).fill('0'); - assert.deepEqual( - await instance.ratesAndUpdatedTimeForCurrencyLastNRounds(sAUD, '5', '0'), - [fiveZeros, fiveZeros] - ); - }); - }); - describe('when invoked for an aggregated price', () => { - it('then it returns the rates as expected', async () => { - assert.deepEqual( - await instance.ratesAndUpdatedTimeForCurrencyLastNRounds(sJPY, '3', '0'), - [ - [toUnit('102'), toUnit('101'), toUnit('100')], - ['1002', '1001', '1000'], - ] - ); - }); - - it('then it returns the rates as expected, even over the edge', async () => { - assert.deepEqual( - await instance.ratesAndUpdatedTimeForCurrencyLastNRounds(sJPY, '5', '0'), - [ - [toUnit('102'), toUnit('101'), toUnit('100'), '0', '0'], - ['1002', '1001', '1000', '0', '0'], - ] - ); - }); + describe('when invoked for an aggregated price', () => { + it('then it returns the rates as expected', async () => { + assert.deepEqual( + await instance.ratesAndUpdatedTimeForCurrencyLastNRounds(sJPY, '3'), + [ + [toUnit('102'), toUnit('101'), toUnit('100')], + ['1002', '1001', '1000'], + ] + ); }); - describe('when invoked for a regular price', () => { - it('then it returns the rates as expected', async () => { - assert.deepEqual( - await instance.ratesAndUpdatedTimeForCurrencyLastNRounds(sBNB, '3', '0'), - [ - [toUnit('1002'), toUnit('1001'), toUnit('1000')], - ['10002', '10001', '10000'], - ] - ); - }); - it('then it returns the rates as expected, even over the edge', async () => { - assert.deepEqual( - await instance.ratesAndUpdatedTimeForCurrencyLastNRounds(sBNB, '5', '0'), - [ - [toUnit('1002'), toUnit('1001'), toUnit('1000'), '0', '0'], - ['10002', '10001', '10000', '0', '0'], - ] - ); - }); + it('then it returns the rates as expected, even over the edge', async () => { + assert.deepEqual( + await instance.ratesAndUpdatedTimeForCurrencyLastNRounds(sJPY, '5'), + [ + [toUnit('102'), toUnit('101'), toUnit('100'), '0', '0'], + ['1002', '1001', '1000', '0', '0'], + ] + ); }); }); }); }); - describe('and both the aggregator and regular prices have been given three rates, 30seconds apart', () => { + describe('and the aggregator has been given three rates, 30seconds apart', () => { beforeEach(async () => { await aggregatorJPY.setLatestAnswer(convertToDecimals(100, 8), 30); // round 1 for sJPY await aggregatorJPY.setLatestAnswer(convertToDecimals(200, 8), 60); // round 2 for sJPY await aggregatorJPY.setLatestAnswer(convertToDecimals(300, 8), 90); // round 3 for sJPY - - await instance.updateRates([sBNB], [toUnit('1000')], '30', { from: oracle }); // round 1 for sBNB - await instance.updateRates([sBNB], [toUnit('2000')], '60', { from: oracle }); // round 2 for sBNB - await instance.updateRates([sBNB], [toUnit('3000')], '90', { from: oracle }); // round 3 for sBNB }); describe('getLastRoundIdBeforeElapsedSecs()', () => { describe('when getLastRoundIdBeforeElapsedSecs() is invoked with the first round and a waiting time of less than 30s', () => { it('then it receives round 1 - no change ', async () => { - // assert both aggregated price and regular prices work as expected assert.equal( (await instance.getLastRoundIdBeforeElapsedSecs(sJPY, '1', 40, 10)).toString(), '1' ); - assert.equal( - (await instance.getLastRoundIdBeforeElapsedSecs(sBNB, '1', 40, 10)).toString(), - '1' - ); }); }); @@ -2046,10 +1064,6 @@ contract('Exchange Rates', async accounts => { (await instance.getLastRoundIdBeforeElapsedSecs(sJPY, '1', 40, 20)).toString(), '2' ); - assert.equal( - (await instance.getLastRoundIdBeforeElapsedSecs(sBNB, '1', 40, 20)).toString(), - '2' - ); }); }); @@ -2059,10 +1073,6 @@ contract('Exchange Rates', async accounts => { (await instance.getLastRoundIdBeforeElapsedSecs(sJPY, '2', 65, 25)).toString(), '3' ); - assert.equal( - (await instance.getLastRoundIdBeforeElapsedSecs(sBNB, '2', 65, 25)).toString(), - '3' - ); }); }); @@ -2072,10 +1082,6 @@ contract('Exchange Rates', async accounts => { (await instance.getLastRoundIdBeforeElapsedSecs(sJPY, '1', 40, 40)).toString(), '2' ); - assert.equal( - (await instance.getLastRoundIdBeforeElapsedSecs(sBNB, '1', 40, 40)).toString(), - '2' - ); }); }); describe('when getLastRoundIdBeforeElapsedSecs() is invoked with the first round and a waiting time of 60s exactly', () => { @@ -2084,10 +1090,6 @@ contract('Exchange Rates', async accounts => { (await instance.getLastRoundIdBeforeElapsedSecs(sJPY, '1', 50, 40)).toString(), '3' ); - assert.equal( - (await instance.getLastRoundIdBeforeElapsedSecs(sBNB, '1', 50, 40)).toString(), - '3' - ); }); }); describe('when getLastRoundIdBeforeElapsedSecs() is invoked with the first round and a waiting time beyond 60s', () => { @@ -2096,10 +1098,6 @@ contract('Exchange Rates', async accounts => { (await instance.getLastRoundIdBeforeElapsedSecs(sJPY, '1', 55, 6000)).toString(), '3' ); - assert.equal( - (await instance.getLastRoundIdBeforeElapsedSecs(sBNB, '1', 50, 40)).toString(), - '3' - ); }); }); describe('when getLastRoundIdBeforeElapsedSecs() is invoked with the third round and a waiting time beyond 60s', () => { @@ -2108,30 +1106,23 @@ contract('Exchange Rates', async accounts => { (await instance.getLastRoundIdBeforeElapsedSecs(sJPY, '3', 180, 9000)).toString(), '3' ); - assert.equal( - (await instance.getLastRoundIdBeforeElapsedSecs(sBNB, '1', 50, 40)).toString(), - '3' - ); }); }); }); }); + describe('effectiveValueAtRound()', () => { - describe('when both the aggregator and regular prices have been give three rates with current timestamps', () => { + describe('when both aggregated prices have been given three rates with current timestamps', () => { beforeEach(async () => { - let timestamp = await currentTime(); - await aggregatorJPY.setLatestAnswer(convertToDecimals(100, 8), timestamp); // round 1 for sJPY - await instance.updateRates([sBNB], [toUnit('1000')], timestamp, { from: oracle }); // round 1 for sBNB + await setupAggregators([sBNB]); + + await updateRates([sJPY, sBNB], [convertToDecimals(100, 8), toUnit('1000')]); await fastForward(120); - timestamp = await currentTime(); - await aggregatorJPY.setLatestAnswer(convertToDecimals(200, 8), timestamp); // round 2 for sJPY - await instance.updateRates([sBNB], [toUnit('2000')], timestamp, { from: oracle }); // round 2 for sBNB + await updateRates([sJPY, sBNB], [convertToDecimals(200, 8), toUnit('2000')]); await fastForward(120); - timestamp = await currentTime(); - await aggregatorJPY.setLatestAnswer(convertToDecimals(300, 8), timestamp); // round 3 for sJPY - await instance.updateRates([sBNB], [toUnit('4000')], timestamp, { from: oracle }); // round 3 for sBNB + await updateRates([sJPY, sBNB], [convertToDecimals(300, 8), toUnit('4000')]); }); it('accepts various changes to src roundId', async () => { assert.bnEqual( @@ -2989,20 +1980,28 @@ contract('Exchange Rates', async accounts => { }); }; + // utility function to setup price aggregators + async function setupAggregators(keys, decimalsArray = []) { + await setupPriceAggregators(instance, owner, keys, decimalsArray); + } + + // utility function update rates for aggregators that are already set up + async function updateRates(keys, rates, timestamp = undefined) { + await updateAggregatorRates(instance, keys, rates, timestamp); + } + describe('Using ExchangeRates', () => { const exchangeRatesContract = 'ExchangeRates'; before(async () => { - initialTime = await currentTime(); - ({ - ExchangeRates: instance, - SystemSettings: systemSettings, - AddressResolver: resolver, - } = await setupAllContracts({ + ({ ExchangeRates: instance, SystemSettings: systemSettings } = await setupAllContracts({ accounts, contracts: [exchangeRatesContract, 'SystemSettings', 'AddressResolver'], })); + // remove the pre-configured aggregator + await instance.removeAggregator(toBytes32('SNX'), { from: owner }); + aggregatorJPY = await MockAggregator.new({ from: owner }); aggregatorXTZ = await MockAggregator.new({ from: owner }); aggregatorFastGasPrice = await MockAggregator.new({ from: owner }); @@ -3017,22 +2016,10 @@ contract('Exchange Rates', async accounts => { addSnapshotBeforeRestoreAfterEach(); - beforeEach(async () => { - timeSent = await currentTime(); - }); - itIncludesCorrectMutativeFunctions(exchangeRatesContract); itIsConstructedCorrectly(exchangeRatesContract); - itUpdatesRates(); - - itSetsOracle(); - - itDeletesRates(); - - itReturnsRates(); - itCalculatesStaleRates(); itCalculatesInvalidRates(); @@ -3052,16 +2039,14 @@ contract('Exchange Rates', async accounts => { const exchangeRatesContract = 'ExchangeRatesWithDexPricing'; before(async () => { - initialTime = await currentTime(); - ({ - ExchangeRates: instance, - SystemSettings: systemSettings, - AddressResolver: resolver, - } = await setupAllContracts({ + ({ ExchangeRates: instance, SystemSettings: systemSettings } = await setupAllContracts({ accounts, contracts: [exchangeRatesContract, 'SystemSettings', 'AddressResolver'], })); + // remove the pre-configured aggregator + await instance.removeAggregator(toBytes32('SNX'), { from: owner }); + aggregatorJPY = await MockAggregator.new({ from: owner }); aggregatorXTZ = await MockAggregator.new({ from: owner }); aggregatorFastGasPrice = await MockAggregator.new({ from: owner }); @@ -3076,22 +2061,10 @@ contract('Exchange Rates', async accounts => { addSnapshotBeforeRestoreAfterEach(); - beforeEach(async () => { - timeSent = await currentTime(); - }); - itIncludesCorrectMutativeFunctions(exchangeRatesContract); itIsConstructedCorrectly(exchangeRatesContract); - itUpdatesRates(); - - itSetsOracle(); - - itDeletesRates(); - - itReturnsRates(); - itCalculatesStaleRates(); itCalculatesInvalidRates(); diff --git a/test/contracts/Exchanger.spec.js b/test/contracts/Exchanger.spec.js index 6a77044cfd..5a6f7c5000 100644 --- a/test/contracts/Exchanger.spec.js +++ b/test/contracts/Exchanger.spec.js @@ -18,6 +18,8 @@ const { setStatus, convertToAggregatorPrice, updateRatesWithDefaults, + setupPriceAggregators, + updateAggregatorRates, } = require('./helpers'); const { @@ -63,8 +65,6 @@ contract('Exchanger (spec tests)', async accounts => { sEURContract, sBTCContract, sETHContract, - oracle, - timestamp, exchanger, exchangeState, exchangeFeeRate, @@ -144,7 +144,7 @@ contract('Exchanger (spec tests)', async accounts => { const amountOfSrcExchanged = toUnit('10'); beforeEach(async () => { - await updateRatesWithDefaults({ exchangeRates, oracle, debtCache }); + await updateRatesWithDefaults({ exchangeRates, owner, debtCache }); await sUSDContract.issue(owner, toUnit('100')); await synthetix.exchange(sUSD, toUnit('10'), sETH, { from: owner }); }); @@ -278,9 +278,7 @@ contract('Exchanger (spec tests)', async accounts => { beforeEach(async () => { await fastForward(10); // base rate of sETH is 100 from shared setup above - await exchangeRates.updateRates([sETH], [toUnit('300')], await currentTime(), { - from: oracle, - }); + await updateRates([sETH], [toUnit('300')]); await synthetix.exchange(sUSD, toUnit('1'), sETH, { from: account1 }); }); it('then the synth is suspended', async () => { @@ -293,9 +291,7 @@ contract('Exchanger (spec tests)', async accounts => { beforeEach(async () => { await fastForward(10); // base rate of sETH is 100 from shared setup above - await exchangeRates.updateRates([sETH], [toUnit('33')], await currentTime(), { - from: oracle, - }); + await updateRates([sETH], [toUnit('33')]); await synthetix.exchange(sUSD, toUnit('1'), sETH, { from: account1 }); }); it('then the synth is suspended', async () => { @@ -313,9 +309,7 @@ contract('Exchanger (spec tests)', async accounts => { beforeEach(async () => { await fastForward(10); // base rate of sETH is 100 from shared setup above - await exchangeRates.updateRates([sETH], [toUnit('300')], await currentTime(), { - from: oracle, - }); + await updateRates([sETH], [toUnit('300')]); await synthetix.exchange(sUSD, toUnit('1'), sETH, { from: account1 }); }); it('then the synth is not suspended', async () => { @@ -328,9 +322,7 @@ contract('Exchanger (spec tests)', async accounts => { beforeEach(async () => { await fastForward(10); // base rate of sETH is 100 from shared setup above - await exchangeRates.updateRates([sETH], [toUnit('33')], await currentTime(), { - from: oracle, - }); + await updateRates([sETH], [toUnit('33')]); await synthetix.exchange(sUSD, toUnit('1'), sETH, { from: account1 }); }); it('then the synth is not suspended', async () => { @@ -574,47 +566,6 @@ contract('Exchanger (spec tests)', async accounts => { assert.bnEqual(amountReceived, effectiveValue.sub(tripleFee)); }); }); - describe('when sBTC price rate spike', () => { - let sUSDsBTCFee; - let sBTCBalance; - let sBTCsUSDFee; - beforeEach(async () => { - timestamp = await currentTime(); - await exchangeRates.updateRates([sETH, sBTC], ['110', '5100'].map(toUnit), timestamp, { - from: oracle, - }); - }); - describe('and swap sUSD to sBTC', () => { - let destinationFee; - beforeEach(async () => { - console.log('amountIssued', amountIssued.toString()); - await synthetix.exchange(sUSD, amountIssued, sBTC, { from: account1 }); - const { fee } = await exchanger.getAmountsForExchange(amountIssued, sUSD, sBTC); - destinationFee = fee; - }); - it('then destination fee is greater as included dynamic fee in destination currency', async () => { - const effectiveValue = await exchangeRates.effectiveValue(sUSD, amountIssued, sBTC); - sUSDsBTCFee = exchangeFeeIncurred(effectiveValue, bipsCrypto); - assert.bnGt(destinationFee, sUSDsBTCFee); - }); - it('then swap sBTC to sETH and fee is more than sUSD to sBTC as dynamic fee in both direction', async () => { - await fastForward(await systemSettings.waitingPeriodSecs()); - sBTCBalance = await sBTCContract.balanceOf(account1); - await synthetix.exchange(sBTC, sBTCBalance, sETH, { from: account1 }); - const { fee } = await exchanger.getAmountsForExchange(sBTCBalance, sBTC, sETH); - assert.bnGt(fee, destinationFee); - }); - it('then swap sBTC to sUSD and fee is greater as included dynamic fee in source currency', async () => { - await fastForward(await systemSettings.waitingPeriodSecs()); - sBTCBalance = await sBTCContract.balanceOf(account1); - await synthetix.exchange(sBTC, sBTCBalance, sUSD, { from: account1 }); - const { fee } = await exchanger.getAmountsForExchange(sBTCBalance, sBTC, sUSD); - const effectiveValue = await exchangeRates.effectiveValue(sBTC, sBTCBalance, sUSD); - sBTCsUSDFee = exchangeFeeIncurred(effectiveValue, bipsCrypto); - assert.bnGt(fee, sBTCsUSDFee); - }); - }); - }); }); }); }; @@ -716,14 +667,7 @@ contract('Exchanger (spec tests)', async accounts => { describe('given the sEUR rate is 2, and sETH is 100, sBTC is 9000', () => { beforeEach(async () => { // set sUSD:sEUR as 2:1, sUSD:sETH at 100:1, sUSD:sBTC at 9000:1 - await exchangeRates.updateRates( - [sEUR, sETH, sBTC], - ['2', '100', '9000'].map(toUnit), - timestamp, - { - from: oracle, - } - ); + await updateRates([sEUR, sETH, sBTC], ['2', '100', '9000'].map(toUnit)); // Disable Dynamic Fee by setting rounds to 0 await systemSettings.setExchangeDynamicFeeRounds('0', { from: owner }); }); @@ -909,11 +853,7 @@ contract('Exchanger (spec tests)', async accounts => { describe('when the price doubles for sUSD:sEUR to 4:1', () => { beforeEach(async () => { await fastForward(5); - timestamp = await currentTime(); - - await exchangeRates.updateRates([sEUR], ['4'].map(toUnit), timestamp, { - from: oracle, - }); + await updateRates([sEUR], ['4'].map(toUnit)); }); it('then settlement reclaimAmount shows a reclaim of half the entire balance of sEUR', async () => { const expected = calculateExpectedSettlementAmount({ @@ -1135,12 +1075,7 @@ contract('Exchanger (spec tests)', async accounts => { describe('when the price halves for sUSD:sEUR to 1:1', () => { beforeEach(async () => { await fastForward(5); - - timestamp = await currentTime(); - - await exchangeRates.updateRates([sEUR], ['1'].map(toUnit), timestamp, { - from: oracle, - }); + await updateRates([sEUR], ['1'].map(toUnit)); }); it('then settlement rebateAmount shows a rebate of half the entire balance of sEUR', async () => { const expected = calculateExpectedSettlementAmount({ @@ -1169,12 +1104,7 @@ contract('Exchanger (spec tests)', async accounts => { describe('and then the price increases for sUSD:sEUR to 2:1', () => { beforeEach(async () => { await fastForward(5); - - timestamp = await currentTime(); - - await exchangeRates.updateRates([sEUR], ['2'].map(toUnit), timestamp, { - from: oracle, - }); + await updateRates([sEUR], ['2'].map(toUnit)); }); describe('when settlement is invoked', () => { describe('when another minute passes', () => { @@ -1383,12 +1313,7 @@ contract('Exchanger (spec tests)', async accounts => { describe('when the price returns to sUSD:sEUR to 2:1', () => { beforeEach(async () => { await fastForward(12); - - timestamp = await currentTime(); - - await exchangeRates.updateRates([sEUR], ['2'].map(toUnit), timestamp, { - from: oracle, - }); + await updateRates([sEUR], ['2'].map(toUnit)); }); it('then settlement reclaimAmount shows 0 reclaim and 0 refund', async () => { const settlement = await exchanger.settlementOwing(account1, sEUR); @@ -1402,11 +1327,7 @@ contract('Exchanger (spec tests)', async accounts => { describe('when another minute elapses and the sETH price changes', () => { beforeEach(async () => { await fastForward(60); - timestamp = await currentTime(); - - await exchangeRates.updateRates([sEUR], ['3'].map(toUnit), timestamp, { - from: oracle, - }); + await updateRates([sEUR], ['3'].map(toUnit)); }); it('then settlement reclaimAmount still shows 0 reclaim and 0 refund as the timeout period ended', async () => { const settlement = await exchanger.settlementOwing(account1, sEUR); @@ -1462,11 +1383,7 @@ contract('Exchanger (spec tests)', async accounts => { describe('when the price doubles for sUSD:sEUR to 4:1', () => { beforeEach(async () => { await fastForward(5); - timestamp = await currentTime(); - - await exchangeRates.updateRates([sEUR], ['4'].map(toUnit), timestamp, { - from: oracle, - }); + await updateRates([sEUR], ['4'].map(toUnit)); }); it('then settlement shows a rebate rebateAmount', async () => { const { reclaimAmount, rebateAmount } = await exchanger.settlementOwing( @@ -1493,16 +1410,7 @@ contract('Exchanger (spec tests)', async accounts => { }); describe('when the price gains for sBTC more than the loss of the sEUR change', () => { beforeEach(async () => { - await fastForward(5); - timestamp = await currentTime(); - await exchangeRates.updateRates( - [sBTC], - ['20000'].map(toUnit), - timestamp, - { - from: oracle, - } - ); + await updateRates([sBTC], ['20000'].map(toUnit)); }); it('then the reclaimAmount is whats left when subtracting the rebate', async () => { const { reclaimAmount, rebateAmount } = await exchanger.settlementOwing( @@ -1557,16 +1465,8 @@ contract('Exchanger (spec tests)', async accounts => { let expectedFromSecond; beforeEach(async () => { await fastForward(5); - timestamp = await currentTime(); - - await exchangeRates.updateRates( - [sBTC], - ['10000'].map(toUnit), - timestamp, - { - from: oracle, - } - ); + + await updateRates([sBTC], ['10000'].map(toUnit)); expectedFromFirst = calculateExpectedSettlementAmount({ amount: amountOfSrcExchanged, @@ -1978,11 +1878,7 @@ contract('Exchanger (spec tests)', async accounts => { }); describe('when that synth has a fresh rate', () => { beforeEach(async () => { - const timestamp = await currentTime(); - - await exchangeRates.updateRates([sAUD], ['0.75'].map(toUnit), timestamp, { - from: oracle, - }); + await updateRates([sAUD], ['0.75'].map(toUnit)); }); describe(`when the user ${type} into that synth`, () => { beforeEach(async () => { @@ -2815,14 +2711,7 @@ contract('Exchanger (spec tests)', async accounts => { const updateRate = ({ target, rate }) => { beforeEach(async () => { await fastForward(10); - await exchangeRates.updateRates( - [target], - [toUnit(rate.toString())], - await currentTime(), - { - from: oracle, - } - ); + await updateRates([target], [toUnit(rate.toString())]); }); }; @@ -2907,13 +2796,10 @@ contract('Exchanger (spec tests)', async accounts => { beforeEach(async () => { // sETH over deviation and sEUR slight change await fastForward(10); - await exchangeRates.updateRates( + await updateAggregatorRates( + exchangeRates, [sETH, sEUR], - [toUnit(baseRate * 3).toString(), toUnit('1.9')], - await currentTime(), - { - from: oracle, - } + [toUnit(baseRate * 3).toString(), toUnit('1.9')] ); }); describe('and another user exchanges sETH to sEUR', () => { @@ -2936,13 +2822,10 @@ contract('Exchanger (spec tests)', async accounts => { beforeEach(async () => { // sEUR over deviation and sETH slight change await fastForward(10); - await exchangeRates.updateRates( + await updateAggregatorRates( + exchangeRates, [sETH, sEUR], - [toUnit(baseRate * 1.1).toString(), toUnit('10')], - await currentTime(), - { - from: oracle, - } + [toUnit(baseRate * 1.1).toString(), toUnit('10')] ); }); describe('and another user exchanges sEUR to sETH', () => { @@ -3134,21 +3017,12 @@ contract('Exchanger (spec tests)', async accounts => { describe('when a recent price rate is set way outside of the threshold', () => { beforeEach(async () => { await fastForward(10); - await exchangeRates.updateRates([sETH], [toUnit('1000')], await currentTime(), { - from: oracle, - }); + await updateRates([sETH], [toUnit('1000')]); }); describe('and then put back to normal', () => { beforeEach(async () => { await fastForward(10); - await exchangeRates.updateRates( - [sETH], - [baseRate.toString()], - await currentTime(), - { - from: oracle, - } - ); + await updateRates([sETH], [baseRate.toString()]); }); assertSpike({ from: sUSD, @@ -3568,9 +3442,14 @@ contract('Exchanger (spec tests)', async accounts => { }); }; + async function updateRates(keys, rates) { + await updateAggregatorRates(exchangeRates, keys, rates); + } + describe('With L1 configuration (Synthetix, ExchangerWithFeeRecAlternatives, ExchangeRatesWithDexPricing)', () => { before(async () => { const VirtualSynthMastercopy = artifacts.require('VirtualSynthMastercopy'); + const synths = ['sUSD', 'sETH', 'sEUR', 'sAUD', 'sBTC', 'iBTC', 'sTRX']; ({ Exchanger: exchanger, @@ -3592,7 +3471,7 @@ contract('Exchanger (spec tests)', async accounts => { FlexibleStorage: flexibleStorage, } = await setupAllContracts({ accounts, - synths: ['sUSD', 'sETH', 'sEUR', 'sAUD', 'sBTC', 'iBTC', 'sTRX'], + synths: synths, contracts: [ // L1 specific 'Synthetix', @@ -3616,8 +3495,7 @@ contract('Exchanger (spec tests)', async accounts => { }, })); - // Send a price update to guarantee we're not stale. - oracle = account1; + await setupPriceAggregators(exchangeRates, owner, synths.map(toBytes32)); amountIssued = toUnit('1000'); @@ -3630,18 +3508,12 @@ contract('Exchanger (spec tests)', async accounts => { beforeEach(async () => { for (let i = 0; i < EXCHANGE_DYNAMIC_FEE_ROUNDS; i++) { - timestamp = await currentTime(); - await exchangeRates.updateRates( - [sAUD, sEUR, SNX, sETH, sBTC, iBTC], - ['0.5', '2', '1', '100', '5000', '5000'].map(toUnit), - timestamp, - { - from: oracle, - } - ); + const keys = [sAUD, sEUR, SNX, sETH, sBTC, iBTC]; + const rates = ['0.5', '2', '1', '100', '5000', '5000'].map(toUnit); + await setupPriceAggregators(exchangeRates, owner, keys); + await updateRates(keys, rates); } - // set a 0.5% exchange fee rate (1/200) exchangeFeeRate = toUnit('0.005'); await setExchangeFeeRateForSynths({ owner, @@ -3680,6 +3552,7 @@ contract('Exchanger (spec tests)', async accounts => { describe('With L2 configuration (MintableSynthetix, Exchanger, ExchangeRates)', () => { before(async () => { + const synths = ['sUSD', 'sETH', 'sEUR', 'sAUD', 'sBTC', 'iBTC', 'sTRX']; ({ Exchanger: exchanger, Synthetix: synthetix, @@ -3700,7 +3573,7 @@ contract('Exchanger (spec tests)', async accounts => { FlexibleStorage: flexibleStorage, } = await setupAllContracts({ accounts, - synths: ['sUSD', 'sETH', 'sEUR', 'sAUD', 'sBTC', 'iBTC', 'sTRX'], + synths: synths, contracts: [ // L2 specific 'MintableSynthetix', @@ -3720,8 +3593,7 @@ contract('Exchanger (spec tests)', async accounts => { ], })); - // Send a price update to guarantee we're not stale. - oracle = account1; + await setupPriceAggregators(exchangeRates, owner, synths.map(toBytes32)); amountIssued = toUnit('1000'); @@ -3734,15 +3606,10 @@ contract('Exchanger (spec tests)', async accounts => { beforeEach(async () => { for (let i = 0; i < EXCHANGE_DYNAMIC_FEE_ROUNDS; i++) { - timestamp = await currentTime(); - await exchangeRates.updateRates( - [sAUD, sEUR, SNX, sETH, sBTC, iBTC], - ['0.5', '2', '1', '100', '5000', '5000'].map(toUnit), - timestamp, - { - from: oracle, - } - ); + const keys = [sAUD, sEUR, SNX, sETH, sBTC, iBTC]; + const rates = ['0.5', '2', '1', '100', '5000', '5000'].map(toUnit); + await setupPriceAggregators(exchangeRates, owner, keys); + await updateRates(keys, rates); } // set a 0.5% exchange fee rate (1/200) diff --git a/test/contracts/FeePool.js b/test/contracts/FeePool.js index 2c14550911..7c22d40790 100644 --- a/test/contracts/FeePool.js +++ b/test/contracts/FeePool.js @@ -7,14 +7,7 @@ const { assert, addSnapshotBeforeRestoreAfterEach } = require('./common'); const FeePool = artifacts.require('FeePool'); const FlexibleStorage = artifacts.require('FlexibleStorage'); -const { - currentTime, - fastForward, - toUnit, - toPreciseUnit, - fromUnit, - multiplyDecimal, -} = require('../utils')(); +const { fastForward, toUnit, toPreciseUnit, fromUnit, multiplyDecimal } = require('../utils')(); const { ensureOnlyExpectedMutativeFunctions, @@ -24,6 +17,8 @@ const { decodedEventEqual, proxyThruTo, setExchangeFeeRateForSynths, + setupPriceAggregators, + updateAggregatorRates, } = require('./helpers'); const { setupAllContracts } = require('./setup'); @@ -34,17 +29,12 @@ const { } = require('../..'); contract('FeePool', async accounts => { - const [deployerAccount, owner, oracle, account1, account2] = accounts; + const [deployerAccount, owner, , account1, account2] = accounts; // Updates rates with defaults so they're not stale. const updateRatesWithDefaults = async () => { - let timestamp; for (let i = 0; i < EXCHANGE_DYNAMIC_FEE_ROUNDS; i++) { - timestamp = await currentTime(); - - await exchangeRates.updateRates([sAUD, SNX], ['0.5', '0.1'].map(toUnit), timestamp, { - from: oracle, - }); + await updateAggregatorRates(exchangeRates, [sAUD, SNX], ['0.5', '0.1'].map(toUnit)); } await debtCache.takeDebtSnapshot(); }; @@ -121,6 +111,8 @@ contract('FeePool', async accounts => { ], })); + await setupPriceAggregators(exchangeRates, owner, [sAUD]); + FEE_ADDRESS = await feePool.FEE_ADDRESS(); }); @@ -828,15 +820,10 @@ contract('FeePool', async accounts => { .concat(synths) .filter(key => key !== 'sUSD' && ![].concat(type).includes(key)); - const timestamp = await currentTime(); - - await exchangeRates.updateRates( + await updateAggregatorRates( + exchangeRates, ratesToUpdate.map(toBytes32), - ratesToUpdate.map(() => toUnit('1')), - timestamp, - { - from: oracle, - } + ratesToUpdate.map(() => toUnit('1')) ); await debtCache.takeDebtSnapshot(); }); @@ -1103,10 +1090,7 @@ contract('FeePool', async accounts => { // Increase the price so we start well and truly within our 20% ratio. const newRate = (await exchangeRates.rateForCurrency(SNX)).add(web3.utils.toBN('1')); - const timestamp = await currentTime(); - await exchangeRates.updateRates([SNX], [newRate], timestamp, { - from: oracle, - }); + await updateAggregatorRates(exchangeRates, [SNX], [newRate]); await debtCache.takeDebtSnapshot(); assert.equal(await feePool.isFeesClaimable(owner), true); @@ -1120,10 +1104,7 @@ contract('FeePool', async accounts => { const newRate = (await exchangeRates.rateForCurrency(SNX)).add( step.mul(web3.utils.toBN('1')) ); - const timestamp = await currentTime(); - await exchangeRates.updateRates([SNX], [newRate], timestamp, { - from: oracle, - }); + await updateAggregatorRates(exchangeRates, [SNX], [newRate]); await debtCache.takeDebtSnapshot(); const issuanceRatio = fromUnit(await feePool.issuanceRatio()); @@ -1145,10 +1126,7 @@ contract('FeePool', async accounts => { // Bump the rate down. const newRate = (await exchangeRates.rateForCurrency(SNX)).sub(step); - const timestamp = await currentTime(); - await exchangeRates.updateRates([SNX], [newRate], timestamp, { - from: oracle, - }); + await updateAggregatorRates(exchangeRates, [SNX], [newRate]); await debtCache.takeDebtSnapshot(); } }); @@ -1180,10 +1158,7 @@ contract('FeePool', async accounts => { const currentRate = await exchangeRates.rateForCurrency(SNX); const newRate = currentRate.sub(multiplyDecimal(currentRate, toUnit('0.15'))); - const timestamp = await currentTime(); - await exchangeRates.updateRates([SNX], [newRate], timestamp, { - from: oracle, - }); + await updateAggregatorRates(exchangeRates, [SNX], [newRate]); await debtCache.takeDebtSnapshot(); // fees available is unaffected but not claimable @@ -1223,10 +1198,7 @@ contract('FeePool', async accounts => { const currentRate = await exchangeRates.rateForCurrency(SNX); const newRate = currentRate.sub(multiplyDecimal(currentRate, toUnit('0.15'))); - const timestamp = await currentTime(); - await exchangeRates.updateRates([SNX], [newRate], timestamp, { - from: oracle, - }); + await updateAggregatorRates(exchangeRates, [SNX], [newRate]); await debtCache.takeDebtSnapshot(); // fees available is unaffected but not claimable @@ -1327,15 +1299,10 @@ contract('FeePool', async accounts => { .concat(synths) .filter(key => key !== 'sUSD' && ![].concat(type).includes(key)); - const timestamp = await currentTime(); - - await exchangeRates.updateRates( + await updateAggregatorRates( + exchangeRates, ratesToUpdate.map(toBytes32), - ratesToUpdate.map(() => toUnit('1')), - timestamp, - { - from: oracle, - } + ratesToUpdate.map(() => toUnit('1')) ); await debtCache.takeDebtSnapshot(); }); diff --git a/test/contracts/FeePoolState.js b/test/contracts/FeePoolState.js index 19542e204a..e88cc83c16 100644 --- a/test/contracts/FeePoolState.js +++ b/test/contracts/FeePoolState.js @@ -360,14 +360,11 @@ contract('FeePoolState', async accounts => { // TODO checks SynthetixState debt entry is same as stored FeePoolState Entry // it.only('should allow an issuer to issue max synths and track debt issuance in feePool', async function() { // // Send a price update to guarantee we're not depending on values from outside this test. - // const oracle = await exchangeRates.oracle(); - // const timestamp = await currentTime(); - // await exchangeRates.updateRates( + // await updateAggregatorRates( + // exchangeRates, // [sAUD, sEUR, SNX], - // ['0.5', '1.25', '0.1'].map(toUnit), - // timestamp, - // { from: oracle } + // ['0.5', '1.25', '0.1'].map(toUnit) // ); // // Give some SNX to account1 @@ -397,14 +394,11 @@ contract('FeePoolState', async accounts => { // it('should allow an issuer to issue synths many times and track debt issuance in feePool', async function() { // // Send a price update to guarantee we're not depending on values from outside this test. - // const oracle = await exchangeRates.oracle(); - // const timestamp = await currentTime(); - // await exchangeRates.updateRates( + // await updateAggregatorRates( + // exchangeRates, // [sAUD, sEUR, SNX], // ['0.5', '1.25', '0.1'].map(toUnit), - // timestamp, - // { from: oracle } // ); // // Give some SNX to account1 diff --git a/test/contracts/Issuer.js b/test/contracts/Issuer.js index d49754f592..febac572c5 100644 --- a/test/contracts/Issuer.js +++ b/test/contracts/Issuer.js @@ -25,6 +25,8 @@ const { onlyGivenAddressCanInvoke, ensureOnlyExpectedMutativeFunctions, setStatus, + setupPriceAggregators, + updateAggregatorRates, } = require('./helpers'); const { @@ -41,7 +43,7 @@ contract('Issuer (via Synthetix)', async accounts => { ); const synthKeys = [sUSD, sAUD, sEUR, sETH, SNX]; - const [, owner, oracle, account1, account2, account3, account6] = accounts; + const [, owner, , account1, account2, account3, account6] = accounts; let synthetix, systemStatus, @@ -56,7 +58,6 @@ contract('Issuer (via Synthetix)', async accounts => { sAUDContract, escrow, rewardEscrowV2, - timestamp, debtCache, issuer, synths, @@ -111,19 +112,18 @@ contract('Issuer (via Synthetix)', async accounts => { 'SynthRedeemer', ], })); + + await setupPriceAggregators(exchangeRates, owner, [sAUD, sEUR, sETH, ETH]); }); addSnapshotBeforeRestoreAfterEach(); beforeEach(async () => { for (let i = 0; i < EXCHANGE_DYNAMIC_FEE_ROUNDS; i++) { - timestamp = await currentTime(); - - await exchangeRates.updateRates( + await updateAggregatorRates( + exchangeRates, [sAUD, sEUR, SNX, sETH], - ['0.5', '1.25', '0.1', '200'].map(toUnit), - timestamp, - { from: oracle } + ['0.5', '1.25', '0.1', '200'].map(toUnit) ); } @@ -316,11 +316,10 @@ contract('Issuer (via Synthetix)', async accounts => { beforeEach(async () => { await fastForward(10); // Send a price update to give the synth rates - await exchangeRates.updateRates( + await updateAggregatorRates( + exchangeRates, [sAUD, sEUR, sETH, ETH, SNX], - ['0.5', '1.25', '100', '100', '2'].map(toUnit), - await currentTime(), - { from: oracle } + ['0.5', '1.25', '100', '100', '2'].map(toUnit) ); await debtCache.takeDebtSnapshot(); }); @@ -398,8 +397,7 @@ contract('Issuer (via Synthetix)', async accounts => { describe('debtBalance()', () => { it('should not change debt balance % if exchange rates change', async () => { let newAUDRate = toUnit('0.5'); - let timestamp = await currentTime(); - await exchangeRates.updateRates([sAUD], [newAUDRate], timestamp, { from: oracle }); + await updateAggregatorRates(exchangeRates, [sAUD], [newAUDRate]); await debtCache.takeDebtSnapshot(); await synthetix.transfer(account1, toUnit('20000'), { @@ -429,9 +427,8 @@ contract('Issuer (via Synthetix)', async accounts => { PRECISE_UNIT ); - timestamp = await currentTime(); newAUDRate = toUnit('1.85'); - await exchangeRates.updateRates([sAUD], [newAUDRate], timestamp, { from: oracle }); + await updateAggregatorRates(exchangeRates, [sAUD], [newAUDRate]); await debtCache.takeDebtSnapshot(); totalIssuedSynthsUSD = await synthetix.totalIssuedSynths(sUSD); @@ -671,6 +668,7 @@ contract('Issuer (via Synthetix)', async accounts => { })); await issuer.addSynth(synth.address, { from: owner }); + await setupPriceAggregators(exchangeRates, owner, [currencyKey]); }); it('should be able to query multiple synth addresses', async () => { @@ -722,9 +720,7 @@ contract('Issuer (via Synthetix)', async accounts => { describe('when the synth has a rate', () => { beforeEach(async () => { for (let i = 0; i < EXCHANGE_DYNAMIC_FEE_ROUNDS; i++) { - await exchangeRates.updateRates([currencyKey], [toUnit('2')], timestamp, { - from: oracle, - }); + await updateAggregatorRates(exchangeRates, [currencyKey], [toUnit('2')]); } }); @@ -743,17 +739,6 @@ contract('Issuer (via Synthetix)', async accounts => { const { numEntries } = await exchanger.settlementOwing(owner, currencyKey); assert.equal(numEntries, '0'); }); - describe('when the rate is also removed', () => { - beforeEach(async () => { - await exchangeRates.deleteRate(currencyKey, { from: oracle }); - }); - it('then settling works as expected', async () => { - await synthetix.settle(currencyKey); - - const { numEntries } = await exchanger.settlementOwing(owner, currencyKey); - assert.equal(numEntries, '0'); - }); - }); }); describe('when the same user exchanges out of the synth', () => { beforeEach(async () => { @@ -777,22 +762,6 @@ contract('Issuer (via Synthetix)', async accounts => { const { numEntries } = await exchanger.settlementOwing(owner, currencyKey); assert.equal(numEntries, '0'); }); - describe('when the rate is also removed', () => { - beforeEach(async () => { - await exchangeRates.deleteRate(currencyKey, { from: oracle }); - }); - it('then settling works as expected', async () => { - await synthetix.settle(currencyKey); - - const { numEntries } = await exchanger.settlementOwing(owner, currencyKey); - assert.equal(numEntries, '0'); - }); - it('then settling from the original currency works too', async () => { - await synthetix.settle(currencyKey); - const { numEntries } = await exchanger.settlementOwing(owner, currencyKey); - assert.equal(numEntries, '0'); - }); - }); }); }); }); @@ -1044,15 +1013,10 @@ contract('Issuer (via Synthetix)', async accounts => { .concat(synths) .filter(key => key !== 'sUSD' && ![].concat(type).includes(key)); - const timestamp = await currentTime(); - - await exchangeRates.updateRates( + await updateAggregatorRates( + exchangeRates, ratesToUpdate.map(toBytes32), - ratesToUpdate.map(() => toUnit('1')), - timestamp, - { - from: oracle, - } + ratesToUpdate.map(() => toUnit('1')) ); await debtCache.takeDebtSnapshot(); }); @@ -1276,15 +1240,10 @@ contract('Issuer (via Synthetix)', async accounts => { .concat(synths) .filter(key => key !== 'sUSD' && ![].concat(type).includes(key)); - const timestamp = await currentTime(); - - await exchangeRates.updateRates( + await updateAggregatorRates( + exchangeRates, ratesToUpdate.map(toBytes32), - ratesToUpdate.map(rate => toUnit(rate === 'SNX' ? '0.1' : '1')), - timestamp, - { - from: oracle, - } + ratesToUpdate.map(rate => toUnit(rate === 'SNX' ? '0.1' : '1')) ); await debtCache.takeDebtSnapshot(); }); @@ -1610,9 +1569,7 @@ contract('Issuer (via Synthetix)', async accounts => { from: owner, }); // Set SNX price to 1 - await exchangeRates.updateRates([SNX], ['1'].map(toUnit), timestamp, { - from: oracle, - }); + await updateAggregatorRates(exchangeRates, [SNX], ['1'].map(toUnit)); await debtCache.takeDebtSnapshot(); // Issue await synthetix.issueMaxSynths({ from: account1 }); @@ -1625,9 +1582,7 @@ contract('Issuer (via Synthetix)', async accounts => { describe('when the SNX price drops 50%', () => { let maxIssuableSynths; beforeEach(async () => { - await exchangeRates.updateRates([SNX], ['.5'].map(toUnit), timestamp, { - from: oracle, - }); + await updateAggregatorRates(exchangeRates, [SNX], ['.5'].map(toUnit)); await debtCache.takeDebtSnapshot(); maxIssuableSynths = await synthetix.maxIssuableSynths(account1); assert.equal(await feePool.isFeesClaimable(account1), false); @@ -1649,9 +1604,7 @@ contract('Issuer (via Synthetix)', async accounts => { describe('when the SNX price drops 10%', () => { let maxIssuableSynths; beforeEach(async () => { - await exchangeRates.updateRates([SNX], ['.9'].map(toUnit), timestamp, { - from: oracle, - }); + await updateAggregatorRates(exchangeRates, [SNX], ['.9'].map(toUnit)); await debtCache.takeDebtSnapshot(); maxIssuableSynths = await synthetix.maxIssuableSynths(account1); }); @@ -1672,9 +1625,7 @@ contract('Issuer (via Synthetix)', async accounts => { describe('when the SNX price drops 90%', () => { let maxIssuableSynths; beforeEach(async () => { - await exchangeRates.updateRates([SNX], ['.1'].map(toUnit), timestamp, { - from: oracle, - }); + await updateAggregatorRates(exchangeRates, [SNX], ['.1'].map(toUnit)); await debtCache.takeDebtSnapshot(); maxIssuableSynths = await synthetix.maxIssuableSynths(account1); }); @@ -1695,9 +1646,7 @@ contract('Issuer (via Synthetix)', async accounts => { describe('when the SNX price increases 100%', () => { let maxIssuableSynths; beforeEach(async () => { - await exchangeRates.updateRates([SNX], ['2'].map(toUnit), timestamp, { - from: oracle, - }); + await updateAggregatorRates(exchangeRates, [SNX], ['2'].map(toUnit)); await debtCache.takeDebtSnapshot(); maxIssuableSynths = await synthetix.maxIssuableSynths(account1); }); @@ -1784,9 +1733,7 @@ contract('Issuer (via Synthetix)', async accounts => { }); describe('and the sEUR price decreases by 20% to 1', () => { beforeEach(async () => { - await exchangeRates.updateRates([sEUR], ['1'].map(toUnit), timestamp, { - from: oracle, - }); + await updateAggregatorRates(exchangeRates, [sEUR], ['1'].map(toUnit)); await debtCache.takeDebtSnapshot(); }); describe('and 60s elapses', () => { @@ -2109,8 +2056,7 @@ contract('Issuer (via Synthetix)', async accounts => { it("should prevent more issuance if the user's collaterisation changes to be insufficient", async () => { // Set sEUR for purposes of this test - const timestamp1 = await currentTime(); - await exchangeRates.updateRates([sEUR], [toUnit('0.75')], timestamp1, { from: oracle }); + await updateAggregatorRates(exchangeRates, [sEUR], [toUnit('0.75')]); await debtCache.takeDebtSnapshot(); const issuedSynthetixs = web3.utils.toBN('200000'); @@ -2129,8 +2075,7 @@ contract('Issuer (via Synthetix)', async accounts => { await synthetix.exchange(sUSD, issuedSynths, sEUR, { from: account1 }); // Increase the value of sEUR relative to synthetix - const timestamp2 = await currentTime(); - await exchangeRates.updateRates([sEUR], [toUnit('1.10')], timestamp2, { from: oracle }); + await updateAggregatorRates(exchangeRates, [sEUR], [toUnit('1.1')]); await debtCache.takeDebtSnapshot(); await assert.revert( @@ -2431,7 +2376,7 @@ contract('Issuer (via Synthetix)', async accounts => { await synthetix.transfer(authoriser, toUnit('20000'), { from: owner, }); - await exchangeRates.updateRates([SNX], ['1'].map(toUnit), timestamp, { from: oracle }); + await updateAggregatorRates(exchangeRates, [SNX], [toUnit('1')]); await debtCache.takeDebtSnapshot(); }); describe('when not approved it should revert on', async () => { @@ -2518,9 +2463,7 @@ contract('Issuer (via Synthetix)', async accounts => { }); it('and calling burnSynthsToTargetOnBehalf() succeeds', async () => { // need the user to be undercollaterized for this to succeed - await exchangeRates.updateRates([SNX], ['0.001'].map(toUnit), timestamp, { - from: oracle, - }); + await updateAggregatorRates(exchangeRates, [SNX], [toUnit('0.001')]); await debtCache.takeDebtSnapshot(); await synthetix.burnSynthsToTargetOnBehalf(authoriser, { from: delegate }); }); @@ -2570,7 +2513,7 @@ contract('Issuer (via Synthetix)', async accounts => { }); it('should approveBurnOnBehalf and burnSynthsToTarget', async () => { await synthetix.issueMaxSynths({ from: authoriser }); - await exchangeRates.updateRates([SNX], ['0.01'].map(toUnit), timestamp, { from: oracle }); + await updateAggregatorRates(exchangeRates, [SNX], [toUnit('0.01')]); await debtCache.takeDebtSnapshot(); await delegateApprovals.approveBurnOnBehalf(delegate, { from: authoriser }); diff --git a/test/contracts/Liquidations.js b/test/contracts/Liquidations.js index 2f4bef7079..1255160b68 100644 --- a/test/contracts/Liquidations.js +++ b/test/contracts/Liquidations.js @@ -12,6 +12,7 @@ const { onlyGivenAddressCanInvoke, ensureOnlyExpectedMutativeFunctions, setStatus, + updateAggregatorRates, } = require('./helpers'); const { @@ -24,7 +25,7 @@ const FlexibleStorage = artifacts.require('FlexibleStorage'); contract('Liquidations', accounts => { const [sUSD, SNX] = ['sUSD', 'SNX'].map(toBytes32); - const [deployerAccount, owner, oracle, account1, alice, bob, carol, david] = accounts; + const [deployerAccount, owner, , account1, alice, bob, carol, david] = accounts; const week = 3600 * 24 * 7; const sUSD100 = toUnit('100'); @@ -38,8 +39,7 @@ contract('Liquidations', accounts => { systemStatus, feePoolState, debtCache, - issuer, - timestamp; + issuer; // run this once before all tests to prepare our environment, snapshots on beforeEach will take // care of resetting to this state @@ -86,16 +86,11 @@ contract('Liquidations', accounts => { }; const updateRatesWithDefaults = async () => { - timestamp = await currentTime(); - // SNX is 6 dolla await updateSNXPrice('6'); }; const updateSNXPrice = async rate => { - timestamp = await currentTime(); - await exchangeRates.updateRates([SNX], [rate].map(toUnit), timestamp, { - from: oracle, - }); + await updateAggregatorRates(exchangeRates, [SNX], [rate].map(toUnit)); await debtCache.takeDebtSnapshot(); }; diff --git a/test/contracts/MultiCollateralSynth.js b/test/contracts/MultiCollateralSynth.js index 60e92c1c7c..165a13186a 100644 --- a/test/contracts/MultiCollateralSynth.js +++ b/test/contracts/MultiCollateralSynth.js @@ -6,8 +6,13 @@ const { assert, addSnapshotBeforeRestoreAfterEach } = require('./common'); let MultiCollateralSynth; -const { onlyGivenAddressCanInvoke, ensureOnlyExpectedMutativeFunctions } = require('./helpers'); -const { toUnit, currentTime, fastForward } = require('../utils')(); +const { + onlyGivenAddressCanInvoke, + ensureOnlyExpectedMutativeFunctions, + setupPriceAggregators, + updateAggregatorRates, +} = require('./helpers'); +const { toUnit, fastForward } = require('../utils')(); const { toBytes32, constants: { ZERO_ADDRESS }, @@ -16,9 +21,10 @@ const { const { setupAllContracts } = require('./setup'); contract('MultiCollateralSynth', accounts => { - const [deployerAccount, owner, oracle, , account1] = accounts; + const [deployerAccount, owner, , , account1] = accounts; const sETH = toBytes32('sETH'); + const sBTC = toBytes32('sBTC'); let issuer, resolver, @@ -43,20 +49,6 @@ contract('MultiCollateralSynth', accounts => { }); }; - const updateRatesWithDefaults = async () => { - const timestamp = await currentTime(); - - await exchangeRates.updateRates([sETH], ['100'].map(toUnit), timestamp, { - from: oracle, - }); - - const sBTC = toBytes32('sBTC'); - - await exchangeRates.updateRates([sBTC], ['10000'].map(toUnit), timestamp, { - from: oracle, - }); - }; - before(async () => { MultiCollateralSynth = artifacts.require('MultiCollateralSynth'); }); @@ -91,6 +83,9 @@ contract('MultiCollateralSynth', accounts => { ], })); + await setupPriceAggregators(exchangeRates, owner, [sETH, sBTC]); + await updateAggregatorRates(exchangeRates, [sETH, sBTC], [100, 10000].map(toUnit)); + await managerState.setAssociatedContract(manager.address, { from: owner }); await manager.rebuildCache(); @@ -99,8 +94,6 @@ contract('MultiCollateralSynth', accounts => { await manager.addCollaterals([ceth.address], { from: owner }); - await updateRatesWithDefaults(); - await issuesUSDToAccount(toUnit(1000), owner); await debtCache.takeDebtSnapshot(); }); @@ -199,11 +192,9 @@ contract('MultiCollateralSynth', accounts => { describe('when multiCollateral is set to the owner', () => { beforeEach(async () => { - const timestamp = await currentTime(); - - await exchangeRates.updateRates([toBytes32('sXYZ')], [toUnit(5)], timestamp, { - from: oracle, - }); + const sXYZ = toBytes32('sXYZ'); + await setupPriceAggregators(exchangeRates, owner, [sXYZ]); + await updateAggregatorRates(exchangeRates, [sXYZ], [toUnit(5)]); }); describe('when multiCollateral tries to issue', () => { it('then it can issue new synths', async () => { diff --git a/test/contracts/NativeEtherWrapper.js b/test/contracts/NativeEtherWrapper.js index 920891fcb2..c305cb4327 100644 --- a/test/contracts/NativeEtherWrapper.js +++ b/test/contracts/NativeEtherWrapper.js @@ -4,13 +4,15 @@ const { contract, web3 } = require('hardhat'); const { assert, addSnapshotBeforeRestoreAfterEach } = require('./common'); -const { currentTime, toUnit } = require('../utils')(); +const { toUnit } = require('../utils')(); const { GAS_PRICE } = require('../../hardhat.config'); const { ensureOnlyExpectedMutativeFunctions, getDecodedLogs, decodedEventEqual, + setupPriceAggregators, + updateAggregatorRates, } = require('./helpers'); const { setupAllContracts } = require('./setup'); @@ -22,7 +24,7 @@ contract('NativeEtherWrapper', async accounts => { const synths = ['sUSD', 'sETH', 'ETH', 'SNX']; const [sETH, ETH] = ['sETH', 'ETH'].map(toBytes32); - const [, owner, oracle, , account1] = accounts; + const [, owner, , , account1] = accounts; let systemSettings, exchangeRates, @@ -30,8 +32,7 @@ contract('NativeEtherWrapper', async accounts => { sETHSynth, etherWrapper, nativeEtherWrapper, - weth, - timestamp; + weth; before(async () => { ({ @@ -63,12 +64,10 @@ contract('NativeEtherWrapper', async accounts => { ], })); - timestamp = await currentTime(); + await setupPriceAggregators(exchangeRates, owner, [sETH, ETH]); // Depot requires ETH rates - await exchangeRates.updateRates([sETH, ETH], ['1500', '1500'].map(toUnit), timestamp, { - from: oracle, - }); + await updateAggregatorRates(exchangeRates, [sETH, ETH], ['1500', '1500'].map(toUnit)); }); addSnapshotBeforeRestoreAfterEach(); diff --git a/test/contracts/PurgeableSynth.js b/test/contracts/PurgeableSynth.js index 1c5d53e550..d3f0137022 100644 --- a/test/contracts/PurgeableSynth.js +++ b/test/contracts/PurgeableSynth.js @@ -8,7 +8,7 @@ const TokenState = artifacts.require('TokenState'); const Proxy = artifacts.require('Proxy'); const PurgeableSynth = artifacts.require('PurgeableSynth'); -const { currentTime, fastForward, toUnit } = require('../utils')(); +const { fastForward, toUnit } = require('../utils')(); const { toBytes32, defaults: { EXCHANGE_DYNAMIC_FEE_ROUNDS }, @@ -21,6 +21,8 @@ const { onlyGivenAddressCanInvoke, ensureOnlyExpectedMutativeFunctions, setStatus, + setupPriceAggregators, + updateAggregatorRates, } = require('./helpers'); const { setupAllContracts } = require('./setup'); @@ -28,7 +30,7 @@ const { setupAllContracts } = require('./setup'); contract('PurgeableSynth', accounts => { const [sUSD, SNX, sAUD, iETH] = ['sUSD', 'SNX', 'sAUD', 'iETH'].map(toBytes32); const synthKeys = [sUSD, sAUD, iETH]; - const [deployerAccount, owner, oracle, , account1, account2] = accounts; + const [deployerAccount, owner, , , account1, account2] = accounts; let exchangeRates, exchanger, @@ -37,7 +39,6 @@ contract('PurgeableSynth', accounts => { sAUDContract, iETHContract, systemStatus, - timestamp, addressResolver, debtCache, issuer; @@ -72,7 +73,7 @@ contract('PurgeableSynth', accounts => { ], })); - timestamp = await currentTime(); + await setupPriceAggregators(exchangeRates, owner, [sAUD, iETH]); }); beforeEach(async () => { @@ -157,13 +158,10 @@ contract('PurgeableSynth', accounts => { describe("when there's a price for the purgeable synth", () => { beforeEach(async () => { for (let i = 0; i < EXCHANGE_DYNAMIC_FEE_ROUNDS; i++) { - await exchangeRates.updateRates( + await updateAggregatorRates( + exchangeRates, [sAUD, SNX, iETH], - ['0.5', '1', '170'].map(toUnit), - timestamp, - { - from: oracle, - } + ['0.5', '1', '170'].map(toUnit) ); } await debtCache.takeDebtSnapshot(); @@ -210,9 +208,7 @@ contract('PurgeableSynth', accounts => { }); describe('when rates are received', () => { beforeEach(async () => { - await exchangeRates.updateRates([iETH], ['170'].map(toUnit), await currentTime(), { - from: oracle, - }); + await updateAggregatorRates(exchangeRates, [iETH], ['170'].map(toUnit)); await debtCache.takeDebtSnapshot(); }); it('then purge() still works as expected', async () => { @@ -326,9 +322,7 @@ contract('PurgeableSynth', accounts => { describe('when sAUD has a price', () => { beforeEach(async () => { for (let i = 0; i < EXCHANGE_DYNAMIC_FEE_ROUNDS; i++) { - await exchangeRates.updateRates([sAUD], ['0.776845993'].map(toUnit), timestamp, { - from: oracle, - }); + await updateAggregatorRates(exchangeRates, [sAUD], ['0.776845993'].map(toUnit)); } await debtCache.takeDebtSnapshot(); }); diff --git a/test/contracts/RewardsIntegrationTests.js b/test/contracts/RewardsIntegrationTests.js index 84f280bb0d..f50af7f70c 100644 --- a/test/contracts/RewardsIntegrationTests.js +++ b/test/contracts/RewardsIntegrationTests.js @@ -4,14 +4,16 @@ const { contract, web3 } = require('hardhat'); const { assert, addSnapshotBeforeRestoreAfterEach } = require('./common'); -const { - toBytes32, - defaults: { EXCHANGE_DYNAMIC_FEE_ROUNDS }, -} = require('../..'); +const { toBytes32 } = require('../..'); -const { currentTime, fastForward, toUnit, toPreciseUnit, multiplyDecimal } = require('../utils')(); +const { fastForward, toUnit, toPreciseUnit, multiplyDecimal } = require('../utils')(); -const { setExchangeFeeRateForSynths } = require('./helpers'); +const { + setExchangeFeeRateForSynths, + setupPriceAggregators, + updateRatesWithDefaults, + updateAggregatorRates, +} = require('./helpers'); const { setupAllContracts } = require('./setup'); @@ -63,25 +65,6 @@ contract('Rewards Integration Tests', accounts => { const synthKeys = [sUSD, sAUD, sEUR, sBTC, iBTC, sETH, ETH]; - // Updates rates with defaults so they're not stale. - const updateRatesWithDefaults = async () => { - let timestamp; - for (let i = 0; i < EXCHANGE_DYNAMIC_FEE_ROUNDS; i++) { - timestamp = await currentTime(); - - await exchangeRates.updateRates( - [sAUD, sEUR, SNX, sBTC, iBTC, sETH, ETH], - ['0.5', '1.25', '0.1', '5000', '4000', '172', '172'].map(toUnit), - timestamp, - { - from: oracle, - } - ); - } - - await debtCache.takeDebtSnapshot(); - }; - const fastForwardAndCloseFeePeriod = async () => { const feePeriodDuration = await feePool.feePeriodDuration(); // Note: add on a small addition of 10 seconds - this seems to have @@ -93,12 +76,12 @@ contract('Rewards Integration Tests', accounts => { // Fast forward another day after feePeriod closed before minting await fastForward(DAY + 10); - await updateRatesWithDefaults(); + await updateRatesWithDefaults({ exchangeRates, owner, debtCache }); }; const fastForwardAndUpdateRates = async seconds => { await fastForward(seconds); - await updateRatesWithDefaults(); + await updateRatesWithDefaults({ exchangeRates, owner, debtCache }); }; const exchangeFeeRate = toUnit('0.003'); // 30 bips @@ -136,7 +119,7 @@ contract('Rewards Integration Tests', accounts => { // const YEAR = 31556926; // ACCOUNTS - const [deployerAccount, owner, oracle, feeAuthority, account1, account2, account3] = accounts; + const [deployerAccount, owner, , feeAuthority, account1, account2, account3] = accounts; // VARIABLES let feePool, @@ -187,6 +170,8 @@ contract('Rewards Integration Tests', accounts => { ], })); + await setupPriceAggregators(exchangeRates, owner, [sAUD, sEUR, sBTC, iBTC, sETH, ETH]); + MINTER_SNX_REWARD = await supplySchedule.minterReward(); await setExchangeFeeRateForSynths({ @@ -633,10 +618,7 @@ contract('Rewards Integration Tests', accounts => { ); // Increase sBTC price by 100% - const timestamp = await currentTime(); - await exchangeRates.updateRates([sBTC], ['10000'].map(toUnit), timestamp, { - from: oracle, - }); + await updateAggregatorRates(exchangeRates, [sBTC], ['10000'].map(toUnit)); await debtCache.takeDebtSnapshot(); // Account 3 (enters the system and) mints 10K sUSD (minus half of an exchange fee - to balance the fact @@ -934,10 +916,7 @@ contract('Rewards Integration Tests', accounts => { const currentRate = await exchangeRates.rateForCurrency(SNX); const newRate = currentRate.sub(multiplyDecimal(currentRate, toUnit('0.009'))); - const timestamp = await currentTime(); - await exchangeRates.updateRates([SNX], [newRate], timestamp, { - from: oracle, - }); + await updateAggregatorRates(exchangeRates, [SNX], [newRate]); // we will be able to claim fees assert.equal(await feePool.isFeesClaimable(account1), true); @@ -959,11 +938,7 @@ contract('Rewards Integration Tests', accounts => { it('should block user from claiming fees and rewards when users claim rewards >10% threshold collateralisation ratio', async () => { // But if the price of SNX decreases a lot... const newRate = (await exchangeRates.rateForCurrency(SNX)).sub(toUnit('0.09')); - const timestamp = await currentTime(); - await exchangeRates.updateRates([SNX], [newRate], timestamp, { - from: oracle, - }); - + await updateAggregatorRates(exchangeRates, [SNX], [newRate]); // we will fall into the >100% bracket assert.equal(await feePool.isFeesClaimable(account1), false); diff --git a/test/contracts/ShortingRewards.js b/test/contracts/ShortingRewards.js index 4c6de47f63..4915282112 100644 --- a/test/contracts/ShortingRewards.js +++ b/test/contracts/ShortingRewards.js @@ -5,7 +5,12 @@ const { toBytes32, constants: { ZERO_ADDRESS }, } = require('../..'); -const { onlyGivenAddressCanInvoke, ensureOnlyExpectedMutativeFunctions } = require('./helpers'); +const { + onlyGivenAddressCanInvoke, + ensureOnlyExpectedMutativeFunctions, + setupPriceAggregators, + updateAggregatorRates, +} = require('./helpers'); const { assert, addSnapshotBeforeRestoreAfterEach } = require('./common'); const { setupAllContracts, setupContract } = require('./setup'); const { currentTime, toUnit, fastForward } = require('../utils')(); @@ -17,7 +22,7 @@ contract('ShortingRewards', accounts => { const [ deployerAccount, owner, - oracle, + , authority, rewardEscrowAddress, account1, @@ -27,7 +32,9 @@ contract('ShortingRewards', accounts => { const sUSD = toBytes32('sUSD'); const sETH = toBytes32('sETH'); + const iETH = toBytes32('iETH'); const sBTC = toBytes32('sBTC'); + const iBTC = toBytes32('iBTC'); // Synthetix is the rewardsToken let rewardsToken, @@ -57,33 +64,11 @@ contract('ShortingRewards', accounts => { return event.args.id; }; - const updateRatesWithDefaults = async () => { - const timestamp = await currentTime(); - - await exchangeRates.updateRates([sETH], ['100'].map(toUnit), timestamp, { - from: oracle, - }); - - const sBTC = toBytes32('sBTC'); - - await exchangeRates.updateRates([sBTC], ['10000'].map(toUnit), timestamp, { - from: oracle, - }); - }; - const setRewardsTokenExchangeRate = async ({ rateStaleDays } = { rateStaleDays: 7 }) => { const rewardsTokenIdentifier = await rewardsToken.symbol(); await systemSettings.setRateStalePeriod(DAY * rateStaleDays, { from: owner }); - const updatedTime = await currentTime(); - await exchangeRates.updateRates( - [toBytes32(rewardsTokenIdentifier)], - [toUnit('2')], - updatedTime, - { - from: oracle, - } - ); + await updateAggregatorRates(exchangeRates, [toBytes32(rewardsTokenIdentifier)], [toUnit('2')]); assert.equal(await exchangeRates.rateIsStale(toBytes32(rewardsTokenIdentifier)), false); }; @@ -150,6 +135,8 @@ contract('ShortingRewards', accounts => { ], })); + await setupPriceAggregators(exchangeRates, owner, [sBTC, iBTC, sETH, iETH]); + managerState = await CollateralManagerState.new(owner, ZERO_ADDRESS, { from: deployerAccount }); const maxDebt = toUnit(10000000); @@ -232,7 +219,7 @@ contract('ShortingRewards', accounts => { }); beforeEach(async () => { - await updateRatesWithDefaults(); + await updateAggregatorRates(exchangeRates, [sETH, sBTC], [100, 10000].map(toUnit)); await issuesUSDToAccount(toUnit(100000), owner); await issuesBTCtoAccount(toUnit(10), owner); @@ -443,10 +430,7 @@ contract('ShortingRewards', accounts => { await fastForward(DAY); // Make the short so underwater it must get closed. - const timestamp = await currentTime(); - await exchangeRates.updateRates([sBTC], ['20000'].map(toUnit), timestamp, { - from: oracle, - }); + await updateAggregatorRates(exchangeRates, [sBTC], ['20000'].map(toUnit)); // close the loan via liquidation await issuesBTCtoAccount(toUnit(1), account2); @@ -465,10 +449,7 @@ contract('ShortingRewards', accounts => { await fastForward(DAY); // Make the short so underwater it must get closed. - const timestamp = await currentTime(); - await exchangeRates.updateRates([sBTC], ['20000'].map(toUnit), timestamp, { - from: oracle, - }); + await updateAggregatorRates(exchangeRates, [sBTC], ['20000'].map(toUnit)); // close the loan via liquidation await issuesBTCtoAccount(toUnit(1), account2); diff --git a/test/contracts/StakingRewards.js b/test/contracts/StakingRewards.js index 59a94833ef..ae19180d18 100644 --- a/test/contracts/StakingRewards.js +++ b/test/contracts/StakingRewards.js @@ -2,7 +2,12 @@ const { contract } = require('hardhat'); const { toBN } = require('web3-utils'); const { toBytes32 } = require('../..'); -const { onlyGivenAddressCanInvoke, ensureOnlyExpectedMutativeFunctions } = require('./helpers'); +const { + onlyGivenAddressCanInvoke, + ensureOnlyExpectedMutativeFunctions, + setupPriceAggregators, + updateAggregatorRates, +} = require('./helpers'); const { assert, addSnapshotBeforeRestoreAfterEach } = require('./common'); const { mockToken, setupAllContracts, setupContract } = require('./setup'); const { currentTime, toUnit, fastForward } = require('../utils')(); @@ -11,7 +16,7 @@ contract('StakingRewards', accounts => { const [ , owner, - oracle, + , authority, rewardEscrowAddress, stakingAccount1, @@ -33,18 +38,12 @@ contract('StakingRewards', accounts => { const setRewardsTokenExchangeRate = async ({ rateStaleDays } = { rateStaleDays: 7 }) => { const rewardsTokenIdentifier = await rewardsToken.symbol(); + const tokenKey = toBytes32(rewardsTokenIdentifier); await systemSettings.setRateStalePeriod(DAY * rateStaleDays, { from: owner }); - const updatedTime = await currentTime(); - await exchangeRates.updateRates( - [toBytes32(rewardsTokenIdentifier)], - [toUnit('2')], - updatedTime, - { - from: oracle, - } - ); - assert.equal(await exchangeRates.rateIsStale(toBytes32(rewardsTokenIdentifier)), false); + await setupPriceAggregators(exchangeRates, owner, [tokenKey]); + await updateAggregatorRates(exchangeRates, [tokenKey], [toUnit('2')]); + assert.equal(await exchangeRates.rateIsStale(tokenKey), false); }; addSnapshotBeforeRestoreAfterEach(); diff --git a/test/contracts/Synth.js b/test/contracts/Synth.js index a9e1833265..bae72386bc 100644 --- a/test/contracts/Synth.js +++ b/test/contracts/Synth.js @@ -9,12 +9,14 @@ const Synth = artifacts.require('Synth'); const { setupAllContracts } = require('./setup'); -const { currentTime, toUnit, bytesToString } = require('../utils')(); +const { toUnit, bytesToString } = require('../utils')(); const { issueSynthsToUser, ensureOnlyExpectedMutativeFunctions, onlyGivenAddressCanInvoke, setStatus, + setupPriceAggregators, + updateAggregatorRates, } = require('./helpers'); const { @@ -26,7 +28,7 @@ const { contract('Synth', async accounts => { const [sUSD, SNX, sEUR] = ['sUSD', 'SNX', 'sEUR'].map(toBytes32); - const [deployerAccount, owner, oracle, , account1, account2] = accounts; + const [deployerAccount, owner, , , account1, account2] = accounts; let feePool, FEE_ADDRESS, @@ -72,20 +74,17 @@ contract('Synth', async accounts => { ], })); + await setupPriceAggregators(exchangeRates, owner, [sEUR]); + FEE_ADDRESS = await feePool.FEE_ADDRESS(); }); addSnapshotBeforeRestoreAfterEach(); beforeEach(async () => { - let timestamp; - - // Send a price update to guarantee we're not stale. for (let i = 0; i < EXCHANGE_DYNAMIC_FEE_ROUNDS; i++) { - timestamp = await currentTime(); - await exchangeRates.updateRates([SNX], ['0.1'].map(toUnit), timestamp, { - from: oracle, - }); + // Send a price update to guarantee we're not stale. + await updateAggregatorRates(exchangeRates, [SNX], ['0.1'].map(toUnit)); } await debtCache.takeDebtSnapshot(); @@ -737,14 +736,9 @@ contract('Synth', async accounts => { contracts: [{ contract: 'Synth', properties: { currencyKey: sEUR } }], })); - let timestamp; - - // Send a price update to guarantee we're not stale. for (let i = 0; i < EXCHANGE_DYNAMIC_FEE_ROUNDS; i++) { - timestamp = await currentTime(); - await exchangeRates.updateRates([sEUR], ['1'].map(toUnit), timestamp, { - from: oracle, - }); + // Send a price update to guarantee we're not stale. + await updateAggregatorRates(exchangeRates, [sEUR], ['1'].map(toUnit)); } await debtCache.takeDebtSnapshot(); }); diff --git a/test/contracts/SynthUtil.js b/test/contracts/SynthUtil.js index afbd47d4ba..6c8506b9f3 100644 --- a/test/contracts/SynthUtil.js +++ b/test/contracts/SynthUtil.js @@ -6,16 +6,20 @@ const { toBytes32, defaults: { EXCHANGE_DYNAMIC_FEE_ROUNDS }, } = require('../..'); -const { toUnit, currentTime } = require('../utils')(); -const { setExchangeFeeRateForSynths } = require('./helpers'); +const { toUnit } = require('../utils')(); +const { + setExchangeFeeRateForSynths, + setupPriceAggregators, + updateAggregatorRates, +} = require('./helpers'); const { setupAllContracts } = require('./setup'); contract('SynthUtil', accounts => { - const [, ownerAccount, oracle, account2] = accounts; - let synthUtil, sUSDContract, synthetix, exchangeRates, timestamp, systemSettings, debtCache; + const [, ownerAccount, , account2] = accounts; + let synthUtil, sUSDContract, synthetix, exchangeRates, systemSettings, debtCache; - const [sUSD, sBTC, iBTC] = ['sUSD', 'sBTC', 'iBTC'].map(toBytes32); + const [sUSD, sBTC, iBTC, SNX] = ['sUSD', 'sBTC', 'iBTC', 'SNX'].map(toBytes32); const synthKeys = [sUSD, sBTC, iBTC]; const synthPrices = [toUnit('1'), toUnit('5000'), toUnit('5000')]; @@ -45,16 +49,19 @@ contract('SynthUtil', accounts => { 'RewardEscrowV2', // required for issuer._collateral to read collateral ], })); + + await setupPriceAggregators(exchangeRates, ownerAccount, [sBTC, iBTC]); }); addSnapshotBeforeRestoreAfterEach(); beforeEach(async () => { for (let i = 0; i < EXCHANGE_DYNAMIC_FEE_ROUNDS; i++) { - timestamp = await currentTime(); - await exchangeRates.updateRates([sBTC, iBTC], ['5000', '5000'].map(toUnit), timestamp, { - from: oracle, - }); + await updateAggregatorRates( + exchangeRates, + [sBTC, iBTC, SNX], + ['5000', '5000', '0.2'].map(toUnit) + ); } await debtCache.takeDebtSnapshot(); diff --git a/test/contracts/Synthetix.js b/test/contracts/Synthetix.js index a7a5159c6d..a7de48399f 100644 --- a/test/contracts/Synthetix.js +++ b/test/contracts/Synthetix.js @@ -15,6 +15,7 @@ const { fastForwardTo, toUnit, fromUnit } = require('../utils')(); const { ensureOnlyExpectedMutativeFunctions, updateRatesWithDefaults, + setupPriceAggregators, setStatus, } = require('./helpers'); @@ -34,7 +35,6 @@ contract('Synthetix', async accounts => { supplySchedule, rewardEscrow, rewardEscrowV2, - oracle, addressResolver, systemStatus, sUSDContract, @@ -72,8 +72,7 @@ contract('Synthetix', async accounts => { ], })); - // Send a price update to guarantee we're not stale. - oracle = account1; + await setupPriceAggregators(exchangeRates, owner, [sAUD, sEUR, sETH]); }); addSnapshotBeforeRestoreAfterEach(); @@ -187,7 +186,7 @@ contract('Synthetix', async accounts => { // ensure mint() can succeed by default const week234 = INFLATION_START_DATE + WEEK * 234; await fastForwardTo(new Date(week234 * 1000)); - await updateRatesWithDefaults({ exchangeRates, oracle, debtCache }); + await updateRatesWithDefaults({ exchangeRates, owner, debtCache }); }); ['System', 'Issuance'].forEach(section => { describe(`when ${section} is suspended`, () => { @@ -212,7 +211,7 @@ contract('Synthetix', async accounts => { // fast forward EVM to end of inflation supply decay at week 234 const week234 = INFLATION_START_DATE + WEEK * 234; await fastForwardTo(new Date(week234 * 1000)); - await updateRatesWithDefaults({ exchangeRates, oracle, debtCache }); + await updateRatesWithDefaults({ exchangeRates, owner, debtCache }); const existingSupply = await synthetix.totalSupply(); const mintableSupply = await supplySchedule.mintableSupply(); @@ -248,7 +247,7 @@ contract('Synthetix', async accounts => { // fast forward EVM to Week 3 in of the inflationary supply const weekThree = INFLATION_START_DATE + WEEK * 2 + DAY; await fastForwardTo(new Date(weekThree * 1000)); - await updateRatesWithDefaults({ exchangeRates, oracle, debtCache }); + await updateRatesWithDefaults({ exchangeRates, owner, debtCache }); const existingSupply = await synthetix.totalSupply(); const mintableSupply = await supplySchedule.mintableSupply(); @@ -285,7 +284,7 @@ contract('Synthetix', async accounts => { // fast forward EVM to Week 2 in Year 3 schedule starting at UNIX 1583971200+ const weekThirtyNine = INFLATION_START_DATE + WEEK * 39 + DAY; await fastForwardTo(new Date(weekThirtyNine * 1000)); - await updateRatesWithDefaults({ exchangeRates, oracle, debtCache }); + await updateRatesWithDefaults({ exchangeRates, owner, debtCache }); const existingTotalSupply = await synthetix.totalSupply(); const currentRewardEscrowBalance = await synthetix.balanceOf(rewardEscrow.address); @@ -312,7 +311,7 @@ contract('Synthetix', async accounts => { // fast forward EVM to week 236 const september142023 = INFLATION_START_DATE + 236 * WEEK + DAY; await fastForwardTo(new Date(september142023 * 1000)); - await updateRatesWithDefaults({ exchangeRates, oracle, debtCache }); + await updateRatesWithDefaults({ exchangeRates, owner, debtCache }); const existingTotalSupply = await synthetix.totalSupply(); const mintableSupply = await supplySchedule.mintableSupply(); @@ -334,7 +333,7 @@ contract('Synthetix', async accounts => { // fast forward EVM to week 236 const week573 = INFLATION_START_DATE + 572 * WEEK + DAY; await fastForwardTo(new Date(week573 * 1000)); - await updateRatesWithDefaults({ exchangeRates, oracle, debtCache }); + await updateRatesWithDefaults({ exchangeRates, owner, debtCache }); const existingTotalSupply = await synthetix.totalSupply(); const mintableSupply = await supplySchedule.mintableSupply(); @@ -356,7 +355,7 @@ contract('Synthetix', async accounts => { // fast forward EVM to Week 3 in Year 2 schedule starting at UNIX 1553040000+ const weekThree = INFLATION_START_DATE + 2 * WEEK + 1 * DAY; await fastForwardTo(new Date(weekThree * 1000)); - await updateRatesWithDefaults({ exchangeRates, oracle, debtCache }); + await updateRatesWithDefaults({ exchangeRates, owner, debtCache }); let existingTotalSupply = await synthetix.totalSupply(); let mintableSupply = await supplySchedule.mintableSupply(); @@ -370,7 +369,7 @@ contract('Synthetix', async accounts => { // fast forward EVM to Week 4 const weekFour = weekThree + 1 * WEEK + 1 * DAY; await fastForwardTo(new Date(weekFour * 1000)); - await updateRatesWithDefaults({ exchangeRates, oracle, debtCache }); + await updateRatesWithDefaults({ exchangeRates, owner, debtCache }); existingTotalSupply = await synthetix.totalSupply(); mintableSupply = await supplySchedule.mintableSupply(); @@ -386,7 +385,7 @@ contract('Synthetix', async accounts => { // fast forward EVM to Week 3 of inflation const weekThree = INFLATION_START_DATE + 2 * WEEK + DAY; await fastForwardTo(new Date(weekThree * 1000)); - await updateRatesWithDefaults({ exchangeRates, oracle, debtCache }); + await updateRatesWithDefaults({ exchangeRates, owner, debtCache }); const existingTotalSupply = await synthetix.totalSupply(); const mintableSupply = await supplySchedule.mintableSupply(); @@ -444,7 +443,7 @@ contract('Synthetix', async accounts => { contractExample = await MockThirdPartyExchangeContract.new(addressResolver.address); // ensure rates are set - await updateRatesWithDefaults({ exchangeRates, oracle, debtCache }); + await updateRatesWithDefaults({ exchangeRates, owner, debtCache }); // issue sUSD from the owner await synthetix.issueSynths(amountOfsUSD, { from: owner }); diff --git a/test/contracts/TradingRewards.spec.js b/test/contracts/TradingRewards.spec.js index 69bf8fcf9e..ff3e7f80d5 100644 --- a/test/contracts/TradingRewards.spec.js +++ b/test/contracts/TradingRewards.spec.js @@ -2,8 +2,14 @@ const { contract, web3 } = require('hardhat'); const { toBN } = web3.utils; const { assert, addSnapshotBeforeRestoreAfter } = require('./common'); const { setupAllContracts } = require('./setup'); -const { currentTime, toUnit, multiplyDecimal } = require('../utils')(); -const { setExchangeFeeRateForSynths, getDecodedLogs, decodedEventEqual } = require('./helpers'); +const { toUnit, multiplyDecimal } = require('../utils')(); +const { + setExchangeFeeRateForSynths, + getDecodedLogs, + decodedEventEqual, + setupPriceAggregators, + updateAggregatorRates, +} = require('./helpers'); const { toBytes32, defaults: { EXCHANGE_DYNAMIC_FEE_ROUNDS }, @@ -18,9 +24,9 @@ const { contract('TradingRewards', accounts => { const [, owner, account1] = accounts; - const synths = ['sUSD', 'sETH', 'sBTC']; + const synths = ['sUSD', 'sETH', 'sBTC', 'SNX']; const synthKeys = synths.map(toBytes32); - const [sUSD, sETH, sBTC] = synthKeys; + const [sUSD, sETH, sBTC, SNX] = synthKeys; let synthetix, exchanger, exchangeRates, rewards, resolver, systemSettings; let sUSDContract, sETHContract, sBTCContract; @@ -34,6 +40,7 @@ contract('TradingRewards', accounts => { const rates = { [sETH]: toUnit('100'), [sBTC]: toUnit('12000'), + [SNX]: toUnit('0.2'), }; let feesPaidUSD; @@ -94,6 +101,8 @@ contract('TradingRewards', accounts => { 'CollateralManager', ], })); + + await setupPriceAggregators(exchangeRates, owner, [sETH, sBTC]); }); before('BRRRRRR', async () => { @@ -103,14 +112,8 @@ contract('TradingRewards', accounts => { }); before('set exchange rates', async () => { - const oracle = account1; - let timestamp; for (let i = 0; i < EXCHANGE_DYNAMIC_FEE_ROUNDS; i++) { - timestamp = await currentTime(); - - await exchangeRates.updateRates([sETH, sBTC], Object.values(rates), timestamp, { - from: oracle, - }); + await updateAggregatorRates(exchangeRates, [sETH, sBTC, SNX], Object.values(rates)); } await setExchangeFeeRateForSynths({ diff --git a/test/contracts/Wrapper.js b/test/contracts/Wrapper.js index 10ca44eb9d..4247835683 100644 --- a/test/contracts/Wrapper.js +++ b/test/contracts/Wrapper.js @@ -4,12 +4,14 @@ const { contract, artifacts, web3 } = require('hardhat'); const { assert, addSnapshotBeforeRestoreAfterEach } = require('./common'); -const { currentTime, toUnit } = require('../utils')(); +const { toUnit } = require('../utils')(); const { ensureOnlyExpectedMutativeFunctions, getDecodedLogs, decodedEventEqual, + setupPriceAggregators, + updateAggregatorRates, } = require('./helpers'); const { setupAllContracts } = require('./setup'); @@ -23,7 +25,7 @@ contract('Wrapper', async accounts => { const ONE = toBN('1'); - const [, owner, oracle, , account1] = accounts; + const [, owner, , , account1] = accounts; let systemSettings, feePool, @@ -35,8 +37,7 @@ contract('Wrapper', async accounts => { sETHSynth, wrapperFactory, etherWrapper, - weth, - timestamp; + weth; const calculateETHToUSD = async feesInETH => { // Ask the Depot how many sUSD I will get for this ETH @@ -112,12 +113,8 @@ contract('Wrapper', async accounts => { from: owner, }); - timestamp = await currentTime(); - - // Depot requires ETH rates - await exchangeRates.updateRates([sETH, ETH], ['1500', '1500'].map(toUnit), timestamp, { - from: oracle, - }); + await setupPriceAggregators(exchangeRates, owner, [sETH, ETH]); + await updateAggregatorRates(exchangeRates, [sETH, ETH], ['1500', '1500'].map(toUnit)); }); addSnapshotBeforeRestoreAfterEach(); diff --git a/test/contracts/WrapperFactory.js b/test/contracts/WrapperFactory.js index 5fd83537bc..8c0552ebee 100644 --- a/test/contracts/WrapperFactory.js +++ b/test/contracts/WrapperFactory.js @@ -4,13 +4,15 @@ const { contract, artifacts, web3 } = require('hardhat'); const { assert, addSnapshotBeforeRestoreAfterEach } = require('./common'); -const { currentTime, toUnit } = require('../utils')(); +const { toUnit } = require('../utils')(); const { ensureOnlyExpectedMutativeFunctions, onlyGivenAddressCanInvoke, getDecodedLogs, decodedEventEqual, + setupPriceAggregators, + updateAggregatorRates, } = require('./helpers'); const { setupAllContracts } = require('./setup'); @@ -22,7 +24,7 @@ contract('WrapperFactory', async accounts => { const synths = ['sUSD', 'sETH', 'ETH', 'SNX']; const [sETH, ETH] = ['sETH', 'ETH'].map(toBytes32); - const [, owner, oracle, , account1] = accounts; + const [, owner, , , account1] = accounts; let addressResolver, flexibleStorage, @@ -32,8 +34,7 @@ contract('WrapperFactory', async accounts => { FEE_ADDRESS, sUSDSynth, wrapperFactory, - weth, - timestamp; + weth; before(async () => { ({ @@ -66,12 +67,10 @@ contract('WrapperFactory', async accounts => { })); FEE_ADDRESS = await feePool.FEE_ADDRESS(); - timestamp = await currentTime(); // Depot requires ETH rates - await exchangeRates.updateRates([sETH, ETH], ['1500', '1500'].map(toUnit), timestamp, { - from: oracle, - }); + await setupPriceAggregators(exchangeRates, owner, [sETH, ETH]); + await updateAggregatorRates(exchangeRates, [sETH, ETH], ['1500', '1500'].map(toUnit)); }); addSnapshotBeforeRestoreAfterEach(); diff --git a/test/contracts/helpers.js b/test/contracts/helpers.js index 8dac99414d..dd391cee55 100644 --- a/test/contracts/helpers.js +++ b/test/contracts/helpers.js @@ -12,6 +12,48 @@ const { constants: { ZERO_ADDRESS, ZERO_BYTES32 }, } = require('../..'); +const MockAggregator = artifacts.require('MockAggregatorV2V3'); + +/// utility function to setup price aggregators +/// @param exchangeRates instance of ExchangeRates contract +/// @param owner owner account of exchangeRates contract for adding an aggregator +/// @param keys array of bytes32 currency keys +/// @param decimalsArray optional array of ints for each key, defaults to 18 decimals +async function setupPriceAggregators(exchangeRates, owner, keys, decimalsArray = []) { + let aggregator; + for (let i = 0; i < keys.length; i++) { + aggregator = await MockAggregator.new({ from: owner }); + await aggregator.setDecimals(decimalsArray.length > 0 ? decimalsArray[i] : 18); + await exchangeRates.addAggregator(keys[i], aggregator.address, { from: owner }); + } +} + +/// same as setupPriceAggregators, but checks if an aggregator for that currency is already setup up +async function setupMissingPriceAggregators(exchangeRates, owner, keys) { + const missingKeys = []; + for (let i = 0; i < keys.length; i++) { + if ((await exchangeRates.aggregators(keys[i])) === ZERO_ADDRESS) { + missingKeys.push(keys[i]); + } + } + await setupPriceAggregators(exchangeRates, owner, missingKeys); +} +// utility function update rates for aggregators that are already set up +/// @param exchangeRates instance of ExchangeRates contract +/// @param owner owner account of exchangeRates contract for adding an aggregator +/// @param keys array of bytes32 currency keys +/// @param rates array of BN rates +/// @param timestamp optional timestamp for the update, currentTime() is used by default +async function updateAggregatorRates(exchangeRates, keys, rates, timestamp = undefined) { + timestamp = timestamp || (await currentTime()); + for (let i = 0; i < keys.length; i++) { + const aggregatorAddress = await exchangeRates.aggregators(keys[i]); + const aggregator = await MockAggregator.at(aggregatorAddress); + // set the rate + await aggregator.setLatestAnswer(rates[i], timestamp); + } +} + module.exports = { /** * the truffle transaction does not return all events logged, only those from the invoked @@ -88,31 +130,19 @@ module.exports = { return web3.utils.hexToAscii(web3.utils.utf8ToHex(input)); }, - async updateRatesWithDefaults({ exchangeRates, oracle, debtCache }) { - const [SNX, sAUD, sEUR, sBTC, iBTC, sETH, ETH] = [ - 'SNX', - 'sAUD', - 'sEUR', - 'sBTC', - 'iBTC', - 'sETH', - 'ETH', - ].map(toBytes32); - - let timestamp; + setupPriceAggregators, + + updateAggregatorRates, + + async updateRatesWithDefaults({ exchangeRates, owner, debtCache }) { + const keys = ['SNX', 'sAUD', 'sEUR', 'sBTC', 'iBTC', 'sETH', 'ETH'].map(toBytes32); + const rates = ['0.1', '0.5', '1.25', '5000', '4000', '172', '172'].map(toUnit); + // set up any missing aggregators + await setupMissingPriceAggregators(exchangeRates, owner, keys); + for (let i = 0; i < EXCHANGE_DYNAMIC_FEE_ROUNDS; i++) { - timestamp = await currentTime(); - - await exchangeRates.updateRates( - [SNX, sAUD, sEUR, sBTC, iBTC, sETH, ETH], - ['0.1', '0.5', '1.25', '5000', '4000', '172', '172'].map(toUnit), - timestamp, - { - from: oracle, - } - ); + await updateAggregatorRates(exchangeRates, keys, rates); } - await debtCache.takeDebtSnapshot(); }, diff --git a/test/contracts/setup.js b/test/contracts/setup.js index 6f09c2f3d4..302de8ea4f 100644 --- a/test/contracts/setup.js +++ b/test/contracts/setup.js @@ -4,6 +4,8 @@ const { artifacts, web3, log } = require('hardhat'); const { toWei } = web3.utils; const { toUnit } = require('../utils')(); +const { setupPriceAggregators, updateAggregatorRates } = require('./helpers'); + const { toBytes32, getUsers, @@ -112,7 +114,7 @@ const setupContract = async ({ skipPostDeploy = false, properties = {}, }) => { - const [deployerAccount, owner, oracle, fundsWallet] = accounts; + const [deployerAccount, owner, , fundsWallet] = accounts; const artifact = artifacts.require(contract); @@ -160,20 +162,8 @@ const setupContract = async ({ AddressResolver: [owner], SystemStatus: [owner], FlexibleStorage: [tryGetAddressOf('AddressResolver')], - ExchangeRates: [ - owner, - oracle, - tryGetAddressOf('AddressResolver'), - [toBytes32('SNX')], - [toWei('0.2', 'ether')], - ], - ExchangeRatesWithDexPricing: [ - owner, - oracle, - tryGetAddressOf('AddressResolver'), - [toBytes32('SNX')], - [toWei('0.2', 'ether')], - ], + ExchangeRates: [owner, tryGetAddressOf('AddressResolver')], + ExchangeRatesWithDexPricing: [owner, tryGetAddressOf('AddressResolver')], SynthetixState: [owner, ZERO_ADDRESS], SupplySchedule: [owner, 0, 0], Proxy: [owner], @@ -1161,6 +1151,13 @@ const setupAllContracts = async ({ .map(mock => mock.setAddressResolver(returnObj['AddressResolver'].address)) ); + if (returnObj['ExchangeRates']) { + // setup SNX price feed + const SNX = toBytes32('SNX'); + await setupPriceAggregators(returnObj['ExchangeRates'], owner, [SNX]); + await updateAggregatorRates(returnObj['ExchangeRates'], [SNX], [toUnit('0.2')]); + } + return returnObj; }; diff --git a/test/integration/behaviors/exchange.behavior.js b/test/integration/behaviors/exchange.behavior.js index 6bf8d62eae..2437fe97eb 100644 --- a/test/integration/behaviors/exchange.behavior.js +++ b/test/integration/behaviors/exchange.behavior.js @@ -41,7 +41,8 @@ function itCanExchange({ ctx }) { await updateCache({ ctx }); const tx = await Synthetix.exchange(toBytes32('sUSD'), sUSDAmount, toBytes32('sETH')); - await tx.wait(); + const { gasUsed } = await tx.wait(); + console.log(`exchange() gas used: ${Math.round(gasUsed / 1000).toString()}k`); }); it('receives the expected amount of sETH', async () => { @@ -77,7 +78,8 @@ function itCanExchange({ ctx }) { before('settle', async () => { const tx = await Synthetix.settle(toBytes32('sETH')); - await tx.wait(); + const { gasUsed } = await tx.wait(); + console.log(`settle() gas used: ${Math.round(gasUsed / 1000).toString()}k`); }); it('shows that the user no longer has pending settlements', async () => { diff --git a/test/integration/behaviors/redeem.behavior.js b/test/integration/behaviors/redeem.behavior.js index 7b1d5f793b..4e855ce62c 100644 --- a/test/integration/behaviors/redeem.behavior.js +++ b/test/integration/behaviors/redeem.behavior.js @@ -6,7 +6,7 @@ const { } = require('../../../index'); const { ensureBalance } = require('../utils/balances'); const { skipWaitingPeriod } = require('../utils/skip'); -const { updateExchangeRatesIfNeeded } = require('../utils/rates'); +const { increaseStalePeriodAndCheckRatesAndCache } = require('../utils/rates'); function itCanRedeem({ ctx }) { describe('redemption of deprecated synths', () => { @@ -57,7 +57,7 @@ function itCanRedeem({ ctx }) { }); before('update rates and take snapshot if needed', async () => { - await updateExchangeRatesIfNeeded({ ctx }); + await increaseStalePeriodAndCheckRatesAndCache({ ctx }); }); before('record total system debt', async () => { diff --git a/test/integration/behaviors/stake.behavior.js b/test/integration/behaviors/stake.behavior.js index cf886c5bdc..4f0b608fe7 100644 --- a/test/integration/behaviors/stake.behavior.js +++ b/test/integration/behaviors/stake.behavior.js @@ -33,7 +33,8 @@ function itCanStake({ ctx }) { Synthetix = Synthetix.connect(user); const tx = await Synthetix.issueSynths(amountToIssueAndBurnsUSD); - await tx.wait(); + const { gasUsed } = await tx.wait(); + console.log(`issueSynths() gas used: ${Math.round(gasUsed / 1000).toString()}k`); }); it('issues the expected amount of sUSD', async () => { @@ -69,7 +70,8 @@ function itCanStake({ ctx }) { FeePool = FeePool.connect(user); const tx = await FeePool.claimFees(); - await tx.wait(); + const { gasUsed } = await tx.wait(); + console.log(`claimFees() gas used: ${Math.round(gasUsed / 1000).toString()}k`); }); it('shows a slight increase in the users sUSD balance', async () => { @@ -93,7 +95,8 @@ function itCanStake({ ctx }) { Synthetix = Synthetix.connect(user); const tx = await Synthetix.burnSynths(amountToIssueAndBurnsUSD); - await tx.wait(); + const { gasUsed } = await tx.wait(); + console.log(`burnSynths() gas used: ${Math.round(gasUsed / 1000).toString()}k`); }); it('reduced the expected amount of debt', async () => { diff --git a/test/integration/utils/bootstrap.js b/test/integration/utils/bootstrap.js index 0060f92781..c905e731e5 100644 --- a/test/integration/utils/bootstrap.js +++ b/test/integration/utils/bootstrap.js @@ -2,7 +2,7 @@ const hre = require('hardhat'); const ethers = require('ethers'); const { loadUsers } = require('./users'); const { connectContracts } = require('./contracts'); -const { updateExchangeRatesIfNeeded } = require('./rates'); +const { increaseStalePeriodAndCheckRatesAndCache } = require('./rates'); const { ensureBalance } = require('./balances'); const { setupOptimismWatchers, approveBridge } = require('./optimism'); const { startOpsHeartbeat } = require('./optimism-temp'); @@ -26,7 +26,7 @@ function bootstrapL1({ ctx }) { connectContracts({ ctx }); - await updateExchangeRatesIfNeeded({ ctx }); + await increaseStalePeriodAndCheckRatesAndCache({ ctx }); }); } @@ -57,7 +57,7 @@ function bootstrapL2({ ctx }) { connectContracts({ ctx }); - await updateExchangeRatesIfNeeded({ ctx }); + await increaseStalePeriodAndCheckRatesAndCache({ ctx }); await ensureBalance({ ctx, @@ -95,8 +95,8 @@ function bootstrapDual({ ctx }) { connectContracts({ ctx: ctx.l1 }); connectContracts({ ctx: ctx.l2 }); - await updateExchangeRatesIfNeeded({ ctx: ctx.l1 }); - await updateExchangeRatesIfNeeded({ ctx: ctx.l2 }); + await increaseStalePeriodAndCheckRatesAndCache({ ctx: ctx.l1 }); + await increaseStalePeriodAndCheckRatesAndCache({ ctx: ctx.l2 }); await approveBridge({ ctx: ctx.l1, amount: ethers.utils.parseEther('100000000') }); diff --git a/test/integration/utils/rates.js b/test/integration/utils/rates.js index 09de49582e..f5bc6ba8dd 100644 --- a/test/integration/utils/rates.js +++ b/test/integration/utils/rates.js @@ -4,15 +4,18 @@ const { toBytes32, defaults: { EXCHANGE_DYNAMIC_FEE_ROUNDS }, } = require('../../..'); +const { createMockAggregatorFactory } = require('../../utils')(); -async function updateExchangeRatesIfNeeded({ ctx }) { +async function increaseStalePeriodAndCheckRatesAndCache({ ctx }) { await setSystemSetting({ ctx, settingName: 'rateStalePeriod', newValue: '1000000000' }); if (await _areRatesInvalid({ ctx })) { - await _setNewRates({ ctx }); + // try to add the missing rates + await _setMissingRates({ ctx }); + // check again if (await _areRatesInvalid({ ctx })) { await _printRatesInfo({ ctx }); - throw new Error('Rates are still invalid after updating them.'); + throw new Error('Rates are still invalid after updating.'); } } @@ -74,36 +77,41 @@ async function _getAvailableCurrencyKeys({ ctx }) { .concat(['SNX', 'ETH'].map(toBytes32)); } -async function _getCurrentRates({ ctx, currencyKeys }) { - const { ExchangeRates } = ctx.contracts; - - const rates = []; - for (const currencyKey of currencyKeys) { - const rate = await ExchangeRates.rateForCurrency(currencyKey); - - // Simualte any exchange rates that are 0. - const newRate = rate.toString() === '0' ? ethers.utils.parseEther('1') : rate; - - rates.push(newRate); +async function _setMissingRates({ ctx }) { + let currencyKeys; + if (ctx.fork) { + // this adds a rate for only e.g. sREDEEMER in fork mode (which is not an existing synth + // but is added to test the redeeming functionality) + // All other synths should have feeds in fork mode + currencyKeys = (ctx.addedSynths || []).map(o => toBytes32(o.name)); + } else { + // set missing rates for all synths, since not in fork mode we don't have existing feeds + currencyKeys = await _getAvailableCurrencyKeys({ ctx }); } - return rates; -} - -async function _setNewRates({ ctx }) { - let { ExchangeRates } = ctx.contracts; - const oracle = await ExchangeRates.oracle(); - const signer = ctx.fork ? ctx.provider.getSigner(oracle) : ctx.users.owner; - ExchangeRates = ExchangeRates.connect(signer); + const owner = ctx.users.owner; + const ExchangeRates = ctx.contracts.ExchangeRates.connect(owner); - const currencyKeys = await _getAvailableCurrencyKeys({ ctx }); - const rates = await _getCurrentRates({ ctx, currencyKeys }); + // factory for price aggregators contracts + const MockAggregatorFactory = await createMockAggregatorFactory(owner); + // got over all rates and add aggregators + const { timestamp } = await ctx.provider.getBlock(); for (let i = 0; i < EXCHANGE_DYNAMIC_FEE_ROUNDS; i++) { - const { timestamp } = await ctx.provider.getBlock(); - - const tx = await ExchangeRates.updateRates(currencyKeys, rates, timestamp); - await tx.wait(); + for (const currencyKey of currencyKeys) { + const rate = await ExchangeRates.rateForCurrency(currencyKey); + if (rate.toString() === '0') { + // deploy an aggregator + let aggregator = await MockAggregatorFactory.deploy(); + aggregator = aggregator.connect(owner); + // set decimals + await (await aggregator.setDecimals(18)).wait(); + // push the new price + await (await aggregator.setLatestAnswer(ethers.utils.parseEther('1'), timestamp)).wait(); + // set the aggregator in ExchangeRates + await (await ExchangeRates.addAggregator(currencyKey, aggregator.address)).wait(); + } + } } } @@ -127,7 +135,7 @@ async function getRate({ ctx, symbol }) { } module.exports = { - updateExchangeRatesIfNeeded, + increaseStalePeriodAndCheckRatesAndCache, getRate, updateCache, }; diff --git a/test/integration/utils/skip.js b/test/integration/utils/skip.js index b32ce985ce..f668acff22 100644 --- a/test/integration/utils/skip.js +++ b/test/integration/utils/skip.js @@ -1,7 +1,7 @@ const { fastForward } = require('../../test-utils/rpc'); const { wait } = require('../../test-utils/wait'); const { getSystemSetting } = require('./settings'); -const { updateExchangeRatesIfNeeded } = require('./rates'); +const { increaseStalePeriodAndCheckRatesAndCache } = require('./rates'); async function skipWaitingPeriod({ ctx }) { await _dualFastForward({ @@ -35,7 +35,7 @@ async function _dualFastForward({ ctx, seconds }) { await fastForward({ seconds: parseInt(seconds), provider: l1Ctx.provider }); await wait({ seconds: 6 }); - await updateExchangeRatesIfNeeded({ ctx }); + await increaseStalePeriodAndCheckRatesAndCache({ ctx }); } module.exports = { diff --git a/test/publish/index.js b/test/publish/index.js index 798992bac8..318eaf80df 100644 --- a/test/publish/index.js +++ b/test/publish/index.js @@ -6,14 +6,12 @@ const pLimit = require('p-limit'); const ethers = require('ethers'); const isCI = require('is-ci'); -const { loadCompiledFiles } = require('../../publish/src/solidity'); const { loadLocalWallets } = require('../test-utils/wallets'); const { fastForward } = require('../test-utils/rpc'); const deployStakingRewardsCmd = require('../../publish/src/commands/deploy-staking-rewards'); const deployShortingRewardsCmd = require('../../publish/src/commands/deploy-shorting-rewards'); const deployCmd = require('../../publish/src/commands/deploy'); -const { buildPath } = deployCmd.DEFAULTS; const testUtils = require('../utils'); const commands = { @@ -97,6 +95,7 @@ describe('publish scripts', () => { let sETH; let provider; let overrides; + let MockAggregatorFactory; const resetConfigAndSynthFiles = () => { // restore the synths and config files for this env (cause removal updated it) @@ -135,7 +134,7 @@ describe('publish scripts', () => { url: 'http://localhost:8545', }); - const { isCompileRequired } = testUtils(); + const { isCompileRequired, createMockAggregatorFactory } = testUtils(); // load accounts used by local EVM const wallets = loadLocalWallets({ provider }); @@ -154,6 +153,8 @@ describe('publish scripts', () => { console.log('Skipping build as everything up to date'); } + MockAggregatorFactory = await createMockAggregatorFactory(accounts.deployer); + [sUSD, sBTC, sETH] = ['sUSD', 'sBTC', 'sETH'].map(toBytes32); gasLimit = 8000000; @@ -195,15 +196,6 @@ describe('publish scripts', () => { ); const createMockAggregator = async () => { - // get last build - const { compiled } = loadCompiledFiles({ buildPath }); - const { - abi, - evm: { - bytecode: { object: bytecode }, - }, - } = compiled['MockAggregatorV2V3']; - const MockAggregatorFactory = new ethers.ContractFactory(abi, bytecode, accounts.deployer); const MockAggregator = await MockAggregatorFactory.deploy({ gasLimit, gasPrice }); const tx = await MockAggregator.setDecimals('8', { diff --git a/test/utils/index.js b/test/utils/index.js index c83711eedf..fcad276337 100644 --- a/test/utils/index.js +++ b/test/utils/index.js @@ -528,6 +528,18 @@ module.exports = ({ web3 } = {}) => { return latestSolTimestamp > earliestCompiledTimestamp; }; + // create a factory to deploy mock price aggregators + const createMockAggregatorFactory = async account => { + const { compiled } = loadCompiledFiles({ buildPath }); + const { + abi, + evm: { + bytecode: { object: bytecode }, + }, + } = compiled['MockAggregatorV2V3']; + return new ethers.ContractFactory(abi, bytecode, account); + }; + const setupProvider = ({ providerUrl, privateKey, publicKey }) => { const provider = new ethers.providers.JsonRpcProvider(providerUrl); @@ -606,6 +618,7 @@ module.exports = ({ web3 } = {}) => { loadLocalUsers, isCompileRequired, + createMockAggregatorFactory, setupProvider, getContract, From 955b5b1dc4e503bc56573eceffd22a918eddfb21 Mon Sep 17 00:00:00 2001 From: Lecky Date: Sat, 25 Dec 2021 22:14:05 +1100 Subject: [PATCH 090/133] fix ExchangeRates test - apply AtRound changes - Adding cacheRates on ExchangeRates --- contracts/ExchangeRates.sol | 73 ++++++++++++++--- contracts/Exchanger.sol | 101 ++++++++++++++++++++++-- contracts/interfaces/IExchangeRates.sol | 2 + test/contracts/ExchangeRates.js | 16 ++-- 4 files changed, 167 insertions(+), 25 deletions(-) diff --git a/contracts/ExchangeRates.sol b/contracts/ExchangeRates.sol index c3bae1220c..a9bf7aa027 100644 --- a/contracts/ExchangeRates.sol +++ b/contracts/ExchangeRates.sol @@ -22,8 +22,7 @@ contract ExchangeRates is Owned, MixinSystemSettings, IExchangeRates { using SafeDecimalMath for uint; bytes32 public constant CONTRACT_NAME = "ExchangeRates"; - //slither-disable-next-line naming-convention - bytes32 internal constant sUSD = "sUSD"; + bytes32 internal constant SUSD = "sUSD"; // Decentralized oracle networks that feed into pricing aggregators mapping(bytes32 => AggregatorV2V3Interface) public aggregators; @@ -33,6 +32,8 @@ contract ExchangeRates is Owned, MixinSystemSettings, IExchangeRates { // List of aggregator keys for convenient iteration bytes32[] public aggregatorKeys; + mapping(bytes32 => mapping(uint => RateAndUpdatedTime)) public cacheRates; + // ========== CONSTRUCTOR ========== constructor(address _owner, address _resolver) public Owned(_owner) MixinSystemSettings(_resolver) {} @@ -96,7 +97,7 @@ contract ExchangeRates is Owned, MixinSystemSettings, IExchangeRates { uint time; (sourceRate, time) = _getRateAndTimestampAtRound(sourceCurrencyKey, roundIdForSrc); // cacheing to save external call - _setRate(sourceCurrencyKey, roundIdForSrc, sourceRate, time); + _setCacheRate(sourceCurrencyKey, roundIdForSrc, sourceRate, time); // If there's no change in the currency, then just return the amount they gave us if (sourceCurrencyKey == destinationCurrencyKey) { destinationRate = sourceRate; @@ -104,7 +105,7 @@ contract ExchangeRates is Owned, MixinSystemSettings, IExchangeRates { } else { (destinationRate, time) = _getRateAndTimestampAtRound(destinationCurrencyKey, roundIdForDest); // cacheing to save external call - _setRate(destinationCurrencyKey, roundIdForDest, destinationRate, time); + _setCacheRate(destinationCurrencyKey, roundIdForDest, destinationRate, time); // prevent divide-by 0 error (this happens if the dest is not a valid rate) if (destinationRate > 0) { // Calculate the effective value by going from source -> USD -> destination @@ -113,6 +114,17 @@ contract ExchangeRates is Owned, MixinSystemSettings, IExchangeRates { } } + function _setCacheRate( + bytes32 currencyKey, + uint roundId, + uint rate, + uint time + ) internal { + if (rate > 0) { + cacheRates[currencyKey][roundId] = RateAndUpdatedTime({rate: uint216(rate), time: uint40(time)}); + } + } + /* ========== VIEWS ========== */ function currenciesUsingAggregator(address aggregator) external view returns (bytes32[] memory currencies) { @@ -288,7 +300,7 @@ contract ExchangeRates is Owned, MixinSystemSettings, IExchangeRates { function rateAndInvalid(bytes32 currencyKey) external view returns (uint rate, bool isInvalid) { RateAndUpdatedTime memory rateAndTime = _getRateAndUpdatedTime(currencyKey); - if (currencyKey == sUSD) { + if (currencyKey == SUSD) { return (rateAndTime.rate, false); } return ( @@ -314,7 +326,7 @@ contract ExchangeRates is Owned, MixinSystemSettings, IExchangeRates { // do one lookup of the rate & time to minimize gas RateAndUpdatedTime memory rateEntry = _getRateAndUpdatedTime(currencyKeys[i]); rates[i] = rateEntry.rate; - if (!anyRateInvalid && currencyKeys[i] != sUSD) { + if (!anyRateInvalid && currencyKeys[i] != SUSD) { anyRateInvalid = flagList[i] || _rateIsStaleWithTime(_rateStalePeriod, rateEntry.time); } } @@ -349,6 +361,27 @@ contract ExchangeRates is Owned, MixinSystemSettings, IExchangeRates { return false; } + function anyRateIsInvalidAtRound(bytes32[] calldata currencyKeys, uint[] calldata roundIds) + external + view + returns (bool) + { + // Loop through each key and check whether the data point is stale. + + require(roundIds.length == currencyKeys.length, "roundIds must be the same length as currencyKeys"); + + uint256 _rateStalePeriod = getRateStalePeriod(); + bool[] memory flagList = getFlagsForRates(currencyKeys); + + for (uint i = 0; i < currencyKeys.length; i++) { + if (flagList[i] || _rateIsStaleAtRound(currencyKeys[i], roundIds[i], _rateStalePeriod)) { + return true; + } + } + + return false; + } + function synthTooVolatileForAtomicExchange(bytes32) external view returns (bool) { _notImplemented(); } @@ -402,7 +435,7 @@ contract ExchangeRates is Owned, MixinSystemSettings, IExchangeRates { function _getRateAndUpdatedTime(bytes32 currencyKey) internal view returns (RateAndUpdatedTime memory) { // sUSD rate is 1.0 - if (currencyKey == sUSD) { + if (currencyKey == SUSD) { return RateAndUpdatedTime({rate: uint216(SafeDecimalMath.unit()), time: 0}); } else { AggregatorV2V3Interface aggregator = aggregators[currencyKey]; @@ -428,7 +461,7 @@ contract ExchangeRates is Owned, MixinSystemSettings, IExchangeRates { } function _getCurrentRoundId(bytes32 currencyKey) internal view returns (uint) { - if (currencyKey == sUSD) { + if (currencyKey == SUSD) { return 0; // no roundIds for sUSD } AggregatorV2V3Interface aggregator = aggregators[currencyKey]; @@ -439,11 +472,17 @@ contract ExchangeRates is Owned, MixinSystemSettings, IExchangeRates { function _getRateAndTimestampAtRound(bytes32 currencyKey, uint roundId) internal view returns (uint rate, uint time) { // short circuit sUSD - if (currencyKey == sUSD) { + if (currencyKey == SUSD) { // sUSD has no rounds, and 0 time is preferrable for "volatility" heuristics // which are used in atomic swaps and fee reclamation return (SafeDecimalMath.unit(), 0); } else { + if (cacheRates[currencyKey][roundId].rate != 0) { + return ( + _formatAggregatorAnswer(currencyKey, cacheRates[currencyKey][roundId].rate), + cacheRates[currencyKey][roundId].time + ); + } AggregatorV2V3Interface aggregator = aggregators[currencyKey]; if (aggregator != AggregatorV2V3Interface(0)) { @@ -500,18 +539,30 @@ contract ExchangeRates is Owned, MixinSystemSettings, IExchangeRates { function _rateIsStale(bytes32 currencyKey, uint _rateStalePeriod) internal view returns (bool) { // sUSD is a special case and is never stale (check before an SLOAD of getRateAndUpdatedTime) - if (currencyKey == sUSD) return false; + if (currencyKey == SUSD) return false; return _rateIsStaleWithTime(_rateStalePeriod, _getUpdatedTime(currencyKey)); } + function _rateIsStaleAtRound( + bytes32 currencyKey, + uint roundId, + uint _rateStalePeriod + ) internal view returns (bool) { + // sUSD is a special case and is never stale (check before an SLOAD of getRateAndUpdatedTime) + if (currencyKey == SUSD) return false; + + (, uint time) = _getRateAndTimestampAtRound(currencyKey, roundId); + return _rateIsStaleWithTime(_rateStalePeriod, time); + } + function _rateIsStaleWithTime(uint _rateStalePeriod, uint _time) internal view returns (bool) { return _time.add(_rateStalePeriod) < now; } function _rateIsFlagged(bytes32 currencyKey, FlagsInterface flags) internal view returns (bool) { // sUSD is a special case and is never invalid - if (currencyKey == sUSD) return false; + if (currencyKey == SUSD) return false; address aggregator = address(aggregators[currencyKey]); // when no aggregator or when the flags haven't been setup if (aggregator == address(0) || flags == FlagsInterface(0)) { diff --git a/contracts/Exchanger.sol b/contracts/Exchanger.sol index 6f308e219f..aa2787f964 100644 --- a/contracts/Exchanger.sol +++ b/contracts/Exchanger.sol @@ -451,7 +451,13 @@ contract Exchanger is Owned, MixinSystemSettings, IExchanger { entry.roundIdForDest ); - _ensureCanExchange(sourceCurrencyKey, sourceAmount, destinationCurrencyKey); + _ensureCanExchangeAtRound( + sourceCurrencyKey, + sourceAmount, + destinationCurrencyKey, + entry.roundIdForSrc, + entry.roundIdForDest + ); // SIP-65: Decentralized Circuit Breaker // mutative call to suspend system if the rate is invalid @@ -470,7 +476,12 @@ contract Exchanger is Owned, MixinSystemSettings, IExchanger { return (0, 0, IVirtualSynth(0)); } - entry.exchangeFeeRate = _feeRateForExchange(sourceCurrencyKey, destinationCurrencyKey); + entry.exchangeFeeRate = _feeRateForExchangeAtRound( + sourceCurrencyKey, + destinationCurrencyKey, + entry.roundIdForSrc, + entry.roundIdForDest + ); amountReceived = _deductFeesFromAmount(entry.destinationAmount, entry.exchangeFeeRate); // Note: `fee` is denominated in the destinationCurrencyKey. @@ -620,6 +631,26 @@ contract Exchanger is Owned, MixinSystemSettings, IExchanger { require(!exchangeRates().anyRateIsInvalid(synthKeys), "Src/dest rate invalid or not found"); } + function _ensureCanExchangeAtRound( + bytes32 sourceCurrencyKey, + uint sourceAmount, + bytes32 destinationCurrencyKey, + uint roundIdForSrc, + uint roundIdForDest + ) internal view { + require(sourceCurrencyKey != destinationCurrencyKey, "Can't be same synth"); + require(sourceAmount > 0, "Zero amount"); + + bytes32[] memory synthKeys = new bytes32[](2); + synthKeys[0] = sourceCurrencyKey; + synthKeys[1] = destinationCurrencyKey; + + uint[] memory roundIds = new uint[](2); + roundIds[0] = roundIdForSrc; + roundIds[1] = roundIdForDest; + require(!exchangeRates().anyRateIsInvalidAtRound(synthKeys, roundIds), "Src/dest rate invalid or not found"); + } + function _isSynthRateInvalid(bytes32 currencyKey, uint currentRate) internal view returns (bool) { if (currentRate == 0) { return true; @@ -771,6 +802,31 @@ contract Exchanger is Owned, MixinSystemSettings, IExchanger { return _calculateFeeRateFromExchangeSynths(baseRate, sourceCurrencyKey, destinationCurrencyKey); } + /// @notice Calculate the exchange fee for a given source and destination currency key + /// @param sourceCurrencyKey The source currency key + /// @param destinationCurrencyKey The destination currency key + /// @param roundIdForSrc The round id of the source currency. + /// @param roundIdForDest The round id of the target currency. + /// @return The exchange fee rate + /// @return The exchange dynamic fee rate + function _feeRateForExchangeAtRound( + bytes32 sourceCurrencyKey, + bytes32 destinationCurrencyKey, + uint roundIdForSrc, + uint roundIdForDest + ) internal view returns (uint exchangeFeeRate) { + // Get the exchange fee rate as per destination currencyKey + uint baseRate = getExchangeFeeRate(destinationCurrencyKey); + return + _calculateFeeRateFromExchangeSynthsAtRound( + baseRate, + sourceCurrencyKey, + destinationCurrencyKey, + roundIdForSrc, + roundIdForDest + ); + } + function _calculateFeeRateFromExchangeSynths( uint exchangeFeeRate, bytes32 sourceCurrencyKey, @@ -786,19 +842,48 @@ contract Exchanger is Owned, MixinSystemSettings, IExchanger { return exchangeFeeRate; } + function _calculateFeeRateFromExchangeSynthsAtRound( + uint exchangeFeeRate, + bytes32 sourceCurrencyKey, + bytes32 destinationCurrencyKey, + uint roundIdForSrc, + uint roundIdForDest + ) internal view returns (uint) { + uint exchangeDynamicFeeRate = _getDynamicFeeForExchangeAtRound(destinationCurrencyKey, roundIdForDest); + exchangeDynamicFeeRate = exchangeDynamicFeeRate.add( + _getDynamicFeeForExchangeAtRound(sourceCurrencyKey, roundIdForSrc) + ); + uint maxDynamicFee = getExchangeMaxDynamicFee(); + + exchangeFeeRate = exchangeFeeRate.add(exchangeDynamicFeeRate); + // Cap to max exchange dynamic fee + exchangeFeeRate = exchangeFeeRate > maxDynamicFee ? maxDynamicFee : exchangeFeeRate; + return exchangeFeeRate; + } + /// @notice Get dynamic fee for a given currency key (SIP-184) /// @param currencyKey The given currency key /// @return The dyanmic fee function _getDynamicFeeForExchange(bytes32 currencyKey) internal view returns (uint dynamicFee) { // No dynamic fee for sUSD - if (currencyKey == sUSD) { - return 0; - } + if (currencyKey == sUSD) return 0; + (uint threshold, uint weightDecay, uint rounds) = getExchangeDynamicFeeData(); + uint[] memory prices; + uint roundId = exchangeRates().getCurrentRoundId(currencyKey); + (prices, ) = exchangeRates().ratesAndUpdatedTimeForCurrencyLastNRounds(currencyKey, rounds, roundId); + dynamicFee = DynamicFee.getDynamicFee(prices, threshold, weightDecay); + } + + /// @notice Get dynamic fee for a given currency key (SIP-184) + /// @param currencyKey The given currency key + /// @param roundId The round id + /// @return The dyanmic fee + function _getDynamicFeeForExchangeAtRound(bytes32 currencyKey, uint roundId) internal view returns (uint dynamicFee) { + // No dynamic fee for sUSD + if (currencyKey == sUSD) return 0; (uint threshold, uint weightDecay, uint rounds) = getExchangeDynamicFeeData(); uint[] memory prices; - // Note: We are using cache round ID here for cheap read - uint currentRoundId = exchangeRates().currentRoundForRate(currencyKey); - (prices, ) = exchangeRates().ratesAndUpdatedTimeForCurrencyLastNRounds(currencyKey, rounds, currentRoundId); + (prices, ) = exchangeRates().ratesAndUpdatedTimeForCurrencyLastNRounds(currencyKey, rounds, roundId); dynamicFee = DynamicFee.getDynamicFee(prices, threshold, weightDecay); } diff --git a/contracts/interfaces/IExchangeRates.sol b/contracts/interfaces/IExchangeRates.sol index f1110010c8..bcc6501956 100644 --- a/contracts/interfaces/IExchangeRates.sol +++ b/contracts/interfaces/IExchangeRates.sol @@ -15,6 +15,8 @@ interface IExchangeRates { function anyRateIsInvalid(bytes32[] calldata currencyKeys) external view returns (bool); + function anyRateIsInvalidAtRound(bytes32[] calldata currencyKeys, uint[] calldata roundIds) external view returns (bool); + function currenciesUsingAggregator(address aggregator) external view returns (bytes32[] memory); function effectiveValue( diff --git a/test/contracts/ExchangeRates.js b/test/contracts/ExchangeRates.js index c5a78a4cfb..1d65f54832 100644 --- a/test/contracts/ExchangeRates.js +++ b/test/contracts/ExchangeRates.js @@ -55,7 +55,11 @@ contract('Exchange Rates', async accounts => { let mockFlagsInterface; const itIncludesCorrectMutativeFunctions = contract => { - const baseFunctions = ['addAggregator', 'removeAggregator']; + const baseFunctions = [ + 'addAggregator', + 'removeAggregator', + 'mutativeEffectiveValueAndRatesAtRound', + ]; const withDexPricingFunctions = baseFunctions.concat(['setDexPriceAggregator']); it('only expected functions should be mutative', () => { @@ -949,7 +953,7 @@ contract('Exchange Rates', async accounts => { }); it('ratesAndUpdatedTimeForCurrencyLastNRounds() shows first entry for sUSD', async () => { - assert.deepEqual(await instance.ratesAndUpdatedTimeForCurrencyLastNRounds(sUSD, '3'), [ + assert.deepEqual(await instance.ratesAndUpdatedTimeForCurrencyLastNRounds(sUSD, '3', '0'), [ [toUnit('1'), '0', '0'], [0, 0, 0], ]); @@ -957,7 +961,7 @@ contract('Exchange Rates', async accounts => { it('ratesAndUpdatedTimeForCurrencyLastNRounds() returns 0s for other currencies without updates', async () => { const fiveZeros = new Array(5).fill('0'); await setupAggregators([sJPY]); - assert.deepEqual(await instance.ratesAndUpdatedTimeForCurrencyLastNRounds(sJPY, '5'), [ + assert.deepEqual(await instance.ratesAndUpdatedTimeForCurrencyLastNRounds(sJPY, '5', '0'), [ fiveZeros, fiveZeros, ]); @@ -1012,7 +1016,7 @@ contract('Exchange Rates', async accounts => { it('then it returns zeros', async () => { const fiveZeros = new Array(5).fill('0'); assert.deepEqual( - await instance.ratesAndUpdatedTimeForCurrencyLastNRounds(sAUD, '5'), + await instance.ratesAndUpdatedTimeForCurrencyLastNRounds(sAUD, '5', '0'), [fiveZeros, fiveZeros] ); }); @@ -1020,7 +1024,7 @@ contract('Exchange Rates', async accounts => { describe('when invoked for an aggregated price', () => { it('then it returns the rates as expected', async () => { assert.deepEqual( - await instance.ratesAndUpdatedTimeForCurrencyLastNRounds(sJPY, '3'), + await instance.ratesAndUpdatedTimeForCurrencyLastNRounds(sJPY, '3', '0'), [ [toUnit('102'), toUnit('101'), toUnit('100')], ['1002', '1001', '1000'], @@ -1030,7 +1034,7 @@ contract('Exchange Rates', async accounts => { it('then it returns the rates as expected, even over the edge', async () => { assert.deepEqual( - await instance.ratesAndUpdatedTimeForCurrencyLastNRounds(sJPY, '5'), + await instance.ratesAndUpdatedTimeForCurrencyLastNRounds(sJPY, '5', '0'), [ [toUnit('102'), toUnit('101'), toUnit('100'), '0', '0'], ['1002', '1001', '1000', '0', '0'], From feb03f0509da303e4b74a19f174a4f6a577780d3 Mon Sep 17 00:00:00 2001 From: Lecky Date: Sun, 26 Dec 2021 11:14:48 +1100 Subject: [PATCH 091/133] fixing ExchangerWithFeeRecAlternatives.unit.js - Adding anyRateIsInvalidAtRound mock --- .../ExchangerWithFeeRecAlternatives.behaviors.js | 8 ++++++++ test/contracts/ExchangerWithFeeRecAlternatives.unit.js | 2 +- 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/test/contracts/ExchangerWithFeeRecAlternatives.behaviors.js b/test/contracts/ExchangerWithFeeRecAlternatives.behaviors.js index a952f77fea..ff93158feb 100644 --- a/test/contracts/ExchangerWithFeeRecAlternatives.behaviors.js +++ b/test/contracts/ExchangerWithFeeRecAlternatives.behaviors.js @@ -113,6 +113,14 @@ module.exports = function({ accounts }) { cb(); }); }, + whenMockedWithExchangeRatesValidityAtRound: ({ valid = true }, cb) => { + describe(`when mocked with ${valid ? 'valid' : 'invalid'} exchange rates`, () => { + beforeEach(async () => { + this.mocks.ExchangeRates.smocked.anyRateIsInvalidAtRound.will.return.with(!valid); + }); + cb(); + }); + }, whenMockedWithNoPriorExchangesToSettle: cb => { describe(`when mocked with no prior exchanges to settle`, () => { beforeEach(async () => { diff --git a/test/contracts/ExchangerWithFeeRecAlternatives.unit.js b/test/contracts/ExchangerWithFeeRecAlternatives.unit.js index d8577c3509..c507886df0 100644 --- a/test/contracts/ExchangerWithFeeRecAlternatives.unit.js +++ b/test/contracts/ExchangerWithFeeRecAlternatives.unit.js @@ -247,7 +247,7 @@ contract('ExchangerWithFeeRecAlternatives (unit tests)', async accounts => { }; describe('failure modes', () => { - behaviors.whenMockedWithExchangeRatesValidity({ valid: false }, () => { + behaviors.whenMockedWithExchangeRatesValidityAtRound({ valid: false }, () => { it('reverts when either rate is invalid', async () => { await assert.revert( this.instance.exchange(...getExchangeArgs()), From 35a4a7b9e9eafa75c05d6e7dede31afe1900813f Mon Sep 17 00:00:00 2001 From: Lecky Date: Sun, 2 Jan 2022 18:03:29 +1100 Subject: [PATCH 092/133] Fix RewardsIntegrationTests.js - Add updateRatesWithDefaults to invoid invalid rate --- test/contracts/RewardsIntegrationTests.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/contracts/RewardsIntegrationTests.js b/test/contracts/RewardsIntegrationTests.js index f50af7f70c..dcb090d0a2 100644 --- a/test/contracts/RewardsIntegrationTests.js +++ b/test/contracts/RewardsIntegrationTests.js @@ -559,8 +559,8 @@ contract('Rewards Integration Tests', accounts => { assert.bnClose(account3Rewards[1], rewardsAmount, '1'); // Accounts 2 & 3 claim + await updateRatesWithDefaults({ exchangeRates, owner, debtCache }); await feePool.claimFees({ from: account2 }); - // updateRatesWithDefaults(); await feePool.claimFees({ from: account3 }); // Accounts 2 & 3 now have the rewards escrowed From d904b7ac34c913653a7e3a298a832cf7f9590a36 Mon Sep 17 00:00:00 2001 From: Lecky Date: Tue, 4 Jan 2022 14:09:49 +1100 Subject: [PATCH 093/133] Fix Exchanger.spec.js - move setPriceAggregator out of the updateRates loop --- test/contracts/Exchanger.spec.js | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/test/contracts/Exchanger.spec.js b/test/contracts/Exchanger.spec.js index 5a6f7c5000..282c71e66e 100644 --- a/test/contracts/Exchanger.spec.js +++ b/test/contracts/Exchanger.spec.js @@ -3507,10 +3507,10 @@ contract('Exchanger (spec tests)', async accounts => { addSnapshotBeforeRestoreAfterEach(); beforeEach(async () => { + const keys = [sAUD, sEUR, SNX, sETH, sBTC, iBTC]; + const rates = ['0.5', '2', '1', '100', '5000', '5000'].map(toUnit); + await setupPriceAggregators(exchangeRates, owner, keys); for (let i = 0; i < EXCHANGE_DYNAMIC_FEE_ROUNDS; i++) { - const keys = [sAUD, sEUR, SNX, sETH, sBTC, iBTC]; - const rates = ['0.5', '2', '1', '100', '5000', '5000'].map(toUnit); - await setupPriceAggregators(exchangeRates, owner, keys); await updateRates(keys, rates); } @@ -3605,10 +3605,10 @@ contract('Exchanger (spec tests)', async accounts => { addSnapshotBeforeRestoreAfterEach(); beforeEach(async () => { + const keys = [sAUD, sEUR, SNX, sETH, sBTC, iBTC]; + const rates = ['0.5', '2', '1', '100', '5000', '5000'].map(toUnit); + await setupPriceAggregators(exchangeRates, owner, keys); for (let i = 0; i < EXCHANGE_DYNAMIC_FEE_ROUNDS; i++) { - const keys = [sAUD, sEUR, SNX, sETH, sBTC, iBTC]; - const rates = ['0.5', '2', '1', '100', '5000', '5000'].map(toUnit); - await setupPriceAggregators(exchangeRates, owner, keys); await updateRates(keys, rates); } From 1d1ef57cb189986baa9bb1ccb2efd4b50d200f62 Mon Sep 17 00:00:00 2001 From: Lecky Date: Tue, 4 Jan 2022 22:15:01 +1100 Subject: [PATCH 094/133] Fix Exchanger.spec.js - Fix set price with no volatility on exchangeAtomically --- test/contracts/Exchanger.spec.js | 12 +++--------- 1 file changed, 3 insertions(+), 9 deletions(-) diff --git a/test/contracts/Exchanger.spec.js b/test/contracts/Exchanger.spec.js index 282c71e66e..24c6d36c6d 100644 --- a/test/contracts/Exchanger.spec.js +++ b/test/contracts/Exchanger.spec.js @@ -2494,15 +2494,9 @@ contract('Exchanger (spec tests)', async accounts => { const aggregator = await MockAggregator.new({ from: owner }); await exchangeRates.addAggregator(sETH, aggregator.address, { from: owner }); // set prices with no volatility - // Need to start from round 11 as the first 10 rounds are used for the setup - await aggregator.setLatestAnswerWithRound( - ethOnCL, - (await currentTime()) - 20 * 60, - '11' - ); - await aggregator.setLatestAnswer(ethOnCL, (await currentTime()) - 15 * 60); - await aggregator.setLatestAnswer(ethOnCL, (await currentTime()) - 10 * 60); - await aggregator.setLatestAnswer(ethOnCL, (await currentTime()) - 5 * 60); + for (let i = EXCHANGE_DYNAMIC_FEE_ROUNDS; i > 0; i--) { + await aggregator.setLatestAnswer(ethOnCL, (await currentTime()) - i * 5 * 60); + } // DexPriceAggregator const dexPriceAggregator = await MockDexPriceAggregator.new(); From b59918762cad241038371fa23f0ace06ac52e8ad Mon Sep 17 00:00:00 2001 From: Lecky Date: Thu, 6 Jan 2022 11:42:56 +1100 Subject: [PATCH 095/133] Fix test/publish - remove format from cacheRate as it's already been formatted --- contracts/ExchangeRates.sol | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/contracts/ExchangeRates.sol b/contracts/ExchangeRates.sol index a9bf7aa027..bf18a05762 100644 --- a/contracts/ExchangeRates.sol +++ b/contracts/ExchangeRates.sol @@ -478,10 +478,7 @@ contract ExchangeRates is Owned, MixinSystemSettings, IExchangeRates { return (SafeDecimalMath.unit(), 0); } else { if (cacheRates[currencyKey][roundId].rate != 0) { - return ( - _formatAggregatorAnswer(currencyKey, cacheRates[currencyKey][roundId].rate), - cacheRates[currencyKey][roundId].time - ); + return (cacheRates[currencyKey][roundId].rate, cacheRates[currencyKey][roundId].time); } AggregatorV2V3Interface aggregator = aggregators[currencyKey]; From d41d2160de646b33f3b3f8f79f18e4ac409e305d Mon Sep 17 00:00:00 2001 From: Lecky Date: Thu, 6 Jan 2022 13:28:57 +1100 Subject: [PATCH 096/133] Using roundId 1 as last round to be consistent --- contracts/ExchangeRates.sol | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/contracts/ExchangeRates.sol b/contracts/ExchangeRates.sol index bf18a05762..044317170d 100644 --- a/contracts/ExchangeRates.sol +++ b/contracts/ExchangeRates.sol @@ -278,7 +278,7 @@ contract ExchangeRates is Owned, MixinSystemSettings, IExchangeRates { // regardless of current rate (rates[i], times[i]) = _getRateAndTimestampAtRound(currencyKey, roundId); - if (roundId == 0) { + if (roundId == 1) { // if we hit the last round, then return what we have return (rates, times); } else { @@ -462,7 +462,7 @@ contract ExchangeRates is Owned, MixinSystemSettings, IExchangeRates { function _getCurrentRoundId(bytes32 currencyKey) internal view returns (uint) { if (currencyKey == SUSD) { - return 0; // no roundIds for sUSD + return 1; // consistent with ChainLink Oracle that start at round 1 } AggregatorV2V3Interface aggregator = aggregators[currencyKey]; if (aggregator != AggregatorV2V3Interface(0)) { From 2b4bd7349ebe2182b22d12d7ad60b6bd33570b01 Mon Sep 17 00:00:00 2001 From: Lecky Date: Thu, 6 Jan 2022 13:59:44 +1100 Subject: [PATCH 097/133] Fix integration test - Having the rounds rate loop inside the aggregator --- test/integration/utils/rates.js | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/test/integration/utils/rates.js b/test/integration/utils/rates.js index f5bc6ba8dd..e40690ad79 100644 --- a/test/integration/utils/rates.js +++ b/test/integration/utils/rates.js @@ -96,21 +96,21 @@ async function _setMissingRates({ ctx }) { const MockAggregatorFactory = await createMockAggregatorFactory(owner); // got over all rates and add aggregators - const { timestamp } = await ctx.provider.getBlock(); - for (let i = 0; i < EXCHANGE_DYNAMIC_FEE_ROUNDS; i++) { - for (const currencyKey of currencyKeys) { - const rate = await ExchangeRates.rateForCurrency(currencyKey); - if (rate.toString() === '0') { - // deploy an aggregator - let aggregator = await MockAggregatorFactory.deploy(); - aggregator = aggregator.connect(owner); - // set decimals - await (await aggregator.setDecimals(18)).wait(); + for (const currencyKey of currencyKeys) { + const rate = await ExchangeRates.rateForCurrency(currencyKey); + if (rate.toString() === '0') { + // deploy an aggregator + let aggregator = await MockAggregatorFactory.deploy(); + aggregator = aggregator.connect(owner); + // set decimals + await (await aggregator.setDecimals(18)).wait(); + for (let i = 0; i < EXCHANGE_DYNAMIC_FEE_ROUNDS; i++) { + const { timestamp } = await ctx.provider.getBlock(); // push the new price await (await aggregator.setLatestAnswer(ethers.utils.parseEther('1'), timestamp)).wait(); - // set the aggregator in ExchangeRates - await (await ExchangeRates.addAggregator(currencyKey, aggregator.address)).wait(); } + // set the aggregator in ExchangeRates + await (await ExchangeRates.addAggregator(currencyKey, aggregator.address)).wait(); } } } From 732e2cb7da0d1e932ea9fcf7eb221bf2fe8f7ace Mon Sep 17 00:00:00 2001 From: Lecky Date: Thu, 6 Jan 2022 14:11:28 +1100 Subject: [PATCH 098/133] Fix ExchangeRates.js - Using roundId 1 for sUSD to be consistent - Fix interface wrong compiler version warning --- contracts/interfaces/IExchanger.sol | 2 +- contracts/interfaces/IFlexibleStorage.sol | 2 +- test/contracts/ExchangeRates.js | 4 ++-- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/contracts/interfaces/IExchanger.sol b/contracts/interfaces/IExchanger.sol index 2cd5323847..ce80bdfcc7 100644 --- a/contracts/interfaces/IExchanger.sol +++ b/contracts/interfaces/IExchanger.sol @@ -1,4 +1,4 @@ -pragma solidity >=0.4.24; +pragma solidity ^0.5.16; import "./IVirtualSynth.sol"; diff --git a/contracts/interfaces/IFlexibleStorage.sol b/contracts/interfaces/IFlexibleStorage.sol index 4419f638be..e72cfbd801 100644 --- a/contracts/interfaces/IFlexibleStorage.sol +++ b/contracts/interfaces/IFlexibleStorage.sol @@ -1,4 +1,4 @@ -pragma solidity >=0.4.24; +pragma solidity ^0.5.16; // https://docs.synthetix.io/contracts/source/interfaces/iflexiblestorage interface IFlexibleStorage { diff --git a/test/contracts/ExchangeRates.js b/test/contracts/ExchangeRates.js index 1d65f54832..57433e7050 100644 --- a/test/contracts/ExchangeRates.js +++ b/test/contracts/ExchangeRates.js @@ -948,8 +948,8 @@ contract('Exchange Rates', async accounts => { assert.equal(await instance.getCurrentRoundId(sBNB), 0); }); - it('getCurrentRoundId() is 0 for sUSD', async () => { - assert.equal(await instance.getCurrentRoundId(sUSD), 0); + it('getCurrentRoundId() is 1 for sUSD', async () => { + assert.equal(await instance.getCurrentRoundId(sUSD), 1); }); it('ratesAndUpdatedTimeForCurrencyLastNRounds() shows first entry for sUSD', async () => { From ab1b89cb0891466d8f99dab7ddf1091a1b2eed7a Mon Sep 17 00:00:00 2001 From: Lecky Date: Thu, 6 Jan 2022 16:55:54 +1100 Subject: [PATCH 099/133] Try to fix CI unit test - adding --max-old-space-size --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index aa01c63629..5a69f1baeb 100644 --- a/package.json +++ b/package.json @@ -18,7 +18,7 @@ "prepublishOnly": "npm run describe && npm run pack", "fork": "node --max-old-space-size=4096 ./node_modules/.bin/hardhat node", "fork:mainnet": "node --max-old-space-size=4096 ./node_modules/.bin/hardhat node --target-network mainnet", - "test": "hardhat test", + "test": "node --max-old-space-size=4096 ./node_modules/.bin/hardhat test", "describe": "hardhat describe", "test:deployments": "mocha test/deployments -- --timeout 60000", "test:etherscan": "node test/etherscan", From d5635a742ddba53ffb436b2081967e85b26ca5e0 Mon Sep 17 00:00:00 2001 From: Lecky Lao Date: Sun, 9 Jan 2022 20:12:00 +1100 Subject: [PATCH 100/133] Fix CI test (#1644) * Fix CI test - increase stake behaviour SNX mint - update setRate to deploy mockAggregator * Revert "Fix CI test" This reverts commit 182b34cd011d556ac16f4cb1d9277c42d4046f93. * Revert "Revert "Fix CI test"" This reverts commit 1be5282e3d5fda26d41932fb1ad9dd260c2ea497. --- test/integration/behaviors/stake.behavior.js | 2 +- .../l1/Liquidations.l1.integrations.js | 22 ------------------- .../l2/Liquidations.l2.integration.js | 22 ------------------- test/integration/utils/rates.js | 19 +++++++++++----- 4 files changed, 14 insertions(+), 51 deletions(-) diff --git a/test/integration/behaviors/stake.behavior.js b/test/integration/behaviors/stake.behavior.js index 4f0b608fe7..c76d319125 100644 --- a/test/integration/behaviors/stake.behavior.js +++ b/test/integration/behaviors/stake.behavior.js @@ -7,7 +7,7 @@ const { skipFeePeriod, skipMinimumStakeTime } = require('../utils/skip'); function itCanStake({ ctx }) { describe('staking and claiming', () => { - const SNXAmount = ethers.utils.parseEther('100'); + const SNXAmount = ethers.utils.parseEther('1000'); const amountToIssueAndBurnsUSD = ethers.utils.parseEther('1'); let user; diff --git a/test/integration/l1/Liquidations.l1.integrations.js b/test/integration/l1/Liquidations.l1.integrations.js index 1027828391..e382b86573 100644 --- a/test/integration/l1/Liquidations.l1.integrations.js +++ b/test/integration/l1/Liquidations.l1.integrations.js @@ -1,31 +1,9 @@ const { bootstrapL1 } = require('../utils/bootstrap'); const { itCanLiquidate } = require('../behaviors/liquidations.behavior'); -const { ethers } = require('hardhat'); - -// Load Compiled -const path = require('path'); -const { - constants: { BUILD_FOLDER }, -} = require('../../..'); -const buildPath = path.join(__dirname, '..', '..', '..', `${BUILD_FOLDER}`); -const { loadCompiledFiles } = require('../../../publish/src/solidity'); -const { compiled } = loadCompiledFiles({ buildPath }); describe('Liquidations (L1)', () => { const ctx = this; bootstrapL1({ ctx }); - before(async () => { - const { - abi, - evm: { - bytecode: { object: bytecode }, - }, - } = compiled.MockAggregatorV2V3; - const MockAggregatorFactory = new ethers.ContractFactory(abi, bytecode, ctx.users.owner); - const MockAggregator = await MockAggregatorFactory.deploy(); - ctx.contracts.MockAggregator = MockAggregator; - }); - itCanLiquidate({ ctx }); }); diff --git a/test/integration/l2/Liquidations.l2.integration.js b/test/integration/l2/Liquidations.l2.integration.js index 34bb13634f..6a0c7a15ad 100644 --- a/test/integration/l2/Liquidations.l2.integration.js +++ b/test/integration/l2/Liquidations.l2.integration.js @@ -1,31 +1,9 @@ const { bootstrapL2 } = require('../utils/bootstrap'); const { itCanLiquidate } = require('../behaviors/liquidations.behavior'); -const { ethers } = require('hardhat'); - -// Load Compiled -const path = require('path'); -const { - constants: { BUILD_FOLDER }, -} = require('../../..'); -const buildPath = path.join(__dirname, '..', '..', '..', `${BUILD_FOLDER}`); -const { loadCompiledFiles } = require('../../../publish/src/solidity'); -const { compiled } = loadCompiledFiles({ buildPath }); describe('Liquidations (L2)', () => { const ctx = this; bootstrapL2({ ctx }); - before(async () => { - const { - abi, - evm: { - bytecode: { object: bytecode }, - }, - } = compiled.MockAggregatorV2V3; - const MockAggregatorFactory = new ethers.ContractFactory(abi, bytecode, ctx.users.owner); - const MockAggregator = await MockAggregatorFactory.deploy(); - ctx.contracts.MockAggregator = MockAggregator; - }); - itCanLiquidate({ ctx }); }); diff --git a/test/integration/utils/rates.js b/test/integration/utils/rates.js index 2e9f4d12a8..65459f5f25 100644 --- a/test/integration/utils/rates.js +++ b/test/integration/utils/rates.js @@ -136,14 +136,21 @@ async function getRate({ ctx, symbol }) { async function setRate({ ctx, symbol, rate }) { const ExchangeRates = ctx.contracts.ExchangeRates.connect(ctx.users.owner); - const MockAggregator = ctx.contracts.MockAggregator.connect(ctx.users.owner); - const { timestamp } = await ctx.provider.getBlock(); + // factory for price aggregators contracts + const MockAggregatorFactory = await createMockAggregatorFactory(ctx.users.owner); - await (await MockAggregator.setDecimals(18)).wait(); - await (await MockAggregator.setLatestAnswer(ethers.utils.parseEther(rate), timestamp)).wait(); - await (await ExchangeRates.addAggregator(toBytes32(symbol), MockAggregator.address)).wait(); - await MockAggregator.setLatestAnswer(rate, timestamp); + // deploy an aggregator + let aggregator = await MockAggregatorFactory.deploy(); + aggregator = aggregator.connect(ctx.users.owner); + + const { timestamp } = await ctx.provider.getBlock(); + // set decimals + await (await aggregator.setDecimals(18)).wait(); + // push the new price + await (await aggregator.setLatestAnswer(ethers.utils.parseEther(rate), timestamp)).wait(); + // set the aggregator in ExchangeRates + await (await ExchangeRates.addAggregator(toBytes32(symbol), aggregator.address)).wait(); } module.exports = { From 5b71a8d00aeb5aa5e6c50ef77404a2e643a5bf5e Mon Sep 17 00:00:00 2001 From: Lecky Date: Sun, 9 Jan 2022 22:45:32 +1100 Subject: [PATCH 101/133] Fix integration test - update setMissingRates to always add all rates --- test/integration/utils/rates.js | 37 ++++++++++++++------------------- 1 file changed, 16 insertions(+), 21 deletions(-) diff --git a/test/integration/utils/rates.js b/test/integration/utils/rates.js index 65459f5f25..2fda4cb1c5 100644 --- a/test/integration/utils/rates.js +++ b/test/integration/utils/rates.js @@ -9,14 +9,12 @@ const { createMockAggregatorFactory } = require('../../utils')(); async function increaseStalePeriodAndCheckRatesAndCache({ ctx }) { await setSystemSetting({ ctx, settingName: 'rateStalePeriod', newValue: '1000000000' }); + // try to add the missing rates + await _setMissingRates({ ctx }); + // check again if (await _areRatesInvalid({ ctx })) { - // try to add the missing rates - await _setMissingRates({ ctx }); - // check again - if (await _areRatesInvalid({ ctx })) { - await _printRatesInfo({ ctx }); - throw new Error('Rates are still invalid after updating.'); - } + await _printRatesInfo({ ctx }); + throw new Error('Rates are still invalid after updating.'); } if (await _isCacheInvalid({ ctx })) { @@ -97,21 +95,18 @@ async function _setMissingRates({ ctx }) { // got over all rates and add aggregators for (const currencyKey of currencyKeys) { - const rate = await ExchangeRates.rateForCurrency(currencyKey); - if (rate.toString() === '0') { - // deploy an aggregator - let aggregator = await MockAggregatorFactory.deploy(); - aggregator = aggregator.connect(owner); - // set decimals - await (await aggregator.setDecimals(18)).wait(); - for (let i = 0; i < EXCHANGE_DYNAMIC_FEE_ROUNDS; i++) { - const { timestamp } = await ctx.provider.getBlock(); - // push the new price - await (await aggregator.setLatestAnswer(ethers.utils.parseEther('1'), timestamp)).wait(); - } - // set the aggregator in ExchangeRates - await (await ExchangeRates.addAggregator(currencyKey, aggregator.address)).wait(); + // deploy an aggregator + let aggregator = await MockAggregatorFactory.deploy(); + aggregator = aggregator.connect(owner); + // set decimals + await (await aggregator.setDecimals(18)).wait(); + for (let i = 0; i < EXCHANGE_DYNAMIC_FEE_ROUNDS; i++) { + const { timestamp } = await ctx.provider.getBlock(); + // push the new price + await (await aggregator.setLatestAnswer(ethers.utils.parseEther('1'), timestamp)).wait(); } + // set the aggregator in ExchangeRates + await (await ExchangeRates.addAggregator(currencyKey, aggregator.address)).wait(); } } From 4223d85fec1862ca1cd4e239e4650f5f60ad7871 Mon Sep 17 00:00:00 2001 From: Lecky Date: Mon, 10 Jan 2022 01:15:29 +1100 Subject: [PATCH 102/133] Fix stake.behavior.js - increase SNXAmount on stake --- test/integration/behaviors/stake.behavior.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/integration/behaviors/stake.behavior.js b/test/integration/behaviors/stake.behavior.js index c76d319125..79877a9188 100644 --- a/test/integration/behaviors/stake.behavior.js +++ b/test/integration/behaviors/stake.behavior.js @@ -7,7 +7,7 @@ const { skipFeePeriod, skipMinimumStakeTime } = require('../utils/skip'); function itCanStake({ ctx }) { describe('staking and claiming', () => { - const SNXAmount = ethers.utils.parseEther('1000'); + const SNXAmount = ethers.utils.parseEther('10000'); const amountToIssueAndBurnsUSD = ethers.utils.parseEther('1'); let user; From e558ac9110e1f5977a310b8eabea50ffdf0e9120 Mon Sep 17 00:00:00 2001 From: Lecky Date: Mon, 10 Jan 2022 01:51:22 +1100 Subject: [PATCH 103/133] Fix stake integration test - increase SNX stake amount --- test/integration/behaviors/stake.behavior.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/integration/behaviors/stake.behavior.js b/test/integration/behaviors/stake.behavior.js index 79877a9188..feeb97563a 100644 --- a/test/integration/behaviors/stake.behavior.js +++ b/test/integration/behaviors/stake.behavior.js @@ -7,7 +7,7 @@ const { skipFeePeriod, skipMinimumStakeTime } = require('../utils/skip'); function itCanStake({ ctx }) { describe('staking and claiming', () => { - const SNXAmount = ethers.utils.parseEther('10000'); + const SNXAmount = ethers.utils.parseEther('100000'); const amountToIssueAndBurnsUSD = ethers.utils.parseEther('1'); let user; From 60fed581d2c7fb319029540cfd405189b24082b1 Mon Sep 17 00:00:00 2001 From: Lecky Date: Mon, 10 Jan 2022 17:51:49 +1100 Subject: [PATCH 104/133] Fix 184 comments - _setCacheRate also check non-duplicate entry - remove duplicate rates in Exchanger.spec.js --- contracts/ExchangeRates.sol | 2 +- test/contracts/Exchanger.spec.js | 3 --- 2 files changed, 1 insertion(+), 4 deletions(-) diff --git a/contracts/ExchangeRates.sol b/contracts/ExchangeRates.sol index 044317170d..d6a14c96e8 100644 --- a/contracts/ExchangeRates.sol +++ b/contracts/ExchangeRates.sol @@ -120,7 +120,7 @@ contract ExchangeRates is Owned, MixinSystemSettings, IExchangeRates { uint rate, uint time ) internal { - if (rate > 0) { + if (rate > 0 && rate != cacheRates[currencyKey][roundId].rate) { cacheRates[currencyKey][roundId] = RateAndUpdatedTime({rate: uint216(rate), time: uint40(time)}); } } diff --git a/test/contracts/Exchanger.spec.js b/test/contracts/Exchanger.spec.js index 24c6d36c6d..1b98e6e48a 100644 --- a/test/contracts/Exchanger.spec.js +++ b/test/contracts/Exchanger.spec.js @@ -2158,9 +2158,6 @@ contract('Exchanger (spec tests)', async accounts => { await exchangeRates.addAggregator(sETH, aggregator.address, { from: owner }); // set a 0 rate to prevent invalid rate from causing a revert on exchange for (let i = 0; i < EXCHANGE_DYNAMIC_FEE_ROUNDS; i++) { - // Need to run twice in order to increase the roundId - // to be greater than the one in the cache - await aggregator.setLatestAnswer('0', await currentTime()); await aggregator.setLatestAnswer('0', await currentTime()); } }); From 7c1d7c46831a3909c09ea52b94c19835b63145d8 Mon Sep 17 00:00:00 2001 From: Lecky Date: Mon, 10 Jan 2022 18:02:58 +1100 Subject: [PATCH 105/133] Revert "Fix stake integration test" This reverts commit e558ac9110e1f5977a310b8eabea50ffdf0e9120. --- test/integration/behaviors/stake.behavior.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/integration/behaviors/stake.behavior.js b/test/integration/behaviors/stake.behavior.js index feeb97563a..79877a9188 100644 --- a/test/integration/behaviors/stake.behavior.js +++ b/test/integration/behaviors/stake.behavior.js @@ -7,7 +7,7 @@ const { skipFeePeriod, skipMinimumStakeTime } = require('../utils/skip'); function itCanStake({ ctx }) { describe('staking and claiming', () => { - const SNXAmount = ethers.utils.parseEther('100000'); + const SNXAmount = ethers.utils.parseEther('10000'); const amountToIssueAndBurnsUSD = ethers.utils.parseEther('1'); let user; From 2cca728d75f83ef8fd024333a59d12d20c068db4 Mon Sep 17 00:00:00 2001 From: Lecky Date: Tue, 11 Jan 2022 21:08:16 +1100 Subject: [PATCH 106/133] Change stake.behavior.js SNXAmount back to 1000 --- test/integration/behaviors/stake.behavior.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/integration/behaviors/stake.behavior.js b/test/integration/behaviors/stake.behavior.js index 79877a9188..c76d319125 100644 --- a/test/integration/behaviors/stake.behavior.js +++ b/test/integration/behaviors/stake.behavior.js @@ -7,7 +7,7 @@ const { skipFeePeriod, skipMinimumStakeTime } = require('../utils/skip'); function itCanStake({ ctx }) { describe('staking and claiming', () => { - const SNXAmount = ethers.utils.parseEther('10000'); + const SNXAmount = ethers.utils.parseEther('1000'); const amountToIssueAndBurnsUSD = ethers.utils.parseEther('1'); let user; From 9312ecd7efd77f478c8e1c77555ee9c3dc7c5426 Mon Sep 17 00:00:00 2001 From: artdgn Date: Thu, 13 Jan 2022 15:03:36 +1100 Subject: [PATCH 107/133] fix fee cap, rename methods, pass config struct --- contracts/Exchanger.sol | 95 +++++++++---------- contracts/ExchangerWithFeeRecAlternatives.sol | 2 +- contracts/MixinSystemSettings.sol | 62 ++++-------- contracts/SystemSettings.sol | 8 +- test/contracts/Exchanger.spec.js | 4 +- .../ExchangerWithFeeRecAlternatives.unit.js | 4 +- test/contracts/PurgeableSynth.js | 2 +- 7 files changed, 73 insertions(+), 104 deletions(-) diff --git a/contracts/Exchanger.sol b/contracts/Exchanger.sol index aa2787f964..9c8285de14 100644 --- a/contracts/Exchanger.sol +++ b/contracts/Exchanger.sol @@ -476,7 +476,7 @@ contract Exchanger is Owned, MixinSystemSettings, IExchanger { return (0, 0, IVirtualSynth(0)); } - entry.exchangeFeeRate = _feeRateForExchangeAtRound( + entry.exchangeFeeRate = _feeRateForExchangeAtRounds( sourceCurrencyKey, destinationCurrencyKey, entry.roundIdForSrc, @@ -628,7 +628,7 @@ contract Exchanger is Owned, MixinSystemSettings, IExchanger { bytes32[] memory synthKeys = new bytes32[](2); synthKeys[0] = sourceCurrencyKey; synthKeys[1] = destinationCurrencyKey; - require(!exchangeRates().anyRateIsInvalid(synthKeys), "Src/dest rate invalid or not found"); + require(!exchangeRates().anyRateIsInvalid(synthKeys), "src/dest rate stale or flagged"); } function _ensureCanExchangeAtRound( @@ -648,7 +648,7 @@ contract Exchanger is Owned, MixinSystemSettings, IExchanger { uint[] memory roundIds = new uint[](2); roundIds[0] = roundIdForSrc; roundIds[1] = roundIdForDest; - require(!exchangeRates().anyRateIsInvalidAtRound(synthKeys, roundIds), "Src/dest rate invalid or not found"); + require(!exchangeRates().anyRateIsInvalidAtRound(synthKeys, roundIds), "src/dest rate stale or flagged"); } function _isSynthRateInvalid(bytes32 currencyKey, uint currentRate) internal view returns (bool) { @@ -799,7 +799,7 @@ contract Exchanger is Owned, MixinSystemSettings, IExchanger { { // Get the exchange fee rate as per destination currencyKey uint baseRate = getExchangeFeeRate(destinationCurrencyKey); - return _calculateFeeRateFromExchangeSynths(baseRate, sourceCurrencyKey, destinationCurrencyKey); + return baseRate.add(_dynamicFeeForExchange(sourceCurrencyKey, destinationCurrencyKey)); } /// @notice Calculate the exchange fee for a given source and destination currency key @@ -818,73 +818,68 @@ contract Exchanger is Owned, MixinSystemSettings, IExchanger { // Get the exchange fee rate as per destination currencyKey uint baseRate = getExchangeFeeRate(destinationCurrencyKey); return - _calculateFeeRateFromExchangeSynthsAtRound( - baseRate, - sourceCurrencyKey, - destinationCurrencyKey, - roundIdForSrc, - roundIdForDest + baseRate.add( + _dynamicFeeForExchangeAtRounds(sourceCurrencyKey, destinationCurrencyKey, roundIdForSrc, roundIdForDest) ); } - function _calculateFeeRateFromExchangeSynths( - uint exchangeFeeRate, - bytes32 sourceCurrencyKey, - bytes32 destinationCurrencyKey - ) internal view returns (uint) { - uint exchangeDynamicFeeRate = _getDynamicFeeForExchange(destinationCurrencyKey); - exchangeDynamicFeeRate = exchangeDynamicFeeRate.add(_getDynamicFeeForExchange(sourceCurrencyKey)); - uint maxDynamicFee = getExchangeMaxDynamicFee(); - - exchangeFeeRate = exchangeFeeRate.add(exchangeDynamicFeeRate); - // Cap to max exchange dynamic fee - exchangeFeeRate = exchangeFeeRate > maxDynamicFee ? maxDynamicFee : exchangeFeeRate; - return exchangeFeeRate; + function _dynamicFeeForExchange(bytes32 sourceCurrencyKey, bytes32 destinationCurrencyKey) + internal + view + returns (uint dynamicFee) + { + DynamicFeeConfig memory config = getExchangeDynamicFeeConfig(); + dynamicFee = _dynamicFeeForCurrency(destinationCurrencyKey, config); + dynamicFee = dynamicFee.add(_dynamicFeeForCurrency(sourceCurrencyKey, config)); + // cap to maxFee + dynamicFee = dynamicFee > config.maxFee ? config.maxFee : dynamicFee; } - function _calculateFeeRateFromExchangeSynthsAtRound( - uint exchangeFeeRate, + function _dynamicFeeForExchangeAtRounds( bytes32 sourceCurrencyKey, bytes32 destinationCurrencyKey, uint roundIdForSrc, uint roundIdForDest - ) internal view returns (uint) { - uint exchangeDynamicFeeRate = _getDynamicFeeForExchangeAtRound(destinationCurrencyKey, roundIdForDest); - exchangeDynamicFeeRate = exchangeDynamicFeeRate.add( - _getDynamicFeeForExchangeAtRound(sourceCurrencyKey, roundIdForSrc) - ); - uint maxDynamicFee = getExchangeMaxDynamicFee(); - - exchangeFeeRate = exchangeFeeRate.add(exchangeDynamicFeeRate); - // Cap to max exchange dynamic fee - exchangeFeeRate = exchangeFeeRate > maxDynamicFee ? maxDynamicFee : exchangeFeeRate; - return exchangeFeeRate; + ) internal view returns (uint dynamicFee) { + DynamicFeeConfig memory config = getExchangeDynamicFeeConfig(); + dynamicFee = _dynamicFeeForCurrencytRound(destinationCurrencyKey, roundIdForDest, config); + dynamicFee = dynamicFee.add(_dynamicFeeForCurrencytRound(sourceCurrencyKey, roundIdForSrc, config)); + // cap to maxFee + dynamicFee = dynamicFee > config.maxFee ? config.maxFee : dynamicFee; } - /// @notice Get dynamic fee for a given currency key (SIP-184) + /// @notice Get dynamic dynamicFee for a given currency key (SIP-184) /// @param currencyKey The given currency key - /// @return The dyanmic fee - function _getDynamicFeeForExchange(bytes32 currencyKey) internal view returns (uint dynamicFee) { - // No dynamic fee for sUSD + /// @param config dynamic fee calculation configuration params + /// @return The dyanmic dynamicFee + function _dynamicFeeForCurrency(bytes32 currencyKey, DynamicFeeConfig memory config) + internal + view + returns (uint dynamicFee) + { + // No dynamic dynamicFee for sUSD if (currencyKey == sUSD) return 0; - (uint threshold, uint weightDecay, uint rounds) = getExchangeDynamicFeeData(); uint[] memory prices; uint roundId = exchangeRates().getCurrentRoundId(currencyKey); - (prices, ) = exchangeRates().ratesAndUpdatedTimeForCurrencyLastNRounds(currencyKey, rounds, roundId); - dynamicFee = DynamicFee.getDynamicFee(prices, threshold, weightDecay); + (prices, ) = exchangeRates().ratesAndUpdatedTimeForCurrencyLastNRounds(currencyKey, config.rounds, roundId); + dynamicFee = DynamicFee.getDynamicFee(prices, config.threshold, config.weightDecay); } - /// @notice Get dynamic fee for a given currency key (SIP-184) + /// @notice Get dynamicFee for a given currency key (SIP-184) /// @param currencyKey The given currency key /// @param roundId The round id - /// @return The dyanmic fee - function _getDynamicFeeForExchangeAtRound(bytes32 currencyKey, uint roundId) internal view returns (uint dynamicFee) { - // No dynamic fee for sUSD + /// @param config dynamic fee calculation configuration params + /// @return The dyanmic dynamicFee + function _dynamicFeeForCurrencytRound( + bytes32 currencyKey, + uint roundId, + DynamicFeeConfig memory config + ) internal view returns (uint dynamicFee) { + // No dynamic dynamicFee for sUSD if (currencyKey == sUSD) return 0; - (uint threshold, uint weightDecay, uint rounds) = getExchangeDynamicFeeData(); uint[] memory prices; - (prices, ) = exchangeRates().ratesAndUpdatedTimeForCurrencyLastNRounds(currencyKey, rounds, roundId); - dynamicFee = DynamicFee.getDynamicFee(prices, threshold, weightDecay); + (prices, ) = exchangeRates().ratesAndUpdatedTimeForCurrencyLastNRounds(currencyKey, config.rounds, roundId); + dynamicFee = DynamicFee.getDynamicFee(prices, config.threshold, config.weightDecay); } function getAmountsForExchange( diff --git a/contracts/ExchangerWithFeeRecAlternatives.sol b/contracts/ExchangerWithFeeRecAlternatives.sol index b6e767a958..e47258a7c4 100644 --- a/contracts/ExchangerWithFeeRecAlternatives.sol +++ b/contracts/ExchangerWithFeeRecAlternatives.sol @@ -279,7 +279,7 @@ contract ExchangerWithFeeRecAlternatives is MinimalProxyFactory, Exchanger { baseRate = getExchangeFeeRate(destinationCurrencyKey); } - return _calculateFeeRateFromExchangeSynths(baseRate, sourceCurrencyKey, destinationCurrencyKey); + return baseRate.add(_dynamicFeeForExchange(sourceCurrencyKey, destinationCurrencyKey)); } function _getAmountsForAtomicExchangeMinusFees( diff --git a/contracts/MixinSystemSettings.sol b/contracts/MixinSystemSettings.sol index 300a76c93b..c4f0ca4d8b 100644 --- a/contracts/MixinSystemSettings.sol +++ b/contracts/MixinSystemSettings.sol @@ -55,6 +55,13 @@ contract MixinSystemSettings is MixinResolver { enum CrossDomainMessageGasLimits {Deposit, Escrow, Reward, Withdrawal, Relay} + struct DynamicFeeConfig { + uint threshold; + uint weightDecay; + uint rounds; + uint maxFee; + } + constructor(address _resolver) internal MixinResolver(_resolver) {} function resolverAddressesRequired() public view returns (bytes32[] memory addresses) { @@ -138,50 +145,17 @@ contract MixinSystemSettings is MixinResolver { ); } - /// @notice Get exchange dynamic fee threshold constant default 40bps - /// @return uint threshold constant - function getExchangeDynamicFeeThreshold() internal view returns (uint) { - return flexibleStorage().getUIntValue(SETTING_CONTRACT_NAME, SETTING_EXCHANGE_DYNAMIC_FEE_THRESHOLD); - } - - /// @notice Get exchange dynamic fee weight decay constant default 0.9 - /// @return uint weight decay constant - function getExchangeDynamicFeeWeightDecay() internal view returns (uint) { - return flexibleStorage().getUIntValue(SETTING_CONTRACT_NAME, SETTING_EXCHANGE_DYNAMIC_FEE_WEIGHT_DECAY); - } - - /// @notice Get exchange dynamic fee rounds default to 10 rounds - /// @return uint last N round - function getExchangeDynamicFeeRounds() internal view returns (uint) { - return flexibleStorage().getUIntValue(SETTING_CONTRACT_NAME, SETTING_EXCHANGE_DYNAMIC_FEE_ROUNDS); - } - - /// @notice Get exchange dynamic fee related data - /// @return threshold, weight decay, and rounds - function getExchangeDynamicFeeData() - internal - view - returns ( - uint threshold, - uint weightDecay, - uint rounds - ) - { - bytes32[] memory data = new bytes32[](3); - uint[] memory result = new uint[](3); - data[0] = SETTING_EXCHANGE_DYNAMIC_FEE_THRESHOLD; - data[1] = SETTING_EXCHANGE_DYNAMIC_FEE_WEIGHT_DECAY; - data[2] = SETTING_EXCHANGE_DYNAMIC_FEE_ROUNDS; - result = flexibleStorage().getUIntValues(SETTING_CONTRACT_NAME, data); - threshold = result[0]; - weightDecay = result[1]; - rounds = result[2]; - } - - /// @notice Get exchange max dynamic fee - /// @return max dynamic fee - function getExchangeMaxDynamicFee() internal view returns (uint) { - return flexibleStorage().getUIntValue(SETTING_CONTRACT_NAME, SETTING_EXCHANGE_MAX_DYNAMIC_FEE); + /// @notice Get exchange dynamic fee related keys + /// @return threshold, weight decay, rounds, and max fee + function getExchangeDynamicFeeConfig() internal view returns (DynamicFeeConfig memory) { + bytes32[] memory keys = new bytes32[](4); + uint[] memory values = new uint[](4); + keys[0] = SETTING_EXCHANGE_DYNAMIC_FEE_THRESHOLD; + keys[1] = SETTING_EXCHANGE_DYNAMIC_FEE_WEIGHT_DECAY; + keys[2] = SETTING_EXCHANGE_DYNAMIC_FEE_ROUNDS; + keys[3] = SETTING_EXCHANGE_MAX_DYNAMIC_FEE; + values = flexibleStorage().getUIntValues(SETTING_CONTRACT_NAME, keys); + return DynamicFeeConfig({threshold: values[0], weightDecay: values[1], rounds: values[2], maxFee: values[3]}); } /* ========== End Exchange Related Fees ========== */ diff --git a/contracts/SystemSettings.sol b/contracts/SystemSettings.sol index 3cd319c348..2dff7dc288 100644 --- a/contracts/SystemSettings.sol +++ b/contracts/SystemSettings.sol @@ -92,25 +92,25 @@ contract SystemSettings is Owned, MixinSystemSettings, ISystemSettings { /// @notice Get the dynamic fee threshold /// @return The dynamic fee threshold function exchangeDynamicFeeThreshold() external view returns (uint) { - return getExchangeDynamicFeeThreshold(); + return getExchangeDynamicFeeConfig().threshold; } /// @notice Get the dynamic fee weight decay per round /// @return The dynamic fee weight decay per round function exchangeDynamicFeeWeightDecay() external view returns (uint) { - return getExchangeDynamicFeeWeightDecay(); + return getExchangeDynamicFeeConfig().weightDecay; } /// @notice Get the dynamic fee total rounds for calculation /// @return The dynamic fee total rounds for calculation function exchangeDynamicFeeRounds() external view returns (uint) { - return getExchangeDynamicFeeRounds(); + return getExchangeDynamicFeeConfig().rounds; } /// @notice Get the max dynamic fee /// @return The max dynamic fee function exchangeMaxDynamicFee() external view returns (uint) { - return getExchangeMaxDynamicFee(); + return getExchangeDynamicFeeConfig().maxFee; } /* ========== End Exchange Related Fees ========== */ diff --git a/test/contracts/Exchanger.spec.js b/test/contracts/Exchanger.spec.js index 1b98e6e48a..993de083a1 100644 --- a/test/contracts/Exchanger.spec.js +++ b/test/contracts/Exchanger.spec.js @@ -1870,7 +1870,7 @@ contract('Exchanger (spec tests)', async accounts => { it(`attempting to ${type} from sUSD into sAUD reverts with dest stale`, async () => { await assert.revert( exchange({ from: sUSD, amount: amountIssued, to: sAUD }), - 'Src/dest rate invalid or not found' + 'src/dest rate stale or flagged' ); }); it('settling still works ', async () => { @@ -1893,7 +1893,7 @@ contract('Exchanger (spec tests)', async accounts => { it(`${type} back to sUSD fails as the source has no rate`, async () => { await assert.revert( exchange({ from: sAUD, amount: amountIssued, to: sUSD }), - 'Src/dest rate invalid or not found' + 'src/dest rate stale or flagged' ); }); }); diff --git a/test/contracts/ExchangerWithFeeRecAlternatives.unit.js b/test/contracts/ExchangerWithFeeRecAlternatives.unit.js index c507886df0..9648db9160 100644 --- a/test/contracts/ExchangerWithFeeRecAlternatives.unit.js +++ b/test/contracts/ExchangerWithFeeRecAlternatives.unit.js @@ -251,7 +251,7 @@ contract('ExchangerWithFeeRecAlternatives (unit tests)', async accounts => { it('reverts when either rate is invalid', async () => { await assert.revert( this.instance.exchange(...getExchangeArgs()), - 'Src/dest rate invalid or not found' + 'src/dest rate stale or flagged' ); }); }); @@ -423,7 +423,7 @@ contract('ExchangerWithFeeRecAlternatives (unit tests)', async accounts => { it('reverts when either rate is invalid', async () => { await assert.revert( this.instance.exchangeAtomically(...getExchangeArgs()), - 'Src/dest rate invalid or not found' + 'src/dest rate stale or flagged' ); }); }); diff --git a/test/contracts/PurgeableSynth.js b/test/contracts/PurgeableSynth.js index d3f0137022..d04cd5ce9e 100644 --- a/test/contracts/PurgeableSynth.js +++ b/test/contracts/PurgeableSynth.js @@ -203,7 +203,7 @@ contract('PurgeableSynth', accounts => { it('then purge() reverts', async () => { await assert.revert( iETHContract.purge([account1], { from: owner }), - 'Src/dest rate invalid or not found' + 'src/dest rate stale or flagged' ); }); describe('when rates are received', () => { From 8b92538a30555f277bdce55f07708cd326d2910b Mon Sep 17 00:00:00 2001 From: artdgn Date: Thu, 13 Jan 2022 15:04:42 +1100 Subject: [PATCH 108/133] fix linking libs after merge, minor caching change --- contracts/ExchangeRates.sol | 19 ++++++++++++++----- test/contracts/setup.js | 20 +++++++++----------- 2 files changed, 23 insertions(+), 16 deletions(-) diff --git a/contracts/ExchangeRates.sol b/contracts/ExchangeRates.sol index d6a14c96e8..aa049bdb35 100644 --- a/contracts/ExchangeRates.sol +++ b/contracts/ExchangeRates.sol @@ -120,8 +120,14 @@ contract ExchangeRates is Owned, MixinSystemSettings, IExchangeRates { uint rate, uint time ) internal { - if (rate > 0 && rate != cacheRates[currencyKey][roundId].rate) { - cacheRates[currencyKey][roundId] = RateAndUpdatedTime({rate: uint216(rate), time: uint40(time)}); + if (rate > 0) { + // valid rate + RateAndUpdatedTime storage cacheEntry = cacheRates[currencyKey][roundId]; + if (cacheEntry.rate != rate) { + // check if cached this round already to avoid SSTORE for same round + cacheEntry.rate = uint216(rate); + cacheEntry.time = uint40(time); + } } } @@ -477,11 +483,14 @@ contract ExchangeRates is Owned, MixinSystemSettings, IExchangeRates { // which are used in atomic swaps and fee reclamation return (SafeDecimalMath.unit(), 0); } else { - if (cacheRates[currencyKey][roundId].rate != 0) { - return (cacheRates[currencyKey][roundId].rate, cacheRates[currencyKey][roundId].time); + // read cache + RateAndUpdatedTime memory cachedEntry = cacheRates[currencyKey][roundId]; + if (cachedEntry.rate != 0) { + return (cachedEntry.rate, cachedEntry.time); } - AggregatorV2V3Interface aggregator = aggregators[currencyKey]; + // if no valid cache entry - read from aggregator + AggregatorV2V3Interface aggregator = aggregators[currencyKey]; if (aggregator != AggregatorV2V3Interface(0)) { // this view from the aggregator is the most gas efficient but it can throw when there's no data, // so let's call it low-level to suppress any reverts diff --git a/test/contracts/setup.js b/test/contracts/setup.js index 564157cf8a..0b0d8bc06e 100644 --- a/test/contracts/setup.js +++ b/test/contracts/setup.js @@ -126,25 +126,23 @@ const setupContract = async ({ ); }; - // Linking library if needed + // Linking libraries if needed if (Object.keys((await artifacts.readArtifact(contract)).linkReferences).length > 0) { const safeDecimalMath = await artifacts.require('SafeDecimalMath').new(); - artifact.link(safeDecimalMath); - // link DynamicFee lib into Exchanger - if ( - /^Exchanger$|^ExchangerWithVirtualSynth$|^ExchangerWithFeeRecAlternatives$/.test( - artifact._json.contractName - ) - ) { + if (artifact._json.contractName.startsWith('Exchanger')) { + // SafeDecimalMath -> DynamicFee -> Exchanger* + artifact.link(safeDecimalMath); const DynamicFee = artifacts.require('DynamicFee'); DynamicFee.link(safeDecimalMath); artifact.link(await DynamicFee.new()); - } - // link SystemSettingsLib into SystemSettings - if (artifact._json.contractName === 'SystemSettings') { + } else if (artifact._json.contractName === 'SystemSettings') { + // SafeDecimalMath -> SystemSettingsLib -> SystemSettings const SystemSettingsLib = artifacts.require('SystemSettingsLib'); SystemSettingsLib.link(safeDecimalMath); artifact.link(await SystemSettingsLib.new()); + } else { + // SafeDecimalMath -> anything else that expects linking + artifact.link(safeDecimalMath); } } From 23f7ab9da8d79f9ab2ec726881dcfec0ea16be6e Mon Sep 17 00:00:00 2001 From: artdgn Date: Thu, 13 Jan 2022 15:08:08 +1100 Subject: [PATCH 109/133] rename dynamic fee test file --- contracts/Exchanger.sol | 2 +- contracts/test-helpers/TestableDynamicFee.sol | 6 ++--- .../{TestableDynamicFee.js => DynamicFee.js} | 23 ++++++++----------- 3 files changed, 14 insertions(+), 17 deletions(-) rename test/contracts/{TestableDynamicFee.js => DynamicFee.js} (82%) diff --git a/contracts/Exchanger.sol b/contracts/Exchanger.sol index 9c8285de14..e06b2287f6 100644 --- a/contracts/Exchanger.sol +++ b/contracts/Exchanger.sol @@ -809,7 +809,7 @@ contract Exchanger is Owned, MixinSystemSettings, IExchanger { /// @param roundIdForDest The round id of the target currency. /// @return The exchange fee rate /// @return The exchange dynamic fee rate - function _feeRateForExchangeAtRound( + function _feeRateForExchangeAtRounds( bytes32 sourceCurrencyKey, bytes32 destinationCurrencyKey, uint roundIdForSrc, diff --git a/contracts/test-helpers/TestableDynamicFee.sol b/contracts/test-helpers/TestableDynamicFee.sol index bb88adca89..28a4705de6 100644 --- a/contracts/test-helpers/TestableDynamicFee.sol +++ b/contracts/test-helpers/TestableDynamicFee.sol @@ -7,15 +7,15 @@ contract TestableDynamicFee { uint public threshold = (4 * SafeDecimalMath.unit()) / 1000; uint public weightDecay = (9 * SafeDecimalMath.unit()) / 10; - function testGetPriceDifferential(uint price, uint previousPrice) external view returns (uint) { + function getPriceDifferential(uint price, uint previousPrice) external view returns (uint) { return DynamicFee.getPriceDifferential(price, previousPrice, threshold); } - function testGetPriceWeight(uint round) external view returns (uint) { + function getPriceWeight(uint round) external view returns (uint) { return DynamicFee.getRoundDecay(round, weightDecay); } - function testGetDynamicFee(uint[] calldata prices) external view returns (uint) { + function getDynamicFee(uint[] calldata prices) external view returns (uint) { return DynamicFee.getDynamicFee(prices, threshold, weightDecay); } } diff --git a/test/contracts/TestableDynamicFee.js b/test/contracts/DynamicFee.js similarity index 82% rename from test/contracts/TestableDynamicFee.js rename to test/contracts/DynamicFee.js index b040a13436..48c4cb851d 100644 --- a/test/contracts/TestableDynamicFee.js +++ b/test/contracts/DynamicFee.js @@ -17,24 +17,21 @@ contract('DynamicFee', () => { }); it('Can get price differential', async () => { - const priceDiff1 = await testableDynamicFee.testGetPriceDifferential(toUnit('8'), toUnit('10')); + const priceDiff1 = await testableDynamicFee.getPriceDifferential(toUnit('8'), toUnit('10')); assert.bnEqual(priceDiff1, '196000000000000000'); - const priceDiff2 = await testableDynamicFee.testGetPriceDifferential( - toUnit('12'), - toUnit('10') - ); + const priceDiff2 = await testableDynamicFee.getPriceDifferential(toUnit('12'), toUnit('10')); assert.bnEqual(priceDiff2, '196000000000000000'); assert.bnEqual(priceDiff1, priceDiff2); }); it('Can get price weight', async () => { - const priceWeight0 = await testableDynamicFee.testGetPriceWeight('0'); + const priceWeight0 = await testableDynamicFee.getPriceWeight('0'); assert.bnEqual(priceWeight0, toUnit('1')); - const priceWeight1 = await testableDynamicFee.testGetPriceWeight('1'); + const priceWeight1 = await testableDynamicFee.getPriceWeight('1'); assert.bnEqual(priceWeight1, toUnit('0.9')); - const priceWeight2 = await testableDynamicFee.testGetPriceWeight('2'); + const priceWeight2 = await testableDynamicFee.getPriceWeight('2'); assert.bnEqual(priceWeight2, toUnit('0.81')); }); @@ -53,7 +50,7 @@ contract('DynamicFee', () => { toUnit('49960.65493467'), toUnit('49994'), ]; - const dynamicFee = await testableDynamicFee.testGetDynamicFee(prices); + const dynamicFee = await testableDynamicFee.getDynamicFee(prices); assert.bnEqual(dynamicFee, '0'); }); @@ -71,7 +68,7 @@ contract('DynamicFee', () => { toUnit('49871.92313713'), toUnit('49981'), ]; - const dynamicFee = await testableDynamicFee.testGetDynamicFee(prices); + const dynamicFee = await testableDynamicFee.getDynamicFee(prices); assert.bnEqual(dynamicFee, '2064427530203592'); }); @@ -89,7 +86,7 @@ contract('DynamicFee', () => { toUnit('49234.65005734'), toUnit('49535.05178912'), ]; - const dynamicFee = await testableDynamicFee.testGetDynamicFee(prices); + const dynamicFee = await testableDynamicFee.getDynamicFee(prices); assert.bnEqual(dynamicFee, '799801523256537'); }); @@ -108,7 +105,7 @@ contract('DynamicFee', () => { toUnit('47222.35138239'), toUnit('47382.88726893'), ]; - const dynamicFee = await testableDynamicFee.testGetDynamicFee(prices); + const dynamicFee = await testableDynamicFee.getDynamicFee(prices); assert.bnEqual(dynamicFee, '18366333809739328'); }); @@ -126,7 +123,7 @@ contract('DynamicFee', () => { toUnit('47670.81054939'), toUnit('47911.8471578599'), ]; - const dynamicFee = await testableDynamicFee.testGetDynamicFee(prices); + const dynamicFee = await testableDynamicFee.getDynamicFee(prices); assert.bnEqual(dynamicFee, '4502723211780442'); }); }); From 68b7de13e8149c4fac6987a239c5ce7bef856188 Mon Sep 17 00:00:00 2001 From: artdgn Date: Thu, 13 Jan 2022 15:19:53 +1100 Subject: [PATCH 110/133] cap total fee amount to 100% --- contracts/Exchanger.sol | 17 ++++++++++------- 1 file changed, 10 insertions(+), 7 deletions(-) diff --git a/contracts/Exchanger.sol b/contracts/Exchanger.sol index e06b2287f6..323a079a35 100644 --- a/contracts/Exchanger.sol +++ b/contracts/Exchanger.sol @@ -795,11 +795,13 @@ contract Exchanger is Owned, MixinSystemSettings, IExchanger { function _feeRateForExchange(bytes32 sourceCurrencyKey, bytes32 destinationCurrencyKey) internal view - returns (uint exchangeFeeRate) + returns (uint feeRate) { // Get the exchange fee rate as per destination currencyKey uint baseRate = getExchangeFeeRate(destinationCurrencyKey); - return baseRate.add(_dynamicFeeForExchange(sourceCurrencyKey, destinationCurrencyKey)); + feeRate = baseRate.add(_dynamicFeeForExchange(sourceCurrencyKey, destinationCurrencyKey)); + // cap fee rate to 100% to prevent negative amounts + feeRate = feeRate > SafeDecimalMath.unit() ? SafeDecimalMath.unit() : feeRate; } /// @notice Calculate the exchange fee for a given source and destination currency key @@ -814,13 +816,14 @@ contract Exchanger is Owned, MixinSystemSettings, IExchanger { bytes32 destinationCurrencyKey, uint roundIdForSrc, uint roundIdForDest - ) internal view returns (uint exchangeFeeRate) { + ) internal view returns (uint feeRate) { // Get the exchange fee rate as per destination currencyKey uint baseRate = getExchangeFeeRate(destinationCurrencyKey); - return - baseRate.add( - _dynamicFeeForExchangeAtRounds(sourceCurrencyKey, destinationCurrencyKey, roundIdForSrc, roundIdForDest) - ); + uint dynamicFee = + _dynamicFeeForExchangeAtRounds(sourceCurrencyKey, destinationCurrencyKey, roundIdForSrc, roundIdForDest); + feeRate = baseRate.add(dynamicFee); + // cap fee rate to 100% to prevent negative amounts + feeRate = feeRate > SafeDecimalMath.unit() ? SafeDecimalMath.unit() : feeRate; } function _dynamicFeeForExchange(bytes32 sourceCurrencyKey, bytes32 destinationCurrencyKey) From d80a0c0d1516b960f5dc6b1e9ce5cb3a540fdd52 Mon Sep 17 00:00:00 2001 From: artdgn Date: Thu, 13 Jan 2022 16:09:28 +1100 Subject: [PATCH 111/133] more efficient dynamic fee calculation --- contracts/DynamicFee.sol | 32 +++++++++++-------- contracts/test-helpers/TestableDynamicFee.sol | 2 +- test/contracts/DynamicFee.js | 10 +++--- test/contracts/Exchanger.spec.js | 4 +-- test/utils/index.js | 1 + 5 files changed, 27 insertions(+), 22 deletions(-) diff --git a/contracts/DynamicFee.sol b/contracts/DynamicFee.sol index f63b9afbaa..a967aa6b13 100644 --- a/contracts/DynamicFee.sol +++ b/contracts/DynamicFee.sol @@ -17,18 +17,19 @@ library DynamicFee { /// A system constant for the price differential default to 40 bps /// @return uint price differential with 18 decimals /// only return if non-zero value, otherwise return 0 - function getPriceDifferential( + function priceDeviation( uint price, uint previousPrice, uint threshold ) public pure returns (uint) { require(price > 0, "Price cannot be 0"); require(previousPrice > 0, "Previous price cannot be 0"); - - int abs = int(price.divideDecimal(previousPrice)) - int(SafeDecimalMath.unit()); - abs = abs > 0 ? abs : -abs; - int priceDifferential = abs - int(threshold); - return priceDifferential > 0 ? uint(priceDifferential) : uint(0); + // abs difference between prices + uint absDelta = price > previousPrice ? price - previousPrice : previousPrice - price; + // relative to previous price + uint deviationRatio = absDelta.divideDecimal(previousPrice); + // must be over threshold + return deviationRatio > threshold ? deviationRatio - threshold : 0; } /// @notice Calculate decay based on round @@ -40,11 +41,11 @@ library DynamicFee { return weightDecay.powDecimal(round); } - /// @notice Calculate dynamic fee - /// @param prices A list of prices from the current round to the previous rounds - /// @param threshold A threshold to determine the price differential - /// @param weightDecay A weight decay constant - /// @return uint dynamic fee + // /// @notice Calculate dynamic fee + // /// @param prices A list of prices from the current round to the previous rounds + // /// @param threshold A threshold to determine the price differential + // /// @param weightDecay A weight decay constant + // /// @return uint dynamic fee function getDynamicFee( uint[] calldata prices, uint threshold, @@ -56,9 +57,12 @@ library DynamicFee { return dynamicFee; } for (uint i = size - 1; i > 0; i--) { - uint priceDifferential = getPriceDifferential(prices[i - 1], prices[i], threshold); - uint roundDecay = getRoundDecay(i - 1, weightDecay); - dynamicFee = dynamicFee.add(priceDifferential.multiplyDecimal(roundDecay)); + // apply decay from previous round (will be 0 for first round) + dynamicFee = dynamicFee.multiplyDecimal(weightDecay); + // calculate price deviation + uint deviation = priceDeviation(prices[i - 1], prices[i], threshold); + // add to total fee + dynamicFee = dynamicFee.add(deviation); } } } diff --git a/contracts/test-helpers/TestableDynamicFee.sol b/contracts/test-helpers/TestableDynamicFee.sol index 28a4705de6..38e0a3ba04 100644 --- a/contracts/test-helpers/TestableDynamicFee.sol +++ b/contracts/test-helpers/TestableDynamicFee.sol @@ -8,7 +8,7 @@ contract TestableDynamicFee { uint public weightDecay = (9 * SafeDecimalMath.unit()) / 10; function getPriceDifferential(uint price, uint previousPrice) external view returns (uint) { - return DynamicFee.getPriceDifferential(price, previousPrice, threshold); + return DynamicFee.priceDeviation(price, previousPrice, threshold); } function getPriceWeight(uint round) external view returns (uint) { diff --git a/test/contracts/DynamicFee.js b/test/contracts/DynamicFee.js index 48c4cb851d..8e237c3054 100644 --- a/test/contracts/DynamicFee.js +++ b/test/contracts/DynamicFee.js @@ -1,6 +1,6 @@ const { contract, artifacts } = require('hardhat'); const { assert } = require('./common'); -const { toUnit } = require('../utils')(); +const { toUnit, toBN } = require('../utils')(); const SafeDecimalMath = artifacts.require('SafeDecimalMath'); const DynamicFee = artifacts.require('DynamicFee'); const TestableDynamicFee = artifacts.require('TestableDynamicFee'); @@ -69,7 +69,7 @@ contract('DynamicFee', () => { toUnit('49981'), ]; const dynamicFee = await testableDynamicFee.getDynamicFee(prices); - assert.bnEqual(dynamicFee, '2064427530203592'); + assert.bnClose(dynamicFee, toUnit(20.6442753020364).div(toBN(10000)), 1e9); }); it('Can get dynamic fee according to SIP feasibility spreadsheet round 23-32, first one above threshold', async () => { @@ -87,7 +87,7 @@ contract('DynamicFee', () => { toUnit('49535.05178912'), ]; const dynamicFee = await testableDynamicFee.getDynamicFee(prices); - assert.bnEqual(dynamicFee, '799801523256537'); + assert.bnClose(dynamicFee, toUnit(7.99801523256557).div(toBN(10000)), 1e9); }); it('Can get dynamic fee according to SIP feasibility spreadsheet round 63-72, 70% above threshold', async () => { @@ -106,7 +106,7 @@ contract('DynamicFee', () => { toUnit('47382.88726893'), ]; const dynamicFee = await testableDynamicFee.getDynamicFee(prices); - assert.bnEqual(dynamicFee, '18366333809739328'); + assert.bnClose(dynamicFee, toUnit(183.663338097394).div(toBN(10000)), 1e9); }); it('Can get the same dynamic fee according to SIP feasibility spreadsheet round 58-67, 50% above threshold', async () => { @@ -124,6 +124,6 @@ contract('DynamicFee', () => { toUnit('47911.8471578599'), ]; const dynamicFee = await testableDynamicFee.getDynamicFee(prices); - assert.bnEqual(dynamicFee, '4502723211780442'); + assert.bnClose(dynamicFee, toUnit(45.0272321178039).div(toBN(10000)), 1e9); }); }); diff --git a/test/contracts/Exchanger.spec.js b/test/contracts/Exchanger.spec.js index 993de083a1..1b98e6e48a 100644 --- a/test/contracts/Exchanger.spec.js +++ b/test/contracts/Exchanger.spec.js @@ -1870,7 +1870,7 @@ contract('Exchanger (spec tests)', async accounts => { it(`attempting to ${type} from sUSD into sAUD reverts with dest stale`, async () => { await assert.revert( exchange({ from: sUSD, amount: amountIssued, to: sAUD }), - 'src/dest rate stale or flagged' + 'Src/dest rate invalid or not found' ); }); it('settling still works ', async () => { @@ -1893,7 +1893,7 @@ contract('Exchanger (spec tests)', async accounts => { it(`${type} back to sUSD fails as the source has no rate`, async () => { await assert.revert( exchange({ from: sAUD, amount: amountIssued, to: sUSD }), - 'src/dest rate stale or flagged' + 'Src/dest rate invalid or not found' ); }); }); diff --git a/test/utils/index.js b/test/utils/index.js index fcad276337..38a9830308 100644 --- a/test/utils/index.js +++ b/test/utils/index.js @@ -592,6 +592,7 @@ module.exports = ({ web3 } = {}) => { divideDecimalRound, powerToDecimal, + toBN, toUnit, fromUnit, From c4445f13dfb411ad1a13aa34d41bb97cb1753764 Mon Sep 17 00:00:00 2001 From: artdgn Date: Thu, 13 Jan 2022 16:56:17 +1100 Subject: [PATCH 112/133] remove dynamic fee lib, move into exchanger --- contracts/DynamicFee.sol | 68 ------------------- contracts/Exchanger.sol | 51 +++++++++++++- contracts/test-helpers/TestableDynamicFee.sol | 27 ++++---- publish/deployed/local-ovm/config.json | 5 +- publish/deployed/local/config.json | 3 - publish/releases.json | 2 +- publish/src/Deployer.js | 2 +- publish/src/commands/deploy/deploy-core.js | 4 -- test/contracts/DynamicFee.js | 66 +++++++++++------- ...changerWithFeeRecAlternatives.behaviors.js | 3 - test/contracts/setup.js | 8 +-- 11 files changed, 110 insertions(+), 129 deletions(-) delete mode 100644 contracts/DynamicFee.sol diff --git a/contracts/DynamicFee.sol b/contracts/DynamicFee.sol deleted file mode 100644 index a967aa6b13..0000000000 --- a/contracts/DynamicFee.sol +++ /dev/null @@ -1,68 +0,0 @@ -pragma solidity ^0.5.16; - -// Libraries -import "./SafeDecimalMath.sol"; -import "./Math.sol"; - -library DynamicFee { - using SafeDecimalMath for uint; - using Math for uint; - using SafeMath for uint; - - /// @notice Calculate price differential - - /// The difference between the current price and the previous price - /// @param price Current round price - /// @param previousPrice Previous round price - /// @param threshold Threshold constant - - /// A system constant for the price differential default to 40 bps - /// @return uint price differential with 18 decimals - /// only return if non-zero value, otherwise return 0 - function priceDeviation( - uint price, - uint previousPrice, - uint threshold - ) public pure returns (uint) { - require(price > 0, "Price cannot be 0"); - require(previousPrice > 0, "Previous price cannot be 0"); - // abs difference between prices - uint absDelta = price > previousPrice ? price - previousPrice : previousPrice - price; - // relative to previous price - uint deviationRatio = absDelta.divideDecimal(previousPrice); - // must be over threshold - return deviationRatio > threshold ? deviationRatio - threshold : 0; - } - - /// @notice Calculate decay based on round - /// @param round A round number that go back - /// from the current round from 0 to N - /// @param weightDecay Weight decay constant - /// @return uint decay with 18 decimals - function getRoundDecay(uint round, uint weightDecay) public pure returns (uint) { - return weightDecay.powDecimal(round); - } - - // /// @notice Calculate dynamic fee - // /// @param prices A list of prices from the current round to the previous rounds - // /// @param threshold A threshold to determine the price differential - // /// @param weightDecay A weight decay constant - // /// @return uint dynamic fee - function getDynamicFee( - uint[] calldata prices, - uint threshold, - uint weightDecay - ) external pure returns (uint dynamicFee) { - uint size = prices.length; - // disable dynamic fee when price feeds less than 2 rounds - if (size < 2) { - return dynamicFee; - } - for (uint i = size - 1; i > 0; i--) { - // apply decay from previous round (will be 0 for first round) - dynamicFee = dynamicFee.multiplyDecimal(weightDecay); - // calculate price deviation - uint deviation = priceDeviation(prices[i - 1], prices[i], threshold); - // add to total fee - dynamicFee = dynamicFee.add(deviation); - } - } -} diff --git a/contracts/Exchanger.sol b/contracts/Exchanger.sol index 323a079a35..d7af580e9a 100644 --- a/contracts/Exchanger.sol +++ b/contracts/Exchanger.sol @@ -7,7 +7,7 @@ import "./MixinSystemSettings.sol"; import "./interfaces/IExchanger.sol"; // Libraries -import "./DynamicFee.sol"; +import "./SafeDecimalMath.sol"; // Internal references import "./interfaces/ISystemStatus.sol"; @@ -865,7 +865,7 @@ contract Exchanger is Owned, MixinSystemSettings, IExchanger { uint[] memory prices; uint roundId = exchangeRates().getCurrentRoundId(currencyKey); (prices, ) = exchangeRates().ratesAndUpdatedTimeForCurrencyLastNRounds(currencyKey, config.rounds, roundId); - dynamicFee = DynamicFee.getDynamicFee(prices, config.threshold, config.weightDecay); + dynamicFee = _dynamicFeeCalculation(prices, config.threshold, config.weightDecay); } /// @notice Get dynamicFee for a given currency key (SIP-184) @@ -882,7 +882,52 @@ contract Exchanger is Owned, MixinSystemSettings, IExchanger { if (currencyKey == sUSD) return 0; uint[] memory prices; (prices, ) = exchangeRates().ratesAndUpdatedTimeForCurrencyLastNRounds(currencyKey, config.rounds, roundId); - dynamicFee = DynamicFee.getDynamicFee(prices, config.threshold, config.weightDecay); + dynamicFee = _dynamicFeeCalculation(prices, config.threshold, config.weightDecay); + } + + /// @notice Calculate dynamic fee according to SIP-184 + /// @param prices A list of prices from the current round to the previous rounds + /// @param threshold A threshold to clip the price deviation ratop + /// @param weightDecay A weight decay constant + /// @return uint dynamic fee rate as decimal + function _dynamicFeeCalculation( + uint[] memory prices, + uint threshold, + uint weightDecay + ) internal pure returns (uint dynamicFee) { + uint size = prices.length; + // short circuit if there is a single price + if (size <= 1) { + return dynamicFee; + } + // go backwards in price array + for (uint i = size - 1; i > 0; i--) { + // apply decay from previous round (will be 0 for first round) + dynamicFee = dynamicFee.multiplyDecimal(weightDecay); + // calculate price deviation + uint deviation = _thresholdedAbsDeviationRatio(prices[i - 1], prices[i], threshold); + // add to total fee + dynamicFee = dynamicFee.add(deviation); + } + } + + /// absolute price deviation ratio used by dynamic fee calculation + /// deviationRatio = (abs(current - previous) / previous) - threshold + /// if negative, zero is returned + function _thresholdedAbsDeviationRatio( + uint price, + uint previousPrice, + uint threshold + ) public pure returns (uint deviationRatio) { + if (previousPrice == 0) { + return 0; // don't divide by zero + } + // abs difference between prices + uint absDelta = price > previousPrice ? price - previousPrice : previousPrice - price; + // relative to previous price + deviationRatio = absDelta.divideDecimal(previousPrice); + // only the positive difference from threshold + deviationRatio = deviationRatio > threshold ? deviationRatio - threshold : 0; } function getAmountsForExchange( diff --git a/contracts/test-helpers/TestableDynamicFee.sol b/contracts/test-helpers/TestableDynamicFee.sol index 38e0a3ba04..121a10e323 100644 --- a/contracts/test-helpers/TestableDynamicFee.sol +++ b/contracts/test-helpers/TestableDynamicFee.sol @@ -1,21 +1,24 @@ pragma solidity ^0.5.16; // Libraries -import "../DynamicFee.sol"; +import "../Exchanger.sol"; -contract TestableDynamicFee { - uint public threshold = (4 * SafeDecimalMath.unit()) / 1000; - uint public weightDecay = (9 * SafeDecimalMath.unit()) / 10; +contract TestableDynamicFee is Exchanger { + constructor(address _owner, address _resolver) public Exchanger(_owner, _resolver) {} - function getPriceDifferential(uint price, uint previousPrice) external view returns (uint) { - return DynamicFee.priceDeviation(price, previousPrice, threshold); + function thresholdedAbsDeviationRatio( + uint price, + uint previousPrice, + uint threshold + ) external view returns (uint) { + return _thresholdedAbsDeviationRatio(price, previousPrice, threshold); } - function getPriceWeight(uint round) external view returns (uint) { - return DynamicFee.getRoundDecay(round, weightDecay); - } - - function getDynamicFee(uint[] calldata prices) external view returns (uint) { - return DynamicFee.getDynamicFee(prices, threshold, weightDecay); + function dynamicFeeCalculation( + uint[] calldata prices, + uint threshold, + uint weightDecay + ) external view returns (uint) { + return _dynamicFeeCalculation(prices, threshold, weightDecay); } } diff --git a/publish/deployed/local-ovm/config.json b/publish/deployed/local-ovm/config.json index 188fbfd1ce..16cedd4427 100644 --- a/publish/deployed/local-ovm/config.json +++ b/publish/deployed/local-ovm/config.json @@ -160,11 +160,8 @@ }, "SynthRedeemer": { "deploy": true - }, + }, "SystemSettingsLib": { "deploy": true - }, - "DynamicFee": { - "deploy": true } } diff --git a/publish/deployed/local/config.json b/publish/deployed/local/config.json index d2d60bcf6e..9b492c6221 100644 --- a/publish/deployed/local/config.json +++ b/publish/deployed/local/config.json @@ -391,8 +391,5 @@ }, "SystemSettingsLib": { "deploy": true - }, - "DynamicFee": { - "deploy": true } } diff --git a/publish/releases.json b/publish/releases.json index f721336814..526d03e017 100644 --- a/publish/releases.json +++ b/publish/releases.json @@ -405,7 +405,7 @@ { "sip": 184, "layer": "both", - "sources": ["Exchanger", "ExchangeRates", "SystemSettings", "DynamicFee"] + "sources": ["Exchanger", "ExchangeRates", "SystemSettings"] }, { "sip": 187, diff --git a/publish/src/Deployer.js b/publish/src/Deployer.js index 2bfa8e2645..b98872cba6 100644 --- a/publish/src/Deployer.js +++ b/publish/src/Deployer.js @@ -184,7 +184,7 @@ class Deployer { // Any contract after SafeDecimalMath can automatically get linked. // Doing this with bytecode that doesn't require the library is a no-op. let bytecode = compiled.evm.bytecode.object; - ['SafeDecimalMath', 'Math', 'SystemSettingsLib', 'DynamicFee'].forEach(contractName => { + ['SafeDecimalMath', 'Math', 'SystemSettingsLib'].forEach(contractName => { if (this.deployedContracts[contractName]) { bytecode = linker.linkBytecode(bytecode, { [source + '.sol']: { diff --git a/publish/src/commands/deploy/deploy-core.js b/publish/src/commands/deploy/deploy-core.js index c2875bce95..223bec3c57 100644 --- a/publish/src/commands/deploy/deploy-core.js +++ b/publish/src/commands/deploy/deploy-core.js @@ -22,10 +22,6 @@ module.exports = async ({ name: 'SafeDecimalMath', }); - await deployer.deployContract({ - name: 'DynamicFee', - }); - await deployer.deployContract({ name: 'Math', }); diff --git a/test/contracts/DynamicFee.js b/test/contracts/DynamicFee.js index 8e237c3054..89c53d668f 100644 --- a/test/contracts/DynamicFee.js +++ b/test/contracts/DynamicFee.js @@ -2,39 +2,39 @@ const { contract, artifacts } = require('hardhat'); const { assert } = require('./common'); const { toUnit, toBN } = require('../utils')(); const SafeDecimalMath = artifacts.require('SafeDecimalMath'); -const DynamicFee = artifacts.require('DynamicFee'); const TestableDynamicFee = artifacts.require('TestableDynamicFee'); -contract('DynamicFee', () => { +contract('DynamicFee', accounts => { + const [, owner, account1] = accounts; + let testableDynamicFee; + const threshold = toUnit('0.004'); + const weightDecay = toUnit('0.9'); + before(async () => { const safeDecimalMath = await SafeDecimalMath.new(); - DynamicFee.link(safeDecimalMath); TestableDynamicFee.link(safeDecimalMath); - TestableDynamicFee.link(await DynamicFee.new()); - testableDynamicFee = await TestableDynamicFee.new(); + const addressResolver = account1; // is not important for these tests + testableDynamicFee = await TestableDynamicFee.new(owner, addressResolver); }); it('Can get price differential', async () => { - const priceDiff1 = await testableDynamicFee.getPriceDifferential(toUnit('8'), toUnit('10')); + const priceDiff1 = await testableDynamicFee.thresholdedAbsDeviationRatio( + toUnit('8'), + toUnit('10'), + threshold + ); assert.bnEqual(priceDiff1, '196000000000000000'); - const priceDiff2 = await testableDynamicFee.getPriceDifferential(toUnit('12'), toUnit('10')); + const priceDiff2 = await testableDynamicFee.thresholdedAbsDeviationRatio( + toUnit('12'), + toUnit('10'), + threshold + ); assert.bnEqual(priceDiff2, '196000000000000000'); assert.bnEqual(priceDiff1, priceDiff2); }); - it('Can get price weight', async () => { - const priceWeight0 = await testableDynamicFee.getPriceWeight('0'); - assert.bnEqual(priceWeight0, toUnit('1')); - - const priceWeight1 = await testableDynamicFee.getPriceWeight('1'); - assert.bnEqual(priceWeight1, toUnit('0.9')); - - const priceWeight2 = await testableDynamicFee.getPriceWeight('2'); - assert.bnEqual(priceWeight2, toUnit('0.81')); - }); - it('Can get dynamic fee according to SIP feasibility spreadsheet round 13-22, all below threshold', async () => { const prices = [ toUnit('49535.05178912'), @@ -50,7 +50,11 @@ contract('DynamicFee', () => { toUnit('49960.65493467'), toUnit('49994'), ]; - const dynamicFee = await testableDynamicFee.getDynamicFee(prices); + const dynamicFee = await testableDynamicFee.dynamicFeeCalculation( + prices, + threshold, + weightDecay + ); assert.bnEqual(dynamicFee, '0'); }); @@ -68,7 +72,11 @@ contract('DynamicFee', () => { toUnit('49871.92313713'), toUnit('49981'), ]; - const dynamicFee = await testableDynamicFee.getDynamicFee(prices); + const dynamicFee = await testableDynamicFee.dynamicFeeCalculation( + prices, + threshold, + weightDecay + ); assert.bnClose(dynamicFee, toUnit(20.6442753020364).div(toBN(10000)), 1e9); }); @@ -86,7 +94,11 @@ contract('DynamicFee', () => { toUnit('49234.65005734'), toUnit('49535.05178912'), ]; - const dynamicFee = await testableDynamicFee.getDynamicFee(prices); + const dynamicFee = await testableDynamicFee.dynamicFeeCalculation( + prices, + threshold, + weightDecay + ); assert.bnClose(dynamicFee, toUnit(7.99801523256557).div(toBN(10000)), 1e9); }); @@ -105,7 +117,11 @@ contract('DynamicFee', () => { toUnit('47222.35138239'), toUnit('47382.88726893'), ]; - const dynamicFee = await testableDynamicFee.getDynamicFee(prices); + const dynamicFee = await testableDynamicFee.dynamicFeeCalculation( + prices, + threshold, + weightDecay + ); assert.bnClose(dynamicFee, toUnit(183.663338097394).div(toBN(10000)), 1e9); }); @@ -123,7 +139,11 @@ contract('DynamicFee', () => { toUnit('47670.81054939'), toUnit('47911.8471578599'), ]; - const dynamicFee = await testableDynamicFee.getDynamicFee(prices); + const dynamicFee = await testableDynamicFee.dynamicFeeCalculation( + prices, + threshold, + weightDecay + ); assert.bnClose(dynamicFee, toUnit(45.0272321178039).div(toBN(10000)), 1e9); }); }); diff --git a/test/contracts/ExchangerWithFeeRecAlternatives.behaviors.js b/test/contracts/ExchangerWithFeeRecAlternatives.behaviors.js index ff93158feb..a8f1160883 100644 --- a/test/contracts/ExchangerWithFeeRecAlternatives.behaviors.js +++ b/test/contracts/ExchangerWithFeeRecAlternatives.behaviors.js @@ -23,9 +23,6 @@ module.exports = function({ accounts }) { before(async () => { const safeDecimalMath = await artifacts.require('SafeDecimalMath').new(); ExchangerWithFeeRecAlternatives.link(safeDecimalMath); - const DynamicFee = artifacts.require('DynamicFee'); - DynamicFee.link(safeDecimalMath); - ExchangerWithFeeRecAlternatives.link(await DynamicFee.new()); }); beforeEach(async () => { diff --git a/test/contracts/setup.js b/test/contracts/setup.js index 0b0d8bc06e..9a7bc91892 100644 --- a/test/contracts/setup.js +++ b/test/contracts/setup.js @@ -129,13 +129,7 @@ const setupContract = async ({ // Linking libraries if needed if (Object.keys((await artifacts.readArtifact(contract)).linkReferences).length > 0) { const safeDecimalMath = await artifacts.require('SafeDecimalMath').new(); - if (artifact._json.contractName.startsWith('Exchanger')) { - // SafeDecimalMath -> DynamicFee -> Exchanger* - artifact.link(safeDecimalMath); - const DynamicFee = artifacts.require('DynamicFee'); - DynamicFee.link(safeDecimalMath); - artifact.link(await DynamicFee.new()); - } else if (artifact._json.contractName === 'SystemSettings') { + if (artifact._json.contractName === 'SystemSettings') { // SafeDecimalMath -> SystemSettingsLib -> SystemSettings const SystemSettingsLib = artifacts.require('SystemSettingsLib'); SystemSettingsLib.link(safeDecimalMath); From 3d3efd4c919f386659a0a084576ef981ab17b132 Mon Sep 17 00:00:00 2001 From: artdgn Date: Thu, 13 Jan 2022 23:16:10 +1100 Subject: [PATCH 113/133] remove caching as it doesn't help with dynamic fee --- contracts/ExchangeRates.sol | 91 ++++++------------- contracts/Exchanger.sol | 8 +- contracts/SystemSettingsLib.sol | 16 ++-- contracts/interfaces/IExchangeRates.sol | 39 ++++---- test/contracts/ExchangeRates.js | 44 ++++++--- test/contracts/Exchanger.spec.js | 4 +- ...changerWithFeeRecAlternatives.behaviors.js | 4 +- 7 files changed, 93 insertions(+), 113 deletions(-) diff --git a/contracts/ExchangeRates.sol b/contracts/ExchangeRates.sol index aa049bdb35..6f041a03b3 100644 --- a/contracts/ExchangeRates.sol +++ b/contracts/ExchangeRates.sol @@ -69,68 +69,6 @@ contract ExchangeRates is Owned, MixinSystemSettings, IExchangeRates { } } - /// @notice Same as effectiveValueAndRates but at historical rates. - /// @dev Needs to be mutative as it's calling setRate to cache the rate. - /// It can be call by anyone as rate is always returned by the oracle. - /// @param sourceCurrencyKey The currency key of the source currency. - /// @param sourceAmount The amount of the source currency. - /// @param destinationCurrencyKey The currency key of the destination currency. - /// @param roundIdForSrc The round id of the source currency. - /// @param roundIdForDest The round id of the target currency. - /// @return value of the target currency. - /// @return rate of the source currency. - /// @return rate of the destination currency. - function mutativeEffectiveValueAndRatesAtRound( - bytes32 sourceCurrencyKey, - uint sourceAmount, - bytes32 destinationCurrencyKey, - uint roundIdForSrc, - uint roundIdForDest - ) - external - returns ( - uint value, - uint sourceRate, - uint destinationRate - ) - { - uint time; - (sourceRate, time) = _getRateAndTimestampAtRound(sourceCurrencyKey, roundIdForSrc); - // cacheing to save external call - _setCacheRate(sourceCurrencyKey, roundIdForSrc, sourceRate, time); - // If there's no change in the currency, then just return the amount they gave us - if (sourceCurrencyKey == destinationCurrencyKey) { - destinationRate = sourceRate; - value = sourceAmount; - } else { - (destinationRate, time) = _getRateAndTimestampAtRound(destinationCurrencyKey, roundIdForDest); - // cacheing to save external call - _setCacheRate(destinationCurrencyKey, roundIdForDest, destinationRate, time); - // prevent divide-by 0 error (this happens if the dest is not a valid rate) - if (destinationRate > 0) { - // Calculate the effective value by going from source -> USD -> destination - value = sourceAmount.multiplyDecimalRound(sourceRate).divideDecimalRound(destinationRate); - } - } - } - - function _setCacheRate( - bytes32 currencyKey, - uint roundId, - uint rate, - uint time - ) internal { - if (rate > 0) { - // valid rate - RateAndUpdatedTime storage cacheEntry = cacheRates[currencyKey][roundId]; - if (cacheEntry.rate != rate) { - // check if cached this round already to avoid SSTORE for same round - cacheEntry.rate = uint216(rate); - cacheEntry.time = uint40(time); - } - } - } - /* ========== VIEWS ========== */ function currenciesUsingAggregator(address aggregator) external view returns (bytes32[] memory currencies) { @@ -201,6 +139,35 @@ contract ExchangeRates is Owned, MixinSystemSettings, IExchangeRates { value = sourceAmount.multiplyDecimalRound(srcRate).divideDecimalRound(destRate); } + function effectiveValueAndRatesAtRound( + bytes32 sourceCurrencyKey, + uint sourceAmount, + bytes32 destinationCurrencyKey, + uint roundIdForSrc, + uint roundIdForDest + ) + external + view + returns ( + uint value, + uint sourceRate, + uint destinationRate + ) + { + (sourceRate, ) = _getRateAndTimestampAtRound(sourceCurrencyKey, roundIdForSrc); + // If there's no change in the currency, then just return the amount they gave us + if (sourceCurrencyKey == destinationCurrencyKey) { + value = sourceAmount; + } else { + (destinationRate, ) = _getRateAndTimestampAtRound(destinationCurrencyKey, roundIdForDest); + // prevent divide-by 0 error (this happens if the dest is not a valid rate) + if (destinationRate > 0) { + // Calculate the effective value by going from source -> USD -> destination + value = sourceAmount.multiplyDecimalRound(sourceRate).divideDecimalRound(destinationRate); + } + } + } + function rateAndTimestampAtRound(bytes32 currencyKey, uint roundId) external view returns (uint rate, uint time) { return _getRateAndTimestampAtRound(currencyKey, roundId); } diff --git a/contracts/Exchanger.sol b/contracts/Exchanger.sol index d7af580e9a..95eebb3f74 100644 --- a/contracts/Exchanger.sol +++ b/contracts/Exchanger.sol @@ -206,8 +206,8 @@ contract Exchanger is Owned, MixinSystemSettings, IExchanger { (uint srcRoundIdAtPeriodEnd, uint destRoundIdAtPeriodEnd) = getRoundIdsAtPeriodEnd(exchangeEntry); // given these round ids, determine what effective value they should have received - uint destinationAmount = - exchangeRates().effectiveValueAtRound( + (uint destinationAmount, , ) = + exchangeRates().effectiveValueAndRatesAtRound( exchangeEntry.src, exchangeEntry.amount, exchangeEntry.dest, @@ -441,9 +441,7 @@ contract Exchanger is Owned, MixinSystemSettings, IExchanger { entry.roundIdForSrc = exchangeRates().getCurrentRoundId(sourceCurrencyKey); entry.roundIdForDest = exchangeRates().getCurrentRoundId(destinationCurrencyKey); - // Puting the exchangeRate call first as it's mutative for cheap cache reading later on - (entry.destinationAmount, entry.sourceRate, entry.destinationRate) = exchangeRates() - .mutativeEffectiveValueAndRatesAtRound( + (entry.destinationAmount, entry.sourceRate, entry.destinationRate) = exchangeRates().effectiveValueAndRatesAtRound( sourceCurrencyKey, sourceAmount, destinationCurrencyKey, diff --git a/contracts/SystemSettingsLib.sol b/contracts/SystemSettingsLib.sol index 1a137ea43d..17d171aa92 100644 --- a/contracts/SystemSettingsLib.sol +++ b/contracts/SystemSettingsLib.sol @@ -76,10 +76,10 @@ library SystemSettingsLib { function setIssuanceRatio( IFlexibleStorage flexibleStorage, bytes32 settingName, - uint issuanceRatio + uint ratio ) external { - require(issuanceRatio <= MAX_ISSUANCE_RATIO, "New issuance ratio cannot exceed MAX_ISSUANCE_RATIO"); - flexibleStorage.setUIntValue(SETTINGS_CONTRACT_NAME, settingName, issuanceRatio); + require(ratio <= MAX_ISSUANCE_RATIO, "New issuance ratio cannot exceed MAX_ISSUANCE_RATIO"); + flexibleStorage.setUIntValue(SETTINGS_CONTRACT_NAME, settingName, ratio); } function setTradingRewardsEnabled( @@ -120,12 +120,12 @@ library SystemSettingsLib { function setTargetThreshold( IFlexibleStorage flexibleStorage, bytes32 settingName, - uint _percent - ) external returns (uint targetThreshold) { - require(_percent <= MAX_TARGET_THRESHOLD, "Threshold too high"); - targetThreshold = _percent.mul(SafeDecimalMath.unit()).div(100); + uint percent + ) external returns (uint threshold) { + require(percent <= MAX_TARGET_THRESHOLD, "Threshold too high"); + threshold = percent.mul(SafeDecimalMath.unit()).div(100); - flexibleStorage.setUIntValue(SETTINGS_CONTRACT_NAME, settingName, targetThreshold); + flexibleStorage.setUIntValue(SETTINGS_CONTRACT_NAME, settingName, threshold); } function setLiquidationDelay( diff --git a/contracts/interfaces/IExchangeRates.sol b/contracts/interfaces/IExchangeRates.sol index bcc6501956..4364384d52 100644 --- a/contracts/interfaces/IExchangeRates.sol +++ b/contracts/interfaces/IExchangeRates.sol @@ -25,6 +25,14 @@ interface IExchangeRates { bytes32 destinationCurrencyKey ) external view returns (uint value); + function effectiveValueAtRound( + bytes32 sourceCurrencyKey, + uint sourceAmount, + bytes32 destinationCurrencyKey, + uint roundIdForSrc, + uint roundIdForDest + ) external view returns (uint value); + function effectiveValueAndRates( bytes32 sourceCurrencyKey, uint sourceAmount, @@ -38,40 +46,33 @@ interface IExchangeRates { uint destinationRate ); - function effectiveAtomicValueAndRates( + function effectiveValueAndRatesAtRound( bytes32 sourceCurrencyKey, uint sourceAmount, - bytes32 destinationCurrencyKey + bytes32 destinationCurrencyKey, + uint roundIdForSrc, + uint roundIdForDest ) external view returns ( uint value, - uint systemValue, - uint systemSourceRate, - uint systemDestinationRate + uint sourceRate, + uint destinationRate ); - function effectiveValueAtRound( - bytes32 sourceCurrencyKey, - uint sourceAmount, - bytes32 destinationCurrencyKey, - uint roundIdForSrc, - uint roundIdForDest - ) external view returns (uint value); - - function mutativeEffectiveValueAndRatesAtRound( + function effectiveAtomicValueAndRates( bytes32 sourceCurrencyKey, uint sourceAmount, - bytes32 destinationCurrencyKey, - uint roundIdForSrc, - uint roundIdForDest + bytes32 destinationCurrencyKey ) external + view returns ( uint value, - uint sourceRate, - uint destinationRate + uint systemValue, + uint systemSourceRate, + uint systemDestinationRate ); function getCurrentRoundId(bytes32 currencyKey) external view returns (uint); diff --git a/test/contracts/ExchangeRates.js b/test/contracts/ExchangeRates.js index 57433e7050..68b49c8d7c 100644 --- a/test/contracts/ExchangeRates.js +++ b/test/contracts/ExchangeRates.js @@ -55,11 +55,7 @@ contract('Exchange Rates', async accounts => { let mockFlagsInterface; const itIncludesCorrectMutativeFunctions = contract => { - const baseFunctions = [ - 'addAggregator', - 'removeAggregator', - 'mutativeEffectiveValueAndRatesAtRound', - ]; + const baseFunctions = ['addAggregator', 'removeAggregator']; const withDexPricingFunctions = baseFunctions.concat(['setDexPriceAggregator']); it('only expected functions should be mutative', () => { @@ -1115,7 +1111,7 @@ contract('Exchange Rates', async accounts => { }); }); - describe('effectiveValueAtRound()', () => { + describe('effectiveValueAndRatesAtRound()', () => { describe('when both aggregated prices have been given three rates with current timestamps', () => { beforeEach(async () => { await setupAggregators([sBNB]); @@ -1130,43 +1126,61 @@ contract('Exchange Rates', async accounts => { }); it('accepts various changes to src roundId', async () => { assert.bnEqual( - await instance.effectiveValueAtRound(sJPY, toUnit('1'), sBNB, '1', '1'), + ( + await instance.effectiveValueAndRatesAtRound(sJPY, toUnit('1'), sBNB, '1', '1') + )[0], toUnit('0.1') ); assert.bnEqual( - await instance.effectiveValueAtRound(sJPY, toUnit('1'), sBNB, '2', '1'), + ( + await instance.effectiveValueAndRatesAtRound(sJPY, toUnit('1'), sBNB, '2', '1') + )[0], toUnit('0.2') ); assert.bnEqual( - await instance.effectiveValueAtRound(sJPY, toUnit('1'), sBNB, '3', '1'), + ( + await instance.effectiveValueAndRatesAtRound(sJPY, toUnit('1'), sBNB, '3', '1') + )[0], toUnit('0.3') ); }); it('accepts various changes to dest roundId', async () => { assert.bnEqual( - await instance.effectiveValueAtRound(sJPY, toUnit('1'), sBNB, '1', '1'), + ( + await instance.effectiveValueAndRatesAtRound(sJPY, toUnit('1'), sBNB, '1', '1') + )[0], toUnit('0.1') ); assert.bnEqual( - await instance.effectiveValueAtRound(sJPY, toUnit('1'), sBNB, '1', '2'), + ( + await instance.effectiveValueAndRatesAtRound(sJPY, toUnit('1'), sBNB, '1', '2') + )[0], toUnit('0.05') ); assert.bnEqual( - await instance.effectiveValueAtRound(sJPY, toUnit('1'), sBNB, '1', '3'), + ( + await instance.effectiveValueAndRatesAtRound(sJPY, toUnit('1'), sBNB, '1', '3') + )[0], toUnit('0.025') ); }); it('and combinations therein', async () => { assert.bnEqual( - await instance.effectiveValueAtRound(sJPY, toUnit('1'), sBNB, '2', '2'), + ( + await instance.effectiveValueAndRatesAtRound(sJPY, toUnit('1'), sBNB, '2', '2') + )[0], toUnit('0.1') ); assert.bnEqual( - await instance.effectiveValueAtRound(sJPY, toUnit('1'), sBNB, '3', '3'), + ( + await instance.effectiveValueAndRatesAtRound(sJPY, toUnit('1'), sBNB, '3', '3') + )[0], toUnit('0.075') ); assert.bnEqual( - await instance.effectiveValueAtRound(sJPY, toUnit('1'), sBNB, '3', '2'), + ( + await instance.effectiveValueAndRatesAtRound(sJPY, toUnit('1'), sBNB, '3', '2') + )[0], toUnit('0.15') ); }); diff --git a/test/contracts/Exchanger.spec.js b/test/contracts/Exchanger.spec.js index 1b98e6e48a..993de083a1 100644 --- a/test/contracts/Exchanger.spec.js +++ b/test/contracts/Exchanger.spec.js @@ -1870,7 +1870,7 @@ contract('Exchanger (spec tests)', async accounts => { it(`attempting to ${type} from sUSD into sAUD reverts with dest stale`, async () => { await assert.revert( exchange({ from: sUSD, amount: amountIssued, to: sAUD }), - 'Src/dest rate invalid or not found' + 'src/dest rate stale or flagged' ); }); it('settling still works ', async () => { @@ -1893,7 +1893,7 @@ contract('Exchanger (spec tests)', async accounts => { it(`${type} back to sUSD fails as the source has no rate`, async () => { await assert.revert( exchange({ from: sAUD, amount: amountIssued, to: sUSD }), - 'Src/dest rate invalid or not found' + 'src/dest rate stale or flagged' ); }); }); diff --git a/test/contracts/ExchangerWithFeeRecAlternatives.behaviors.js b/test/contracts/ExchangerWithFeeRecAlternatives.behaviors.js index a8f1160883..281b320fb4 100644 --- a/test/contracts/ExchangerWithFeeRecAlternatives.behaviors.js +++ b/test/contracts/ExchangerWithFeeRecAlternatives.behaviors.js @@ -179,9 +179,9 @@ module.exports = function({ accounts }) { }); }, whenMockedEffectiveRateAsEqualAtRound: cb => { - describe(`when mocked with exchange rates at round giving an effective value of 1:1`, () => { + describe(`when mocked with exchange rates giving an effective value of 1:1`, () => { beforeEach(async () => { - this.mocks.ExchangeRates.smocked.mutativeEffectiveValueAndRatesAtRound.will.return.with( + this.mocks.ExchangeRates.smocked.effectiveValueAndRatesAtRound.will.return.with( (srcKey, amount, destKey) => [amount, (1e18).toString(), (1e18).toString()] ); }); From ef4b2d3d8a12b0a532f0f653dcaa615ac6e19746 Mon Sep 17 00:00:00 2001 From: artdgn Date: Fri, 14 Jan 2022 17:43:15 +1100 Subject: [PATCH 114/133] add spreadheet filename & formulas --- test/contracts/DynamicFee.js | 10 +- test/dynamic-fee-calc.csv | 249 ++++++++++++++++++----------------- 2 files changed, 130 insertions(+), 129 deletions(-) diff --git a/test/contracts/DynamicFee.js b/test/contracts/DynamicFee.js index 89c53d668f..a122dbfb11 100644 --- a/test/contracts/DynamicFee.js +++ b/test/contracts/DynamicFee.js @@ -35,7 +35,7 @@ contract('DynamicFee', accounts => { assert.bnEqual(priceDiff1, priceDiff2); }); - it('Can get dynamic fee according to SIP feasibility spreadsheet round 13-22, all below threshold', async () => { + it('Fee is similar to dynamic-fee-calc.csv rounds 22-11, all below threshold', async () => { const prices = [ toUnit('49535.05178912'), toUnit('49714.05205647'), @@ -58,7 +58,7 @@ contract('DynamicFee', accounts => { assert.bnEqual(dynamicFee, '0'); }); - it('Can get dynamic fee according to SIP feasibility spreadsheet round 14-23, last one above threshold', async () => { + it('Fee is similar to dynamic-fee-calc.csv rounds 23-14, last one above threshold', async () => { const prices = [ toUnit('49234.65005734'), toUnit('49535.05178912'), @@ -80,7 +80,7 @@ contract('DynamicFee', accounts => { assert.bnClose(dynamicFee, toUnit(20.6442753020364).div(toBN(10000)), 1e9); }); - it('Can get dynamic fee according to SIP feasibility spreadsheet round 23-32, first one above threshold', async () => { + it('Fee is similar to dynamic-fee-calc.csv rounds 32-22, first one above threshold', async () => { const prices = [ toUnit('49198.77'), toUnit('49143.5399999999'), @@ -102,7 +102,7 @@ contract('DynamicFee', accounts => { assert.bnClose(dynamicFee, toUnit(7.99801523256557).div(toBN(10000)), 1e9); }); - it('Can get dynamic fee according to SIP feasibility spreadsheet round 63-72, 70% above threshold', async () => { + it('Fee is similar to dynamic-fee-calc.csv rounds 72-63, 70% above threshold', async () => { const prices = [ toUnit('44661.70868763'), toUnit('44672.6561639399'), @@ -125,7 +125,7 @@ contract('DynamicFee', accounts => { assert.bnClose(dynamicFee, toUnit(183.663338097394).div(toBN(10000)), 1e9); }); - it('Can get the same dynamic fee according to SIP feasibility spreadsheet round 58-67, 50% above threshold', async () => { + it('Fee is similar to dynamic-fee-calc.csv rounds 67-58, 50% above threshold', async () => { const prices = [ toUnit('46183.17440371'), toUnit('46217.7336139799'), diff --git a/test/dynamic-fee-calc.csv b/test/dynamic-fee-calc.csv index 8cb80d1c6b..bbbf2b3086 100644 --- a/test/dynamic-fee-calc.csv +++ b/test/dynamic-fee-calc.csv @@ -1,124 +1,125 @@ -,,,,,,, -,round,price,ΔP (bp),boost,dynamic_fee (bp),,deviation threshold (bp) -,1,50007.73057899,,0,,,20 -,2,49953.81262846,10.8,0,,, -,3,50051.305,19.5,0,,,exchange fee (bp) -,4,50069.7111646299,3.7,0,,,20 -,5,50070.40589905,0.1,0,,, -,6,50078.36597318,1.6,0,,,boost threshold (bp) -,7,50080.61,0.4,0,,,40 -,8,50070.3465,2.0,0,,, -,9,50074.192,0.8,0,,,boost decay constant -,10,50076.03,0.4,0,,,0.9 -,11,49994,16.4,0,0,, -,12,49960.65493467,6.7,0,0,, -,13,49981,4.1,0,0,, -,14,49871.92313713,21.8,0,0,, -,15,49933.34034209,12.3,0,0,,max weight -,16,49842.74988613,18.1,0,0,,1 -,17,49838.87627216,0.8,0,0,, -,18,49722.83886705,23.3,0,0,,decay constant -,19,49714.05205647,1.8,0,0,,0.9 -,20,49691.8024553899,4.5,0,0,, -,21,49714.05205647,4.5,0,0,,0.387420489 -,22,49535.05178912,36.0,0,0,,0.43046721 -,23,49234.65005734,60.6,20.6,20.6442753020359,,0.4782969 -,24,49190.99117585,8.9,0,18.5798477718323,,0.531441 -,25,49234.65005734,8.9,0,16.7218629946491,,0.59049 -,26,49088.63670793,29.7,0,15.0496766951842,,0.6561 -,27,49046.17079819,8.7,0,13.5447090256658,,0.729 -,28,49088.63670793,8.7,0,12.2,,0.81 -,29,49131.10261767,8.7,0,11.0,,0.9 -,30,49096.77,7.0,0,9.9,,1 -,31,49143.5399999999,9.5,0,8.9,, -,32,49198.77,11.2,0,8.0,, -,33,49254,11.2,0,0,, -,34,49208,9.3,0,0,, -,35,49101.41,21.7,0,0,, -,36,49095.24231598,1.3,0,0,, -,37,49093.89744338,0.3,0,0,, -,38,49054.03062906,8.1,0,0,, -,39,49009.46274509,9.1,0,0,, -,40,49054.03062906,9.1,0,0,, -,41,48964.89486113,18.2,0,0,, -,42,48954.93260767,2.0,0,0,, -,43,48364.4121895,120.6,80.6,80.625315308366,, -,44,48342.34371195,4.6,0,72.5627837775294,, -,45,48267.37667219,15.5,0,65.3065053997765,, -,46,48225.00037245,8.8,0,58.7758548597988,, -,47,47783.75,91.5,51.5,104.396531487276,, -,48,48198.4621158,86.8,46.8,140.7,, -,49,48043.1218078999,32.2,0,126.7,, -,50,48198.4621158,32.3,0,114.0,, -,51,48044.80901916,31.9,0,102.6,, -,52,48108.4029515399,13.2,0,92.3,, -,53,48112,0.7,0,55.0,, -,54,48056.45718986,11.5,0,49.5,, -,55,47988.17926024,14.2,0,44.5,, -,56,47902.24487106,17.9,0,40.1,, -,57,47911.8471578599,2.0,0,18.1,, -,58,47670.81054939,50.3,10.3,10.3083522694779,, -,59,47580.67384441,18.9,0,9.27751704253011,, -,60,47449.76309439,27.5,0,8.3497653382771,, -,61,47382.88726893,14.1,0,7.51478880444939,, -,62,47222.35138239,33.9,0,6.76330992400445,, -,63,46948.76815888,57.9,17.9,24.0,, -,64,46675.18493538,58.3,18.3,39.9,, -,65,46463.74676537,45.3,5.3,41.2,, -,66,46217.7336139799,52.9,12.9,50.0,, -,67,46183.17440371,7.5,0,45.0,, -,68,45919.00562933,57.2,17.2,54.1,, -,69,45586.5085919099,72.4,32.4,81.1,, -,70,45483.8961602099,22.5,0,73.0,, -,71,44672.6561639399,178.4,138.4,204.1,, -,72,44661.70868763,2.5,0,183.7,, -,73,44650.76121132,2.5,0,159.0,, -,74,44668.7953606,4.0,0,136.8,, -,75,44774.49388229,23.7,0,121.2,, -,76,44853.05,17.5,0,104.6,, -,77,45204.4549999999,78.3,38.3,132.5,, -,78,45231.276,5.9,0,113.2,, -,79,45477.5075,54.4,14.4,105.1,, -,80,45568.3515,20.0,0,94.6,, -,81,45468.09,22.0,0,36.8537523828239,, -,82,45757.7,63.7,23.7,56.9,, -,83,45846.1785,19.3,0,51.2,, -,84,46060.93771094,46.8,6.8,52.9,, -,85,46081.215,4.4,0,47.6,, -,86,46167.82,18.8,0,42.9,, -,87,45860.8944999999,66.5,26.5,51.7,, -,88,45844.31854774,3.6,0,46.5,, -,89,46040.66042188,42.8,2.8,39.7,, -,90,45975.5952109399,14.1,0,35.7,, -,91,45942.48948481,7.2,0,32.1,, -,92,46040.66042188,21.4,0,20.6,, -,93,46090.3302109399,10.8,0,18.6,, -,94,46132.86,9.2,0,14.3,, -,95,45874.42216374,56.0,16.0,28.9,, -,96,45830.84933011,9.5,0,26.0,, -,97,45888.1962499999,12.5,0,14.2,, -,98,46038.42444459,32.7,0,12.8,, -,99,46039.54243323,0.2,0,10.5109430861616,, -,100,46218.23018745,38.8,0,9.5,, -,101,46213.94,0.9,0,8.5,, -,102,46437.773,48.4,8.4,16.1,, -,103,46506.479,14.8,0,14.5,, -,104,46519.9745,2.9,0,13.0,, -,105,46533.47,2.9,0,6.14844957170929,, -,106,46622.632,19.2,0,5.53360461453836,, -,107,46619.216,0.7,0,5.0,, -,108,46615.8,0.7,0,4.5,, -,109,46707.1025,19.6,0,4.0,, -,110,46826.18,25.5,0,3.6,, -,111,46819.33062006,1.5,0,3.3,, -,112,46826.18,1.5,0,0,, -,113,46961.35210329,28.9,0,0,, -,114,47001.22734027,8.5,0,0,, -,115,46829.45375,36.5,0,0,, -,116,46796.05875,7.1,0,0,, -,117,46903.6069999999,23.0,0,0,, -,118,46789.378,24.4,0,0,, -,119,46752.593,7.9,0,0,, -,120,46766.34475,2.9,0,0,, -,121,46780.0965,2.9,0,0,, -,122,46946.0995,35.5,0,0,, \ No newline at end of file +To recreate / alter see formulas at the bottom,,,,,,, +,round,price,ΔP (bp),boost,dynamic_fee (bp),,deviation threshold (bp) +,1,50007.73057899,,,,,20 +,2,49953.81262846,10.7819230958361,0,,, +,3,50051.305,19.5165026271593,0,,,exchange fee (bp) +,4,50069.7111646299,3.67745948480325,0,,,20 +,5,50070.40589905,0.13875343075398,0,,, +,6,50078.36597318,1.58977623349932,0,,,boost threshold (bp) +,7,50080.61,0.448103043377834,0,,,40 +,8,50070.3465,2.04939596382681,0,0,, +,9,50074.192,0.768019450394508,0,0,,boost decay constant +,10,50076.03,0.367055348591272,0,0,,0.9 +,11,49994,16.3810909131568,0,0,, +,12,49960.65493467,6.66981344361384,0,0,, +,13,49981,4.07221749927134,0,0,, +,14,49871.92313713,21.8236655669157,0,0,, +,15,49933.34034209,12.3149862882022,0,0,,max weight +,16,49842.74988613,18.1422783533758,0,0,,1 +,17,49838.87627216,0.777166985940214,0,0,, +,18,49722.83886705,23.2825083126564,0,0,,decay constant +,19,49714.05205647,1.76715786552206,0,0,,0.9 +,20,49691.8024553899,4.47551550511793,0,0,, +,21,49714.05205647,4.47751942587837,0,0,,0.387420489 +,22,49535.05178912,36.0059701322823,0,0,,0.43046721 +,23,49234.65005734,60.6442753020364,20.6442753020364,20.6442753020364,,0.4782969 +,24,49190.99117585,8.86751128303942,0,18.5798477718328,,0.531441 +,25,49234.65005734,8.8753815376319,0,16.7218629946495,,0.59049 +,26,49088.63670793,29.6566237883167,0,15.0496766951846,,0.6561 +,27,49046.17079819,8.65086353745492,0,13.5447090256661,,0.729 +,28,49088.63670793,8.65835376113955,0,12.1902381230995,,0.81 +,29,49131.10261767,8.65086353745381,0,10.9712143107895,,0.9 +,30,49096.77,6.9879599359246,0,9.87409287971058,,1 +,31,49143.5399999999,9.52608491350926,0,8.88668359173952,, +,32,49198.77,11.2385066277465,0,7.99801523256557,, +,33,49254,11.2258904033591,0,0,, +,34,49208,9.33934299752304,0,0,, +,35,49101.41,21.6611120143062,0,0,, +,36,49095.24231598,1.2561113866183,0,0,, +,37,49093.89744338,0.273931349873413,0,0,, +,38,49054.03062906,8.12052340435687,0,0,, +,39,49009.46274509,9.08546828843004,0,0,, +,40,49054.03062906,9.09373036831695,0,0,, +,41,48964.89486113,18.1709365748206,0,0,, +,42,48954.93260767,2.03457058128076,0,0,, +,43,48364.4121895,120.625315308367,80.625315308367,80.625315308367,, +,44,48342.34371195,4.56295787562344,0,72.5627837775303,, +,45,48267.37667219,15.5075310801422,0,65.3065053997772,, +,46,48225.00037245,8.77949096504738,0,58.7758548597995,, +,47,47783.75,91.4982621134564,51.4982621134564,104.396531487276,, +,48,48198.4621158,86.7893616135196,46.7893616135196,140.746239952068,, +,49,48043.1218078999,32.2293079656533,0,126.671615956861,, +,50,48198.4621158,32.3335166522343,0,114.004454361175,, +,51,48044.80901916,31.8792529667944,0,102.604008925058,, +,52,48108.4029515399,13.236379471202,0,92.3436080325518,, +,53,48112,0.747696501943729,0,54.9969380550045,, +,54,48056.45718986,11.5444816553045,0,49.4972442495041,, +,55,47988.17926024,14.2078575102289,0,44.5475198245537,, +,56,47902.24487106,17.9074077209684,0,40.0927678420983,, +,57,47911.8471578599,2.0045588313744,0,18.1271573563076,, +,58,47670.81054939,50.3083522694781,10.3083522694781,10.3083522694781,, +,59,47580.67384441,18.9081544746583,0,9.27751704253025,, +,60,47449.76309439,27.5134291809487,0,8.34976533827722,, +,61,47382.88726893,14.0940272614154,0,7.5147888044495,, +,62,47222.35138239,33.8805623280924,0,6.76330992400455,, +,63,46948.76815888,57.9351123993432,17.9351123993432,24.0220913309473,, +,64,46675.18493538,58.2727160325403,18.2727160325403,39.8925982303928,, +,65,46463.74676537,45.2999104990637,5.29991049906368,41.2032489064172,, +,66,46217.7336139799,52.9473338928954,12.9473338928954,50.0302579086709,, +,67,46183.17440371,7.47747835463941,0,45.0272321178038,, +,68,45919.00562933,57.2002201647659,17.2002201647659,54.1304288814665,, +,69,45586.5085919099,72.4094594086167,32.4094594086167,81.1268454019365,, +,70,45483.8961602099,22.509385971754,0,73.0141608617428,, +,71,44672.6561639399,178.357630888202,138.357630888202,204.07037566377,, +,72,44661.70868763,2.4505989233603,0,183.663338097393,, +,73,44650.76121132,2.45119961409634,0,159.043417273233,, +,74,44668.7953606,4.03893434081759,0,136.767773423293,, +,75,44774.49388229,23.6627204375495,0,121.243031555481,, +,76,44853.05,17.5448365572861,0,104.604272214704,, +,77,45204.4549999999,78.345842701868,38.345842701868,132.489687695102,, +,78,45231.276,5.93326476341804,0,113.243372989164,, +,79,45477.5075,54.4383271433691,14.4383271433691,105.056883082536,, +,80,45568.3515,19.9755890315667,0,94.5511947742825,, +,81,45468.09,22.0024417604836,0,36.8537523828245,, +,82,45757.7,63.6952200983143,23.6952200983143,56.8635972428563,, +,83,45846.1785,19.3363084245934,0,51.1772375185707,, +,84,46060.93771094,46.8434268605389,6.84342686053888,52.9029406272525,, +,85,46081.215,4.40227447978669,0,47.6126465645273,, +,86,46167.82,18.7939923025038,0,42.8513819080745,, +,87,45860.8944999999,66.480396951838,26.4803969518379,51.6762720514978,, +,88,45844.31854774,3.61439793981844,0,46.508644846348,, +,89,46040.66042188,42.8279621902417,2.82796219024165,39.6514091659514,, +,90,45975.5952109399,14.1321193796728,0,35.6862682493563,, +,91,45942.48948481,7.20071724531368,0,32.1176414244206,, +,92,46040.66042188,21.3682232223089,0,20.6438649002722,, +,93,46090.3302109399,10.7882442616525,0,18.579478410245,, +,94,46132.86,9.2274863003694,0,14.3353751665493,, +,95,45874.42216374,56.0203369702217,16.0203369702217,28.9221746201161,, +,96,45830.84933011,9.49828500824923,0,26.0299571581045,, +,97,45888.1962499999,12.5127333942343,0,14.1938179398984,, +,98,46038.42444459,32.7378731061145,0,12.7744361459085,, +,99,46039.54243323,0.242838162576309,0,10.5109430861624,, +,100,46218.23018745,38.8118006340177,0,9.4598487775462,, +,101,46213.94,0.928245723083032,0,8.51386389979158,, +,102,46437.773,48.4340872039901,8.43408720399012,16.0965647138025,, +,103,46506.479,14.7952831415932,0,14.4869082424223,, +,104,46519.9745,2.90185373956087,0,13.0382174181801,, +,105,46533.47,2.90101190833836,0,6.1484495717088,, +,106,46622.632,19.1608319774983,0,5.53360461453792,, +,107,46619.216,0.732691367574256,0,4.98024415308413,, +,108,46615.8,0.732745055171957,0,4.48221973777571,, +,109,46707.1025,19.5861703542577,0,4.03399776399814,, +,110,46826.18,25.4945165994824,0,3.63059798759833,, +,111,46819.33062006,1.46272447165252,0,3.26753818883849,, +,112,46826.18,1.46293845924239,0,0,, +,113,46961.35210329,28.8667799273834,0,0,, +,114,47001.22734027,8.4910751488354,0,0,, +,115,46829.45375,36.5466180332763,0,0,, +,116,46796.05875,7.13119571675636,0,0,, +,117,46903.6069999999,22.9823307502164,0,0,, +,118,46789.378,24.3539905150381,0,0,, +,119,46752.593,7.86182710101335,0,0,, +,120,46766.34475,2.94138765736474,0,0,, +,121,46780.0965,2.94052273563716,0,0,, +,122,46946.0995,35.4858182047568,0,0,, +formulas (apply from bottom up),,,"""=ABS(C125/C124-1)*10000”","""=IF(D125-$H$9>0,D125-$H$9,0)”","""=SUMPRODUCT(E116:E125,$H$23:$H$32)”",, From b5be09c5f6ebb0b7c274eb37d56b279ca50e6445 Mon Sep 17 00:00:00 2001 From: artdgn Date: Mon, 17 Jan 2022 12:59:25 +1100 Subject: [PATCH 115/133] fix shadowing and adjust bnclose precision --- contracts/SystemSettings.sol | 10 +++++----- test/contracts/DynamicFee.js | 8 ++++---- 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/contracts/SystemSettings.sol b/contracts/SystemSettings.sol index 2dff7dc288..cfa37d9deb 100644 --- a/contracts/SystemSettings.sol +++ b/contracts/SystemSettings.sol @@ -233,9 +233,9 @@ contract SystemSettings is Owned, MixinSystemSettings, ISystemSettings { emit CrossDomainMessageGasLimitChanged(_gasLimitType, _crossDomainMessageGasLimit); } - function setIssuanceRatio(uint issuanceRatio) external onlyOwner { - flexibleStorage().setIssuanceRatio(SETTING_ISSUANCE_RATIO, issuanceRatio); - emit IssuanceRatioUpdated(issuanceRatio); + function setIssuanceRatio(uint ratio) external onlyOwner { + flexibleStorage().setIssuanceRatio(SETTING_ISSUANCE_RATIO, ratio); + emit IssuanceRatioUpdated(ratio); } function setTradingRewardsEnabled(bool _tradingRewardsEnabled) external onlyOwner { @@ -262,8 +262,8 @@ contract SystemSettings is Owned, MixinSystemSettings, ISystemSettings { } function setTargetThreshold(uint percent) external onlyOwner { - uint targetThreshold = flexibleStorage().setTargetThreshold(SETTING_TARGET_THRESHOLD, percent); - emit TargetThresholdUpdated(targetThreshold); + uint threshold = flexibleStorage().setTargetThreshold(SETTING_TARGET_THRESHOLD, percent); + emit TargetThresholdUpdated(threshold); } function setLiquidationDelay(uint time) external onlyOwner { diff --git a/test/contracts/DynamicFee.js b/test/contracts/DynamicFee.js index a122dbfb11..e10799661a 100644 --- a/test/contracts/DynamicFee.js +++ b/test/contracts/DynamicFee.js @@ -77,7 +77,7 @@ contract('DynamicFee', accounts => { threshold, weightDecay ); - assert.bnClose(dynamicFee, toUnit(20.6442753020364).div(toBN(10000)), 1e9); + assert.bnClose(dynamicFee, toUnit(20.6442753020364).div(toBN(10000)), 1e4); }); it('Fee is similar to dynamic-fee-calc.csv rounds 32-22, first one above threshold', async () => { @@ -99,7 +99,7 @@ contract('DynamicFee', accounts => { threshold, weightDecay ); - assert.bnClose(dynamicFee, toUnit(7.99801523256557).div(toBN(10000)), 1e9); + assert.bnClose(dynamicFee, toUnit(7.99801523256557).div(toBN(10000)), 1e4); }); it('Fee is similar to dynamic-fee-calc.csv rounds 72-63, 70% above threshold', async () => { @@ -122,7 +122,7 @@ contract('DynamicFee', accounts => { threshold, weightDecay ); - assert.bnClose(dynamicFee, toUnit(183.663338097394).div(toBN(10000)), 1e9); + assert.bnClose(dynamicFee, toUnit(183.663338097394).div(toBN(10000)), 1e4); }); it('Fee is similar to dynamic-fee-calc.csv rounds 67-58, 50% above threshold', async () => { @@ -144,6 +144,6 @@ contract('DynamicFee', accounts => { threshold, weightDecay ); - assert.bnClose(dynamicFee, toUnit(45.0272321178039).div(toBN(10000)), 1e9); + assert.bnClose(dynamicFee, toUnit(45.0272321178039).div(toBN(10000)), 1e4); }); }); From d2631e22ea96c1b79ae96c549646f37aafc6de96 Mon Sep 17 00:00:00 2001 From: artdgn Date: Mon, 17 Jan 2022 15:24:34 +1100 Subject: [PATCH 116/133] fix aggregators setup in integration tests --- .../behaviors/liquidations.behavior.js | 20 ++++- test/integration/utils/rates.js | 82 ++++++++----------- 2 files changed, 51 insertions(+), 51 deletions(-) diff --git a/test/integration/behaviors/liquidations.behavior.js b/test/integration/behaviors/liquidations.behavior.js index 55990c819b..b40b730c09 100644 --- a/test/integration/behaviors/liquidations.behavior.js +++ b/test/integration/behaviors/liquidations.behavior.js @@ -1,7 +1,7 @@ const ethers = require('ethers'); const { toBytes32 } = require('../../../index'); const { assert } = require('../../contracts/common'); -const { getRate, setRate } = require('../utils/rates'); +const { getRate, addAggregatorAndSetRate } = require('../utils/rates'); const { ensureBalance } = require('../utils/balances'); const { skipLiquidationDelay } = require('../utils/skip'); @@ -46,7 +46,11 @@ function itCanLiquidate({ ctx }) { before('exchange rate is set', async () => { exchangeRate = await getRate({ ctx, symbol: 'SNX' }); - await setRate({ ctx, symbol: 'SNX', rate: '1000000000000000000' }); + await addAggregatorAndSetRate({ + ctx, + currencyKey: toBytes32('SNX'), + rate: '1000000000000000000', + }); }); before('someUser stakes their SNX', async () => { @@ -59,7 +63,11 @@ function itCanLiquidate({ ctx }) { describe('getting marked', () => { before('exchange rate changes to allow liquidation', async () => { - await setRate({ ctx, symbol: 'SNX', rate: '200000000000000000' }); + await addAggregatorAndSetRate({ + ctx, + currencyKey: toBytes32('SNX'), + rate: '200000000000000000', + }); }); before('liquidation is marked', async () => { @@ -67,7 +75,11 @@ function itCanLiquidate({ ctx }) { }); after('restore exchange rate', async () => { - await setRate({ ctx, symbol: 'SNX', rate: exchangeRate.toString() }); + await addAggregatorAndSetRate({ + ctx, + currencyKey: toBytes32('SNX'), + rate: exchangeRate.toString(), + }); }); it('still not open for liquidation', async () => { diff --git a/test/integration/utils/rates.js b/test/integration/utils/rates.js index 2fda4cb1c5..1fcc986af7 100644 --- a/test/integration/utils/rates.js +++ b/test/integration/utils/rates.js @@ -1,20 +1,19 @@ const ethers = require('ethers'); const { setSystemSetting } = require('./settings'); -const { - toBytes32, - defaults: { EXCHANGE_DYNAMIC_FEE_ROUNDS }, -} = require('../../..'); +const { toBytes32 } = require('../../..'); const { createMockAggregatorFactory } = require('../../utils')(); async function increaseStalePeriodAndCheckRatesAndCache({ ctx }) { await setSystemSetting({ ctx, settingName: 'rateStalePeriod', newValue: '1000000000' }); - // try to add the missing rates - await _setMissingRates({ ctx }); - // check again if (await _areRatesInvalid({ ctx })) { - await _printRatesInfo({ ctx }); - throw new Error('Rates are still invalid after updating.'); + // try to add the missing rates + await _setMissingRates({ ctx }); + // check again + if (await _areRatesInvalid({ ctx })) { + await _printRatesInfo({ ctx }); + throw new Error('Rates are still invalid after updating.'); + } } if (await _isCacheInvalid({ ctx })) { @@ -26,6 +25,28 @@ async function increaseStalePeriodAndCheckRatesAndCache({ ctx }) { } } +/// this creates and adds a new aggregator (even if a previous one exists) and sets the latest rate in it +async function addAggregatorAndSetRate({ ctx, currencyKey, rate }) { + const owner = ctx.users.owner; + const exchangeRates = ctx.contracts.ExchangeRates.connect(owner); + + // factory for price aggregators contracts + const MockAggregatorFactory = await createMockAggregatorFactory(owner); + + // deploy an aggregator + const aggregator = (await MockAggregatorFactory.deploy()).connect(owner); + + // set decimals + await (await aggregator.setDecimals(18)).wait(); + // push the new price + // for (let i = 0; i < EXCHANGE_DYNAMIC_FEE_ROUNDS; i++) { + const { timestamp } = await ctx.provider.getBlock(); + await (await aggregator.setLatestAnswer(rate, timestamp)).wait(); + // } + // set the aggregator in ExchangeRates + await (await exchangeRates.addAggregator(currencyKey, aggregator.address)).wait(); +} + async function _isCacheInvalid({ ctx }) { const { DebtCache } = ctx.contracts; @@ -87,26 +108,12 @@ async function _setMissingRates({ ctx }) { currencyKeys = await _getAvailableCurrencyKeys({ ctx }); } - const owner = ctx.users.owner; - const ExchangeRates = ctx.contracts.ExchangeRates.connect(owner); - - // factory for price aggregators contracts - const MockAggregatorFactory = await createMockAggregatorFactory(owner); - - // got over all rates and add aggregators + // got over all rates and add aggregators if rate is missing for (const currencyKey of currencyKeys) { - // deploy an aggregator - let aggregator = await MockAggregatorFactory.deploy(); - aggregator = aggregator.connect(owner); - // set decimals - await (await aggregator.setDecimals(18)).wait(); - for (let i = 0; i < EXCHANGE_DYNAMIC_FEE_ROUNDS; i++) { - const { timestamp } = await ctx.provider.getBlock(); - // push the new price - await (await aggregator.setLatestAnswer(ethers.utils.parseEther('1'), timestamp)).wait(); + const rate = await ctx.contracts.ExchangeRates.rateForCurrency(currencyKey); + if (rate.toString() === '0') { + addAggregatorAndSetRate({ ctx, currencyKey, rate: ethers.utils.parseEther('1') }); } - // set the aggregator in ExchangeRates - await (await ExchangeRates.addAggregator(currencyKey, aggregator.address)).wait(); } } @@ -129,28 +136,9 @@ async function getRate({ ctx, symbol }) { return ExchangeRates.rateForCurrency(toBytes32(symbol)); } -async function setRate({ ctx, symbol, rate }) { - const ExchangeRates = ctx.contracts.ExchangeRates.connect(ctx.users.owner); - - // factory for price aggregators contracts - const MockAggregatorFactory = await createMockAggregatorFactory(ctx.users.owner); - - // deploy an aggregator - let aggregator = await MockAggregatorFactory.deploy(); - aggregator = aggregator.connect(ctx.users.owner); - - const { timestamp } = await ctx.provider.getBlock(); - // set decimals - await (await aggregator.setDecimals(18)).wait(); - // push the new price - await (await aggregator.setLatestAnswer(ethers.utils.parseEther(rate), timestamp)).wait(); - // set the aggregator in ExchangeRates - await (await ExchangeRates.addAggregator(toBytes32(symbol), aggregator.address)).wait(); -} - module.exports = { increaseStalePeriodAndCheckRatesAndCache, + addAggregatorAndSetRate, getRate, - setRate, updateCache, }; From f422bfe7b0c3f987c2c04114fc0483cb3b65b7b5 Mon Sep 17 00:00:00 2001 From: artdgn Date: Mon, 17 Jan 2022 16:05:53 +1100 Subject: [PATCH 117/133] remove caching again (how did it slip back?) --- contracts/ExchangeRates.sol | 9 --------- 1 file changed, 9 deletions(-) diff --git a/contracts/ExchangeRates.sol b/contracts/ExchangeRates.sol index 6f041a03b3..60f76b8bad 100644 --- a/contracts/ExchangeRates.sol +++ b/contracts/ExchangeRates.sol @@ -32,8 +32,6 @@ contract ExchangeRates is Owned, MixinSystemSettings, IExchangeRates { // List of aggregator keys for convenient iteration bytes32[] public aggregatorKeys; - mapping(bytes32 => mapping(uint => RateAndUpdatedTime)) public cacheRates; - // ========== CONSTRUCTOR ========== constructor(address _owner, address _resolver) public Owned(_owner) MixinSystemSettings(_resolver) {} @@ -450,13 +448,6 @@ contract ExchangeRates is Owned, MixinSystemSettings, IExchangeRates { // which are used in atomic swaps and fee reclamation return (SafeDecimalMath.unit(), 0); } else { - // read cache - RateAndUpdatedTime memory cachedEntry = cacheRates[currencyKey][roundId]; - if (cachedEntry.rate != 0) { - return (cachedEntry.rate, cachedEntry.time); - } - - // if no valid cache entry - read from aggregator AggregatorV2V3Interface aggregator = aggregators[currencyKey]; if (aggregator != AggregatorV2V3Interface(0)) { // this view from the aggregator is the most gas efficient but it can throw when there's no data, From cfd2e5aad5c30e561a29f7f1cf50c159849889e8 Mon Sep 17 00:00:00 2001 From: artdgn Date: Mon, 17 Jan 2022 16:42:03 +1100 Subject: [PATCH 118/133] fix integration tests setup --- test/integration/utils/rates.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/integration/utils/rates.js b/test/integration/utils/rates.js index 1fcc986af7..652b7b6d26 100644 --- a/test/integration/utils/rates.js +++ b/test/integration/utils/rates.js @@ -112,7 +112,7 @@ async function _setMissingRates({ ctx }) { for (const currencyKey of currencyKeys) { const rate = await ctx.contracts.ExchangeRates.rateForCurrency(currencyKey); if (rate.toString() === '0') { - addAggregatorAndSetRate({ ctx, currencyKey, rate: ethers.utils.parseEther('1') }); + await addAggregatorAndSetRate({ ctx, currencyKey, rate: ethers.utils.parseEther('1') }); } } } From f2d8580b0c7718cab2f47922c73b49679bbcd900 Mon Sep 17 00:00:00 2001 From: artdgn Date: Mon, 17 Jan 2022 17:54:26 +1100 Subject: [PATCH 119/133] fix flxeible storage mock tests --- contracts/MixinSystemSettings.sol | 3 +-- test/contracts/helpers.js | 2 +- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/contracts/MixinSystemSettings.sol b/contracts/MixinSystemSettings.sol index c4f0ca4d8b..0facfd1794 100644 --- a/contracts/MixinSystemSettings.sol +++ b/contracts/MixinSystemSettings.sol @@ -149,12 +149,11 @@ contract MixinSystemSettings is MixinResolver { /// @return threshold, weight decay, rounds, and max fee function getExchangeDynamicFeeConfig() internal view returns (DynamicFeeConfig memory) { bytes32[] memory keys = new bytes32[](4); - uint[] memory values = new uint[](4); keys[0] = SETTING_EXCHANGE_DYNAMIC_FEE_THRESHOLD; keys[1] = SETTING_EXCHANGE_DYNAMIC_FEE_WEIGHT_DECAY; keys[2] = SETTING_EXCHANGE_DYNAMIC_FEE_ROUNDS; keys[3] = SETTING_EXCHANGE_MAX_DYNAMIC_FEE; - values = flexibleStorage().getUIntValues(SETTING_CONTRACT_NAME, keys); + uint[] memory values = flexibleStorage().getUIntValues(SETTING_CONTRACT_NAME, keys); return DynamicFeeConfig({threshold: values[0], weightDecay: values[1], rounds: values[2], maxFee: values[3]}); } diff --git a/test/contracts/helpers.js b/test/contracts/helpers.js index dd391cee55..f77a2865ba 100644 --- a/test/contracts/helpers.js +++ b/test/contracts/helpers.js @@ -333,7 +333,7 @@ module.exports = { const flexibleStorageTypes = [ ['uint', 'getUIntValue', '0'], - ['uints', 'getUIntValues', ['0', '0', '0']], + ['uints', 'getUIntValues', ['0', '0', '0', '0']], ['int', 'getIntValue', '0'], ['address', 'getAddressValue', ZERO_ADDRESS], ['bool', 'getBoolValue', false], From 13b3832a528995d1f8529c9aa64d4491b48fd871 Mon Sep 17 00:00:00 2001 From: artdgn Date: Mon, 17 Jan 2022 18:31:48 +1100 Subject: [PATCH 120/133] remove unused method --- contracts/ExchangeRates.sol | 21 --------------------- contracts/interfaces/IExchangeRates.sol | 8 -------- 2 files changed, 29 deletions(-) diff --git a/contracts/ExchangeRates.sol b/contracts/ExchangeRates.sol index 60f76b8bad..899eb45cdd 100644 --- a/contracts/ExchangeRates.sol +++ b/contracts/ExchangeRates.sol @@ -116,27 +116,6 @@ contract ExchangeRates is Owned, MixinSystemSettings, IExchangeRates { return _getCurrentRoundId(currencyKey); } - function effectiveValueAtRound( - bytes32 sourceCurrencyKey, - uint sourceAmount, - bytes32 destinationCurrencyKey, - uint roundIdForSrc, - uint roundIdForDest - ) external view returns (uint value) { - // If there's no change in the currency, then just return the amount they gave us - if (sourceCurrencyKey == destinationCurrencyKey) return sourceAmount; - - (uint srcRate, ) = _getRateAndTimestampAtRound(sourceCurrencyKey, roundIdForSrc); - (uint destRate, ) = _getRateAndTimestampAtRound(destinationCurrencyKey, roundIdForDest); - if (destRate == 0) { - // prevent divide-by 0 error (this can happen when roundIDs jump epochs due - // to aggregator upgrades) - return 0; - } - // Calculate the effective value by going from source -> USD -> destination - value = sourceAmount.multiplyDecimalRound(srcRate).divideDecimalRound(destRate); - } - function effectiveValueAndRatesAtRound( bytes32 sourceCurrencyKey, uint sourceAmount, diff --git a/contracts/interfaces/IExchangeRates.sol b/contracts/interfaces/IExchangeRates.sol index 4364384d52..43e4f7fbfa 100644 --- a/contracts/interfaces/IExchangeRates.sol +++ b/contracts/interfaces/IExchangeRates.sol @@ -25,14 +25,6 @@ interface IExchangeRates { bytes32 destinationCurrencyKey ) external view returns (uint value); - function effectiveValueAtRound( - bytes32 sourceCurrencyKey, - uint sourceAmount, - bytes32 destinationCurrencyKey, - uint roundIdForSrc, - uint roundIdForDest - ) external view returns (uint value); - function effectiveValueAndRates( bytes32 sourceCurrencyKey, uint sourceAmount, From 36481a1753844280866535aaf7e43936f37b67b6 Mon Sep 17 00:00:00 2001 From: artdgn Date: Mon, 17 Jan 2022 18:35:43 +1100 Subject: [PATCH 121/133] rename susd constant --- contracts/ExchangeRates.sol | 19 ++++++++++--------- 1 file changed, 10 insertions(+), 9 deletions(-) diff --git a/contracts/ExchangeRates.sol b/contracts/ExchangeRates.sol index 899eb45cdd..9f3b8c43c2 100644 --- a/contracts/ExchangeRates.sol +++ b/contracts/ExchangeRates.sol @@ -22,7 +22,8 @@ contract ExchangeRates is Owned, MixinSystemSettings, IExchangeRates { using SafeDecimalMath for uint; bytes32 public constant CONTRACT_NAME = "ExchangeRates"; - bytes32 internal constant SUSD = "sUSD"; + //slither-disable-next-line naming-convention + bytes32 internal constant sUSD = "sUSD"; // Decentralized oracle networks that feed into pricing aggregators mapping(bytes32 => AggregatorV2V3Interface) public aggregators; @@ -250,7 +251,7 @@ contract ExchangeRates is Owned, MixinSystemSettings, IExchangeRates { function rateAndInvalid(bytes32 currencyKey) external view returns (uint rate, bool isInvalid) { RateAndUpdatedTime memory rateAndTime = _getRateAndUpdatedTime(currencyKey); - if (currencyKey == SUSD) { + if (currencyKey == sUSD) { return (rateAndTime.rate, false); } return ( @@ -276,7 +277,7 @@ contract ExchangeRates is Owned, MixinSystemSettings, IExchangeRates { // do one lookup of the rate & time to minimize gas RateAndUpdatedTime memory rateEntry = _getRateAndUpdatedTime(currencyKeys[i]); rates[i] = rateEntry.rate; - if (!anyRateInvalid && currencyKeys[i] != SUSD) { + if (!anyRateInvalid && currencyKeys[i] != sUSD) { anyRateInvalid = flagList[i] || _rateIsStaleWithTime(_rateStalePeriod, rateEntry.time); } } @@ -385,7 +386,7 @@ contract ExchangeRates is Owned, MixinSystemSettings, IExchangeRates { function _getRateAndUpdatedTime(bytes32 currencyKey) internal view returns (RateAndUpdatedTime memory) { // sUSD rate is 1.0 - if (currencyKey == SUSD) { + if (currencyKey == sUSD) { return RateAndUpdatedTime({rate: uint216(SafeDecimalMath.unit()), time: 0}); } else { AggregatorV2V3Interface aggregator = aggregators[currencyKey]; @@ -411,7 +412,7 @@ contract ExchangeRates is Owned, MixinSystemSettings, IExchangeRates { } function _getCurrentRoundId(bytes32 currencyKey) internal view returns (uint) { - if (currencyKey == SUSD) { + if (currencyKey == sUSD) { return 1; // consistent with ChainLink Oracle that start at round 1 } AggregatorV2V3Interface aggregator = aggregators[currencyKey]; @@ -422,7 +423,7 @@ contract ExchangeRates is Owned, MixinSystemSettings, IExchangeRates { function _getRateAndTimestampAtRound(bytes32 currencyKey, uint roundId) internal view returns (uint rate, uint time) { // short circuit sUSD - if (currencyKey == SUSD) { + if (currencyKey == sUSD) { // sUSD has no rounds, and 0 time is preferrable for "volatility" heuristics // which are used in atomic swaps and fee reclamation return (SafeDecimalMath.unit(), 0); @@ -482,7 +483,7 @@ contract ExchangeRates is Owned, MixinSystemSettings, IExchangeRates { function _rateIsStale(bytes32 currencyKey, uint _rateStalePeriod) internal view returns (bool) { // sUSD is a special case and is never stale (check before an SLOAD of getRateAndUpdatedTime) - if (currencyKey == SUSD) return false; + if (currencyKey == sUSD) return false; return _rateIsStaleWithTime(_rateStalePeriod, _getUpdatedTime(currencyKey)); } @@ -493,7 +494,7 @@ contract ExchangeRates is Owned, MixinSystemSettings, IExchangeRates { uint _rateStalePeriod ) internal view returns (bool) { // sUSD is a special case and is never stale (check before an SLOAD of getRateAndUpdatedTime) - if (currencyKey == SUSD) return false; + if (currencyKey == sUSD) return false; (, uint time) = _getRateAndTimestampAtRound(currencyKey, roundId); return _rateIsStaleWithTime(_rateStalePeriod, time); @@ -505,7 +506,7 @@ contract ExchangeRates is Owned, MixinSystemSettings, IExchangeRates { function _rateIsFlagged(bytes32 currencyKey, FlagsInterface flags) internal view returns (bool) { // sUSD is a special case and is never invalid - if (currencyKey == SUSD) return false; + if (currencyKey == sUSD) return false; address aggregator = address(aggregators[currencyKey]); // when no aggregator or when the flags haven't been setup if (aggregator == address(0) || flags == FlagsInterface(0)) { From db48525235a48e73554f07ce392444829526f1b1 Mon Sep 17 00:00:00 2001 From: artdgn Date: Mon, 17 Jan 2022 18:53:48 +1100 Subject: [PATCH 122/133] remove return variable declarations in new methods --- contracts/Exchanger.sol | 65 +++++++++++++++++------------------------ 1 file changed, 26 insertions(+), 39 deletions(-) diff --git a/contracts/Exchanger.sol b/contracts/Exchanger.sol index 95eebb3f74..0df302c304 100644 --- a/contracts/Exchanger.sol +++ b/contracts/Exchanger.sol @@ -777,12 +777,8 @@ contract Exchanger is Owned, MixinSystemSettings, IExchanger { /// @param sourceCurrencyKey The source currency key /// @param destinationCurrencyKey The destination currency key /// @return The exchange fee rate - function feeRateForExchange(bytes32 sourceCurrencyKey, bytes32 destinationCurrencyKey) - external - view - returns (uint exchangeFeeRate) - { - exchangeFeeRate = _feeRateForExchange(sourceCurrencyKey, destinationCurrencyKey); + function feeRateForExchange(bytes32 sourceCurrencyKey, bytes32 destinationCurrencyKey) external view returns (uint) { + return _feeRateForExchange(sourceCurrencyKey, destinationCurrencyKey); } /// @notice Calculate the exchange fee for a given source and destination currency key @@ -790,16 +786,12 @@ contract Exchanger is Owned, MixinSystemSettings, IExchanger { /// @param destinationCurrencyKey The destination currency key /// @return The exchange fee rate /// @return The exchange dynamic fee rate - function _feeRateForExchange(bytes32 sourceCurrencyKey, bytes32 destinationCurrencyKey) - internal - view - returns (uint feeRate) - { + function _feeRateForExchange(bytes32 sourceCurrencyKey, bytes32 destinationCurrencyKey) internal view returns (uint) { // Get the exchange fee rate as per destination currencyKey uint baseRate = getExchangeFeeRate(destinationCurrencyKey); - feeRate = baseRate.add(_dynamicFeeForExchange(sourceCurrencyKey, destinationCurrencyKey)); + uint feeRate = baseRate.add(_dynamicFeeForExchange(sourceCurrencyKey, destinationCurrencyKey)); // cap fee rate to 100% to prevent negative amounts - feeRate = feeRate > SafeDecimalMath.unit() ? SafeDecimalMath.unit() : feeRate; + return feeRate > SafeDecimalMath.unit() ? SafeDecimalMath.unit() : feeRate; } /// @notice Calculate the exchange fee for a given source and destination currency key @@ -814,26 +806,22 @@ contract Exchanger is Owned, MixinSystemSettings, IExchanger { bytes32 destinationCurrencyKey, uint roundIdForSrc, uint roundIdForDest - ) internal view returns (uint feeRate) { + ) internal view returns (uint) { // Get the exchange fee rate as per destination currencyKey uint baseRate = getExchangeFeeRate(destinationCurrencyKey); uint dynamicFee = _dynamicFeeForExchangeAtRounds(sourceCurrencyKey, destinationCurrencyKey, roundIdForSrc, roundIdForDest); - feeRate = baseRate.add(dynamicFee); + uint feeRate = baseRate.add(dynamicFee); // cap fee rate to 100% to prevent negative amounts - feeRate = feeRate > SafeDecimalMath.unit() ? SafeDecimalMath.unit() : feeRate; + return feeRate > SafeDecimalMath.unit() ? SafeDecimalMath.unit() : feeRate; } - function _dynamicFeeForExchange(bytes32 sourceCurrencyKey, bytes32 destinationCurrencyKey) - internal - view - returns (uint dynamicFee) - { + function _dynamicFeeForExchange(bytes32 sourceCurrencyKey, bytes32 destinationCurrencyKey) internal view returns (uint) { DynamicFeeConfig memory config = getExchangeDynamicFeeConfig(); - dynamicFee = _dynamicFeeForCurrency(destinationCurrencyKey, config); + uint dynamicFee = _dynamicFeeForCurrency(destinationCurrencyKey, config); dynamicFee = dynamicFee.add(_dynamicFeeForCurrency(sourceCurrencyKey, config)); // cap to maxFee - dynamicFee = dynamicFee > config.maxFee ? config.maxFee : dynamicFee; + return dynamicFee > config.maxFee ? config.maxFee : dynamicFee; } function _dynamicFeeForExchangeAtRounds( @@ -841,29 +829,25 @@ contract Exchanger is Owned, MixinSystemSettings, IExchanger { bytes32 destinationCurrencyKey, uint roundIdForSrc, uint roundIdForDest - ) internal view returns (uint dynamicFee) { + ) internal view returns (uint) { DynamicFeeConfig memory config = getExchangeDynamicFeeConfig(); - dynamicFee = _dynamicFeeForCurrencytRound(destinationCurrencyKey, roundIdForDest, config); + uint dynamicFee = _dynamicFeeForCurrencytRound(destinationCurrencyKey, roundIdForDest, config); dynamicFee = dynamicFee.add(_dynamicFeeForCurrencytRound(sourceCurrencyKey, roundIdForSrc, config)); // cap to maxFee - dynamicFee = dynamicFee > config.maxFee ? config.maxFee : dynamicFee; + return dynamicFee > config.maxFee ? config.maxFee : dynamicFee; } /// @notice Get dynamic dynamicFee for a given currency key (SIP-184) /// @param currencyKey The given currency key /// @param config dynamic fee calculation configuration params /// @return The dyanmic dynamicFee - function _dynamicFeeForCurrency(bytes32 currencyKey, DynamicFeeConfig memory config) - internal - view - returns (uint dynamicFee) - { + function _dynamicFeeForCurrency(bytes32 currencyKey, DynamicFeeConfig memory config) internal view returns (uint) { // No dynamic dynamicFee for sUSD if (currencyKey == sUSD) return 0; uint[] memory prices; uint roundId = exchangeRates().getCurrentRoundId(currencyKey); (prices, ) = exchangeRates().ratesAndUpdatedTimeForCurrencyLastNRounds(currencyKey, config.rounds, roundId); - dynamicFee = _dynamicFeeCalculation(prices, config.threshold, config.weightDecay); + return _dynamicFeeCalculation(prices, config.threshold, config.weightDecay); } /// @notice Get dynamicFee for a given currency key (SIP-184) @@ -875,12 +859,12 @@ contract Exchanger is Owned, MixinSystemSettings, IExchanger { bytes32 currencyKey, uint roundId, DynamicFeeConfig memory config - ) internal view returns (uint dynamicFee) { + ) internal view returns (uint) { // No dynamic dynamicFee for sUSD if (currencyKey == sUSD) return 0; uint[] memory prices; (prices, ) = exchangeRates().ratesAndUpdatedTimeForCurrencyLastNRounds(currencyKey, config.rounds, roundId); - dynamicFee = _dynamicFeeCalculation(prices, config.threshold, config.weightDecay); + return _dynamicFeeCalculation(prices, config.threshold, config.weightDecay); } /// @notice Calculate dynamic fee according to SIP-184 @@ -892,12 +876,14 @@ contract Exchanger is Owned, MixinSystemSettings, IExchanger { uint[] memory prices, uint threshold, uint weightDecay - ) internal pure returns (uint dynamicFee) { + ) internal pure returns (uint) { uint size = prices.length; // short circuit if there is a single price if (size <= 1) { - return dynamicFee; + return 0; } + + uint dynamicFee = 0; // start with 0 // go backwards in price array for (uint i = size - 1; i > 0; i--) { // apply decay from previous round (will be 0 for first round) @@ -907,6 +893,7 @@ contract Exchanger is Owned, MixinSystemSettings, IExchanger { // add to total fee dynamicFee = dynamicFee.add(deviation); } + return dynamicFee; } /// absolute price deviation ratio used by dynamic fee calculation @@ -916,16 +903,16 @@ contract Exchanger is Owned, MixinSystemSettings, IExchanger { uint price, uint previousPrice, uint threshold - ) public pure returns (uint deviationRatio) { + ) public pure returns (uint) { if (previousPrice == 0) { return 0; // don't divide by zero } // abs difference between prices uint absDelta = price > previousPrice ? price - previousPrice : previousPrice - price; // relative to previous price - deviationRatio = absDelta.divideDecimal(previousPrice); + uint deviationRatio = absDelta.divideDecimal(previousPrice); // only the positive difference from threshold - deviationRatio = deviationRatio > threshold ? deviationRatio - threshold : 0; + return deviationRatio > threshold ? deviationRatio - threshold : 0; } function getAmountsForExchange( From ac868c2d4d51661b2a8fb570a938a6ac30978774 Mon Sep 17 00:00:00 2001 From: artdgn Date: Tue, 18 Jan 2022 14:28:26 +1100 Subject: [PATCH 123/133] update optimistm ops tool commit hash --- hardhat/tasks/task-ops.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/hardhat/tasks/task-ops.js b/hardhat/tasks/task-ops.js index ab8e47c140..c3714bab49 100644 --- a/hardhat/tasks/task-ops.js +++ b/hardhat/tasks/task-ops.js @@ -25,7 +25,7 @@ task('ops', 'Run Optimism chain') .addOptionalParam( 'optimismCommit', 'Commit to checkout', - 'f1631a5f7ddb6eb4a342bfbd7d46233a43412f9b' + '0a20266f85ce0b1f54d8ec327ae093401961411f' ) .setAction(async (taskArguments, hre, runSuper) => { taskArguments.maxMemory = true; From db1462ec071ceddc7d6b43be28c16d83678afc2f Mon Sep 17 00:00:00 2001 From: artdgn Date: Tue, 18 Jan 2022 14:30:17 +1100 Subject: [PATCH 124/133] address review comments --- contracts/ExchangeRates.sol | 4 ++-- contracts/Exchanger.sol | 13 ++++++------- contracts/interfaces/IExchanger.sol | 2 +- contracts/interfaces/IFlexibleStorage.sol | 2 +- hardhat/tasks/task-node.js | 1 - .../commands/deploy/configure-system-settings.js | 8 ++++---- test/contracts/ExchangeRates.js | 2 +- 7 files changed, 15 insertions(+), 17 deletions(-) diff --git a/contracts/ExchangeRates.sol b/contracts/ExchangeRates.sol index 9f3b8c43c2..9a0bdf6bd4 100644 --- a/contracts/ExchangeRates.sol +++ b/contracts/ExchangeRates.sol @@ -229,7 +229,7 @@ contract ExchangeRates is Owned, MixinSystemSettings, IExchangeRates { // regardless of current rate (rates[i], times[i]) = _getRateAndTimestampAtRound(currencyKey, roundId); - if (roundId == 1) { + if (roundId == 0) { // if we hit the last round, then return what we have return (rates, times); } else { @@ -413,7 +413,7 @@ contract ExchangeRates is Owned, MixinSystemSettings, IExchangeRates { function _getCurrentRoundId(bytes32 currencyKey) internal view returns (uint) { if (currencyKey == sUSD) { - return 1; // consistent with ChainLink Oracle that start at round 1 + return 0; } AggregatorV2V3Interface aggregator = aggregators[currencyKey]; if (aggregator != AggregatorV2V3Interface(0)) { diff --git a/contracts/Exchanger.sol b/contracts/Exchanger.sol index 0df302c304..53c444d5ce 100644 --- a/contracts/Exchanger.sol +++ b/contracts/Exchanger.sol @@ -831,8 +831,8 @@ contract Exchanger is Owned, MixinSystemSettings, IExchanger { uint roundIdForDest ) internal view returns (uint) { DynamicFeeConfig memory config = getExchangeDynamicFeeConfig(); - uint dynamicFee = _dynamicFeeForCurrencytRound(destinationCurrencyKey, roundIdForDest, config); - dynamicFee = dynamicFee.add(_dynamicFeeForCurrencytRound(sourceCurrencyKey, roundIdForSrc, config)); + uint dynamicFee = _dynamicFeeForCurrencyRound(destinationCurrencyKey, roundIdForDest, config); + dynamicFee = dynamicFee.add(_dynamicFeeForCurrencyRound(sourceCurrencyKey, roundIdForSrc, config)); // cap to maxFee return dynamicFee > config.maxFee ? config.maxFee : dynamicFee; } @@ -855,7 +855,7 @@ contract Exchanger is Owned, MixinSystemSettings, IExchanger { /// @param roundId The round id /// @param config dynamic fee calculation configuration params /// @return The dyanmic dynamicFee - function _dynamicFeeForCurrencytRound( + function _dynamicFeeForCurrencyRound( bytes32 currencyKey, uint roundId, DynamicFeeConfig memory config @@ -877,15 +877,14 @@ contract Exchanger is Owned, MixinSystemSettings, IExchanger { uint threshold, uint weightDecay ) internal pure returns (uint) { - uint size = prices.length; - // short circuit if there is a single price - if (size <= 1) { + // don't underflow + if (prices.length == 0) { return 0; } uint dynamicFee = 0; // start with 0 // go backwards in price array - for (uint i = size - 1; i > 0; i--) { + for (uint i = prices.length - 1; i > 0; i--) { // apply decay from previous round (will be 0 for first round) dynamicFee = dynamicFee.multiplyDecimal(weightDecay); // calculate price deviation diff --git a/contracts/interfaces/IExchanger.sol b/contracts/interfaces/IExchanger.sol index ce80bdfcc7..2cd5323847 100644 --- a/contracts/interfaces/IExchanger.sol +++ b/contracts/interfaces/IExchanger.sol @@ -1,4 +1,4 @@ -pragma solidity ^0.5.16; +pragma solidity >=0.4.24; import "./IVirtualSynth.sol"; diff --git a/contracts/interfaces/IFlexibleStorage.sol b/contracts/interfaces/IFlexibleStorage.sol index e72cfbd801..4419f638be 100644 --- a/contracts/interfaces/IFlexibleStorage.sol +++ b/contracts/interfaces/IFlexibleStorage.sol @@ -1,4 +1,4 @@ -pragma solidity ^0.5.16; +pragma solidity >=0.4.24; // https://docs.synthetix.io/contracts/source/interfaces/iflexiblestorage interface IFlexibleStorage { diff --git a/hardhat/tasks/task-node.js b/hardhat/tasks/task-node.js index c0e0286abf..42dc834ca1 100644 --- a/hardhat/tasks/task-node.js +++ b/hardhat/tasks/task-node.js @@ -19,7 +19,6 @@ task('node', 'Run a node') const network = taskArguments.targetNetwork; if (network !== 'local') { if (network === 'mainnet') { - hre.config.networks.hardhat.allowUnlimitedContractSize = false; taskArguments.fork = process.env.PROVIDER_URL_MAINNET; } taskArguments.fork = diff --git a/publish/src/commands/deploy/configure-system-settings.js b/publish/src/commands/deploy/configure-system-settings.js index 310cc7231b..819f54602d 100644 --- a/publish/src/commands/deploy/configure-system-settings.js +++ b/publish/src/commands/deploy/configure-system-settings.js @@ -375,7 +375,7 @@ module.exports = async ({ target: SystemSettings, read: 'exchangeDynamicFeeThreshold', readTarget: previousSystemSettings, - expected: input => input !== '0', // only change if zero + expected: allowZeroOrUpdateIfNonZero(exchangeDynamicFeeThreshold), write: 'setExchangeDynamicFeeThreshold', writeArg: exchangeDynamicFeeThreshold, comment: 'Set exchange dynamic fee threshold (SIP-184)', @@ -388,7 +388,7 @@ module.exports = async ({ target: SystemSettings, read: 'exchangeDynamicFeeWeightDecay', readTarget: previousSystemSettings, - expected: input => input !== '0', // only change if zero + expected: allowZeroOrUpdateIfNonZero(exchangeDynamicFeeWeightDecay), write: 'setExchangeDynamicFeeWeightDecay', writeArg: exchangeDynamicFeeWeightDecay, comment: 'Set exchange dynamic fee weight decay (SIP-184)', @@ -399,7 +399,7 @@ module.exports = async ({ target: SystemSettings, read: 'exchangeDynamicFeeRounds', readTarget: previousSystemSettings, - expected: input => input !== '0', // only change if zero + expected: allowZeroOrUpdateIfNonZero(exchangeDynamicFeeRounds), write: 'setExchangeDynamicFeeRounds', writeArg: exchangeDynamicFeeRounds, comment: 'Set exchange dynamic fee rounds (SIP-184)', @@ -410,7 +410,7 @@ module.exports = async ({ target: SystemSettings, read: 'exchangeMaxDynamicFee', readTarget: previousSystemSettings, - expected: input => input !== '0', // only change if zero + expected: allowZeroOrUpdateIfNonZero(exchangeMaxDynamicFee), write: 'setExchangeMaxDynamicFee', writeArg: exchangeMaxDynamicFee, comment: 'Set exchange max dynamic fee (SIP-184)', diff --git a/test/contracts/ExchangeRates.js b/test/contracts/ExchangeRates.js index 68b49c8d7c..b1a1c18c3d 100644 --- a/test/contracts/ExchangeRates.js +++ b/test/contracts/ExchangeRates.js @@ -945,7 +945,7 @@ contract('Exchange Rates', async accounts => { }); it('getCurrentRoundId() is 1 for sUSD', async () => { - assert.equal(await instance.getCurrentRoundId(sUSD), 1); + assert.equal(await instance.getCurrentRoundId(sUSD), 0); }); it('ratesAndUpdatedTimeForCurrencyLastNRounds() shows first entry for sUSD', async () => { From 29346419e5eb465f0ea6ab2fefaaad7eab29995c Mon Sep 17 00:00:00 2001 From: artdgn Date: Tue, 18 Jan 2022 17:25:25 +1100 Subject: [PATCH 125/133] update optimism core utils package --- package-lock.json | 79 ++++++++++++++++++++++++++++++++++++++++++----- package.json | 2 +- 2 files changed, 73 insertions(+), 8 deletions(-) diff --git a/package-lock.json b/package-lock.json index 637621672f..1b26055c5b 100644 --- a/package-lock.json +++ b/package-lock.json @@ -25,7 +25,7 @@ "@chainlink/contracts-0.0.10": "npm:@chainlink/contracts@0.0.10", "@codechecks/client": "0.1.11", "@eth-optimism/contracts": "0.3.4", - "@eth-optimism/core-utils": "~0.4.5", + "@eth-optimism/core-utils": "0.7.3", "@eth-optimism/smock": "1.1.10", "@gnosis.pm/safe-core-sdk": "~0.3.1", "@gnosis.pm/safe-service-client": "~0.1.1", @@ -732,7 +732,7 @@ "ethers": "^5.0.0" } }, - "node_modules/@eth-optimism/core-utils": { + "node_modules/@eth-optimism/contracts/node_modules/@eth-optimism/core-utils": { "version": "0.4.7", "resolved": "https://registry.npmjs.org/@eth-optimism/core-utils/-/core-utils-0.4.7.tgz", "integrity": "sha512-H+NdGAyx6w4Wmjy3PaTF7xnBEeKh8BZK1jfU6yy52sd2E0oH7ABd0H5FtRY2SLPTH8AZ8gAgOhrTNoqE0Mlktw==", @@ -743,6 +743,38 @@ "lodash": "^4.17.21" } }, + "node_modules/@eth-optimism/core-utils": { + "version": "0.7.3", + "resolved": "https://registry.npmjs.org/@eth-optimism/core-utils/-/core-utils-0.7.3.tgz", + "integrity": "sha512-e531gfcMN6LWvGgJGfB37cU8HNUXfCkT6OO84KT8qKHMT4mOxyUqnApTYOHBxXGetLd3z/n67KwBCkJLZBr9jw==", + "dev": true, + "dependencies": { + "@ethersproject/abstract-provider": "^5.4.1", + "@ethersproject/bytes": "^5.5.0", + "@ethersproject/providers": "^5.4.5", + "@ethersproject/web": "^5.5.0", + "chai": "^4.3.4", + "ethers": "^5.4.5", + "lodash": "^4.17.21" + } + }, + "node_modules/@eth-optimism/core-utils/node_modules/chai": { + "version": "4.3.4", + "resolved": "https://registry.npmjs.org/chai/-/chai-4.3.4.tgz", + "integrity": "sha512-yS5H68VYOCtN1cjfwumDSuzn/9c+yza4f3reKXlE5rUg7SFcCEy90gJvydNgOYtblyf4Zi6jIWRnXOgErta0KA==", + "dev": true, + "dependencies": { + "assertion-error": "^1.1.0", + "check-error": "^1.0.2", + "deep-eql": "^3.0.1", + "get-func-name": "^2.0.0", + "pathval": "^1.1.1", + "type-detect": "^4.0.5" + }, + "engines": { + "node": ">=4" + } + }, "node_modules/@eth-optimism/smock": { "version": "1.1.10", "resolved": "https://registry.npmjs.org/@eth-optimism/smock/-/smock-1.1.10.tgz", @@ -29777,17 +29809,50 @@ "@ethersproject/abstract-signer": "^5.1.0", "@ethersproject/contracts": "^5.0.5", "glob": "^7.1.6" + }, + "dependencies": { + "@eth-optimism/core-utils": { + "version": "0.4.7", + "resolved": "https://registry.npmjs.org/@eth-optimism/core-utils/-/core-utils-0.4.7.tgz", + "integrity": "sha512-H+NdGAyx6w4Wmjy3PaTF7xnBEeKh8BZK1jfU6yy52sd2E0oH7ABd0H5FtRY2SLPTH8AZ8gAgOhrTNoqE0Mlktw==", + "dev": true, + "requires": { + "@ethersproject/abstract-provider": "^5.0.9", + "ethers": "^5.0.31", + "lodash": "^4.17.21" + } + } } }, "@eth-optimism/core-utils": { - "version": "0.4.7", - "resolved": "https://registry.npmjs.org/@eth-optimism/core-utils/-/core-utils-0.4.7.tgz", - "integrity": "sha512-H+NdGAyx6w4Wmjy3PaTF7xnBEeKh8BZK1jfU6yy52sd2E0oH7ABd0H5FtRY2SLPTH8AZ8gAgOhrTNoqE0Mlktw==", + "version": "0.7.3", + "resolved": "https://registry.npmjs.org/@eth-optimism/core-utils/-/core-utils-0.7.3.tgz", + "integrity": "sha512-e531gfcMN6LWvGgJGfB37cU8HNUXfCkT6OO84KT8qKHMT4mOxyUqnApTYOHBxXGetLd3z/n67KwBCkJLZBr9jw==", "dev": true, "requires": { - "@ethersproject/abstract-provider": "^5.0.9", - "ethers": "^5.0.31", + "@ethersproject/abstract-provider": "^5.4.1", + "@ethersproject/bytes": "^5.5.0", + "@ethersproject/providers": "^5.4.5", + "@ethersproject/web": "^5.5.0", + "chai": "^4.3.4", + "ethers": "^5.4.5", "lodash": "^4.17.21" + }, + "dependencies": { + "chai": { + "version": "4.3.4", + "resolved": "https://registry.npmjs.org/chai/-/chai-4.3.4.tgz", + "integrity": "sha512-yS5H68VYOCtN1cjfwumDSuzn/9c+yza4f3reKXlE5rUg7SFcCEy90gJvydNgOYtblyf4Zi6jIWRnXOgErta0KA==", + "dev": true, + "requires": { + "assertion-error": "^1.1.0", + "check-error": "^1.0.2", + "deep-eql": "^3.0.1", + "get-func-name": "^2.0.0", + "pathval": "^1.1.1", + "type-detect": "^4.0.5" + } + } } }, "@eth-optimism/smock": { diff --git a/package.json b/package.json index ace72cdffe..1b362acb4c 100644 --- a/package.json +++ b/package.json @@ -75,7 +75,7 @@ "@chainlink/contracts-0.0.10": "npm:@chainlink/contracts@0.0.10", "@codechecks/client": "0.1.11", "@eth-optimism/contracts": "0.3.4", - "@eth-optimism/core-utils": "~0.4.5", + "@eth-optimism/core-utils": "0.7.3", "@eth-optimism/smock": "1.1.10", "@gnosis.pm/safe-core-sdk": "~0.3.1", "@gnosis.pm/safe-service-client": "~0.1.1", From 547c765eb13422d3387a7e622dc9d9676639850c Mon Sep 17 00:00:00 2001 From: artdgn Date: Tue, 18 Jan 2022 21:22:45 +1100 Subject: [PATCH 126/133] fix tests timeouts --- test/contracts/CollateralShort.js | 9 ++------- test/contracts/DebtCache.js | 14 ++++++-------- test/contracts/Exchanger.spec.js | 22 +++++++--------------- test/contracts/FeePool.js | 6 ++---- test/contracts/Issuer.js | 18 +++++++----------- test/contracts/PurgeableSynth.js | 17 ++++++----------- test/contracts/Synth.js | 13 ++++--------- test/contracts/SynthUtil.js | 17 ++++++----------- test/contracts/TradingRewards.spec.js | 9 ++------- test/contracts/helpers.js | 5 +---- test/integration/utils/rates.js | 2 -- 11 files changed, 43 insertions(+), 89 deletions(-) diff --git a/test/contracts/CollateralShort.js b/test/contracts/CollateralShort.js index 24bb6dd32d..eb2c937cfa 100644 --- a/test/contracts/CollateralShort.js +++ b/test/contracts/CollateralShort.js @@ -15,10 +15,7 @@ const { updateAggregatorRates, } = require('./helpers'); -const { - toBytes32, - defaults: { EXCHANGE_DYNAMIC_FEE_ROUNDS }, -} = require('../..'); +const { toBytes32 } = require('../..'); contract('CollateralShort', async accounts => { const YEAR = 31556926; @@ -64,9 +61,7 @@ contract('CollateralShort', async accounts => { const updateRatesWithDefaults = async () => { const sBTC = toBytes32('sBTC'); - for (let i = 0; i < EXCHANGE_DYNAMIC_FEE_ROUNDS; i++) { - await updateAggregatorRates(exchangeRates, [sETH, sBTC], [100, 10000].map(toUnit)); - } + await updateAggregatorRates(exchangeRates, [sETH, sBTC], [100, 10000].map(toUnit)); }; const setupShort = async () => { diff --git a/test/contracts/DebtCache.js b/test/contracts/DebtCache.js index f579a5ad20..9a319d3c63 100644 --- a/test/contracts/DebtCache.js +++ b/test/contracts/DebtCache.js @@ -21,7 +21,7 @@ const { const { toBytes32, - defaults: { DEBT_SNAPSHOT_STALE_TIME, EXCHANGE_DYNAMIC_FEE_ROUNDS }, + defaults: { DEBT_SNAPSHOT_STALE_TIME }, constants: { ZERO_ADDRESS }, } = require('../..'); @@ -287,13 +287,11 @@ contract('DebtCache', async accounts => { addSnapshotBeforeRestoreAfterEach(); beforeEach(async () => { - for (let i = 0; i < EXCHANGE_DYNAMIC_FEE_ROUNDS; i++) { - await updateAggregatorRates( - exchangeRates, - [sAUD, sEUR, SNX, sETH, ETH, iETH], - ['0.5', '1.25', '10', '200', '200', '200'].map(toUnit) - ); - } + await updateAggregatorRates( + exchangeRates, + [sAUD, sEUR, SNX, sETH, ETH, iETH], + ['0.5', '1.25', '10', '200', '200', '200'].map(toUnit) + ); const exchangeFeeRate = toUnit('0.003'); await setExchangeFeeRateForSynths({ diff --git a/test/contracts/Exchanger.spec.js b/test/contracts/Exchanger.spec.js index 993de083a1..c5f9ae6a69 100644 --- a/test/contracts/Exchanger.spec.js +++ b/test/contracts/Exchanger.spec.js @@ -2157,9 +2157,7 @@ contract('Exchanger (spec tests)', async accounts => { aggregator = await MockAggregator.new({ from: owner }); await exchangeRates.addAggregator(sETH, aggregator.address, { from: owner }); // set a 0 rate to prevent invalid rate from causing a revert on exchange - for (let i = 0; i < EXCHANGE_DYNAMIC_FEE_ROUNDS; i++) { - await aggregator.setLatestAnswer('0', await currentTime()); - } + await aggregator.setLatestAnswer('0', await currentTime()); }); describe('when exchanging into that synth', () => { @@ -3216,12 +3214,10 @@ contract('Exchanger (spec tests)', async accounts => { describe('and the aggregator has a rate (so the exchange succeeds)', () => { beforeEach(async () => { - for (let i = 0; i < EXCHANGE_DYNAMIC_FEE_ROUNDS; i++) { - await aggregator.setLatestAnswer( - convertToAggregatorPrice(100), - await currentTime() - ); - } + await aggregator.setLatestAnswer( + convertToAggregatorPrice(100), + await currentTime() + ); }); describe('when a user exchanges out of the aggregated rate into sUSD', () => { beforeEach(async () => { @@ -3501,9 +3497,7 @@ contract('Exchanger (spec tests)', async accounts => { const keys = [sAUD, sEUR, SNX, sETH, sBTC, iBTC]; const rates = ['0.5', '2', '1', '100', '5000', '5000'].map(toUnit); await setupPriceAggregators(exchangeRates, owner, keys); - for (let i = 0; i < EXCHANGE_DYNAMIC_FEE_ROUNDS; i++) { - await updateRates(keys, rates); - } + await updateRates(keys, rates); exchangeFeeRate = toUnit('0.005'); await setExchangeFeeRateForSynths({ @@ -3599,9 +3593,7 @@ contract('Exchanger (spec tests)', async accounts => { const keys = [sAUD, sEUR, SNX, sETH, sBTC, iBTC]; const rates = ['0.5', '2', '1', '100', '5000', '5000'].map(toUnit); await setupPriceAggregators(exchangeRates, owner, keys); - for (let i = 0; i < EXCHANGE_DYNAMIC_FEE_ROUNDS; i++) { - await updateRates(keys, rates); - } + await updateRates(keys, rates); // set a 0.5% exchange fee rate (1/200) exchangeFeeRate = toUnit('0.005'); diff --git a/test/contracts/FeePool.js b/test/contracts/FeePool.js index 7ab37b158a..07a6cd881c 100644 --- a/test/contracts/FeePool.js +++ b/test/contracts/FeePool.js @@ -25,7 +25,7 @@ const { setupAllContracts } = require('./setup'); const { toBytes32, - defaults: { ISSUANCE_RATIO, FEE_PERIOD_DURATION, TARGET_THRESHOLD, EXCHANGE_DYNAMIC_FEE_ROUNDS }, + defaults: { ISSUANCE_RATIO, FEE_PERIOD_DURATION, TARGET_THRESHOLD }, } = require('../..'); contract('FeePool', async accounts => { @@ -33,9 +33,7 @@ contract('FeePool', async accounts => { // Updates rates with defaults so they're not stale. const updateRatesWithDefaults = async () => { - for (let i = 0; i < EXCHANGE_DYNAMIC_FEE_ROUNDS; i++) { - await updateAggregatorRates(exchangeRates, [sAUD, SNX], ['0.5', '0.1'].map(toUnit)); - } + await updateAggregatorRates(exchangeRates, [sAUD, SNX], ['0.5', '0.1'].map(toUnit)); await debtCache.takeDebtSnapshot(); }; diff --git a/test/contracts/Issuer.js b/test/contracts/Issuer.js index febac572c5..fdf8c432d2 100644 --- a/test/contracts/Issuer.js +++ b/test/contracts/Issuer.js @@ -32,7 +32,7 @@ const { const { toBytes32, constants: { ZERO_ADDRESS }, - defaults: { ISSUANCE_RATIO, MINIMUM_STAKE_TIME, EXCHANGE_DYNAMIC_FEE_ROUNDS }, + defaults: { ISSUANCE_RATIO, MINIMUM_STAKE_TIME }, } = require('../..'); contract('Issuer (via Synthetix)', async accounts => { @@ -119,13 +119,11 @@ contract('Issuer (via Synthetix)', async accounts => { addSnapshotBeforeRestoreAfterEach(); beforeEach(async () => { - for (let i = 0; i < EXCHANGE_DYNAMIC_FEE_ROUNDS; i++) { - await updateAggregatorRates( - exchangeRates, - [sAUD, sEUR, SNX, sETH], - ['0.5', '1.25', '0.1', '200'].map(toUnit) - ); - } + await updateAggregatorRates( + exchangeRates, + [sAUD, sEUR, SNX, sETH], + ['0.5', '1.25', '0.1', '200'].map(toUnit) + ); // set a 0.3% default exchange fee rate const exchangeFeeRate = toUnit('0.003'); @@ -719,9 +717,7 @@ contract('Issuer (via Synthetix)', async accounts => { }); describe('when the synth has a rate', () => { beforeEach(async () => { - for (let i = 0; i < EXCHANGE_DYNAMIC_FEE_ROUNDS; i++) { - await updateAggregatorRates(exchangeRates, [currencyKey], [toUnit('2')]); - } + await updateAggregatorRates(exchangeRates, [currencyKey], [toUnit('2')]); }); describe('when another user exchanges into the synth', () => { diff --git a/test/contracts/PurgeableSynth.js b/test/contracts/PurgeableSynth.js index d04cd5ce9e..391af33332 100644 --- a/test/contracts/PurgeableSynth.js +++ b/test/contracts/PurgeableSynth.js @@ -11,7 +11,6 @@ const PurgeableSynth = artifacts.require('PurgeableSynth'); const { fastForward, toUnit } = require('../utils')(); const { toBytes32, - defaults: { EXCHANGE_DYNAMIC_FEE_ROUNDS }, constants: { ZERO_ADDRESS }, } = require('../..'); @@ -157,13 +156,11 @@ contract('PurgeableSynth', accounts => { describe("when there's a price for the purgeable synth", () => { beforeEach(async () => { - for (let i = 0; i < EXCHANGE_DYNAMIC_FEE_ROUNDS; i++) { - await updateAggregatorRates( - exchangeRates, - [sAUD, SNX, iETH], - ['0.5', '1', '170'].map(toUnit) - ); - } + await updateAggregatorRates( + exchangeRates, + [sAUD, SNX, iETH], + ['0.5', '1', '170'].map(toUnit) + ); await debtCache.takeDebtSnapshot(); }); @@ -321,9 +318,7 @@ contract('PurgeableSynth', accounts => { describe('Replacing an existing Synth with a Purgeable one to purge and remove it', () => { describe('when sAUD has a price', () => { beforeEach(async () => { - for (let i = 0; i < EXCHANGE_DYNAMIC_FEE_ROUNDS; i++) { - await updateAggregatorRates(exchangeRates, [sAUD], ['0.776845993'].map(toUnit)); - } + await updateAggregatorRates(exchangeRates, [sAUD], ['0.776845993'].map(toUnit)); await debtCache.takeDebtSnapshot(); }); describe('when a user holds some sAUD', () => { diff --git a/test/contracts/Synth.js b/test/contracts/Synth.js index bae72386bc..73a95273fd 100644 --- a/test/contracts/Synth.js +++ b/test/contracts/Synth.js @@ -22,7 +22,6 @@ const { const { toBytes32, constants: { ZERO_ADDRESS }, - defaults: { EXCHANGE_DYNAMIC_FEE_ROUNDS }, } = require('../..'); contract('Synth', async accounts => { @@ -82,10 +81,8 @@ contract('Synth', async accounts => { addSnapshotBeforeRestoreAfterEach(); beforeEach(async () => { - for (let i = 0; i < EXCHANGE_DYNAMIC_FEE_ROUNDS; i++) { - // Send a price update to guarantee we're not stale. - await updateAggregatorRates(exchangeRates, [SNX], ['0.1'].map(toUnit)); - } + // Send a price update to guarantee we're not stale. + await updateAggregatorRates(exchangeRates, [SNX], ['0.1'].map(toUnit)); await debtCache.takeDebtSnapshot(); // set default issuanceRatio to 0.2 @@ -736,10 +733,8 @@ contract('Synth', async accounts => { contracts: [{ contract: 'Synth', properties: { currencyKey: sEUR } }], })); - for (let i = 0; i < EXCHANGE_DYNAMIC_FEE_ROUNDS; i++) { - // Send a price update to guarantee we're not stale. - await updateAggregatorRates(exchangeRates, [sEUR], ['1'].map(toUnit)); - } + // Send a price update to guarantee we're not stale. + await updateAggregatorRates(exchangeRates, [sEUR], ['1'].map(toUnit)); await debtCache.takeDebtSnapshot(); }); diff --git a/test/contracts/SynthUtil.js b/test/contracts/SynthUtil.js index 6c8506b9f3..9abd6f8360 100644 --- a/test/contracts/SynthUtil.js +++ b/test/contracts/SynthUtil.js @@ -2,10 +2,7 @@ const { contract } = require('hardhat'); const { assert, addSnapshotBeforeRestoreAfterEach } = require('./common'); -const { - toBytes32, - defaults: { EXCHANGE_DYNAMIC_FEE_ROUNDS }, -} = require('../..'); +const { toBytes32 } = require('../..'); const { toUnit } = require('../utils')(); const { setExchangeFeeRateForSynths, @@ -56,13 +53,11 @@ contract('SynthUtil', accounts => { addSnapshotBeforeRestoreAfterEach(); beforeEach(async () => { - for (let i = 0; i < EXCHANGE_DYNAMIC_FEE_ROUNDS; i++) { - await updateAggregatorRates( - exchangeRates, - [sBTC, iBTC, SNX], - ['5000', '5000', '0.2'].map(toUnit) - ); - } + await updateAggregatorRates( + exchangeRates, + [sBTC, iBTC, SNX], + ['5000', '5000', '0.2'].map(toUnit) + ); await debtCache.takeDebtSnapshot(); // set a 0% default exchange fee rate for test purpose diff --git a/test/contracts/TradingRewards.spec.js b/test/contracts/TradingRewards.spec.js index ff3e7f80d5..97fc403a4c 100644 --- a/test/contracts/TradingRewards.spec.js +++ b/test/contracts/TradingRewards.spec.js @@ -10,10 +10,7 @@ const { setupPriceAggregators, updateAggregatorRates, } = require('./helpers'); -const { - toBytes32, - defaults: { EXCHANGE_DYNAMIC_FEE_ROUNDS }, -} = require('../..'); +const { toBytes32 } = require('../..'); /* * This tests the TradingRewards contract's integration @@ -112,9 +109,7 @@ contract('TradingRewards', accounts => { }); before('set exchange rates', async () => { - for (let i = 0; i < EXCHANGE_DYNAMIC_FEE_ROUNDS; i++) { - await updateAggregatorRates(exchangeRates, [sETH, sBTC, SNX], Object.values(rates)); - } + await updateAggregatorRates(exchangeRates, [sETH, sBTC, SNX], Object.values(rates)); await setExchangeFeeRateForSynths({ owner, diff --git a/test/contracts/helpers.js b/test/contracts/helpers.js index f77a2865ba..3510cb7579 100644 --- a/test/contracts/helpers.js +++ b/test/contracts/helpers.js @@ -8,7 +8,6 @@ const { assert } = require('./common'); const { currentTime, toUnit } = require('../utils')(); const { toBytes32, - defaults: { EXCHANGE_DYNAMIC_FEE_ROUNDS }, constants: { ZERO_ADDRESS, ZERO_BYTES32 }, } = require('../..'); @@ -140,9 +139,7 @@ module.exports = { // set up any missing aggregators await setupMissingPriceAggregators(exchangeRates, owner, keys); - for (let i = 0; i < EXCHANGE_DYNAMIC_FEE_ROUNDS; i++) { - await updateAggregatorRates(exchangeRates, keys, rates); - } + await updateAggregatorRates(exchangeRates, keys, rates); await debtCache.takeDebtSnapshot(); }, diff --git a/test/integration/utils/rates.js b/test/integration/utils/rates.js index 652b7b6d26..386553796c 100644 --- a/test/integration/utils/rates.js +++ b/test/integration/utils/rates.js @@ -39,10 +39,8 @@ async function addAggregatorAndSetRate({ ctx, currencyKey, rate }) { // set decimals await (await aggregator.setDecimals(18)).wait(); // push the new price - // for (let i = 0; i < EXCHANGE_DYNAMIC_FEE_ROUNDS; i++) { const { timestamp } = await ctx.provider.getBlock(); await (await aggregator.setLatestAnswer(rate, timestamp)).wait(); - // } // set the aggregator in ExchangeRates await (await exchangeRates.addAggregator(currencyKey, aggregator.address)).wait(); } From 419b5192b15ecb86ef6afd2c62b3ab774d5f16b9 Mon Sep 17 00:00:00 2001 From: artdgn Date: Tue, 18 Jan 2022 22:51:31 +1100 Subject: [PATCH 127/133] add missing exchanger tests --- test/contracts/Exchanger.spec.js | 143 ++++++++++++++++++++++++++++++- 1 file changed, 140 insertions(+), 3 deletions(-) diff --git a/test/contracts/Exchanger.spec.js b/test/contracts/Exchanger.spec.js index c5f9ae6a69..95dc538221 100644 --- a/test/contracts/Exchanger.spec.js +++ b/test/contracts/Exchanger.spec.js @@ -5,7 +5,14 @@ const { smockit } = require('@eth-optimism/smock'); const BN = require('bn.js'); const { assert, addSnapshotBeforeRestoreAfterEach } = require('./common'); -const { currentTime, fastForward, multiplyDecimal, divideDecimal, toUnit } = require('../utils')(); +const { + currentTime, + fastForward, + multiplyDecimal, + divideDecimal, + toUnit, + toBN, +} = require('../utils')(); const { setupAllContracts } = require('./setup'); @@ -29,6 +36,8 @@ const { PRICE_DEVIATION_THRESHOLD_FACTOR, ATOMIC_MAX_VOLUME_PER_BLOCK, EXCHANGE_DYNAMIC_FEE_ROUNDS, + EXCHANGE_DYNAMIC_FEE_WEIGHT_DECAY, + EXCHANGE_DYNAMIC_FEE_THRESHOLD, }, } = require('../..'); @@ -566,6 +575,134 @@ contract('Exchanger (spec tests)', async accounts => { assert.bnEqual(amountReceived, effectiveValue.sub(tripleFee)); }); }); + + describe('dynamic fee when rates change', () => { + const threshold = toBN(EXCHANGE_DYNAMIC_FEE_THRESHOLD); + + it('initial fee is correct', async () => { + assert.bnEqual(await exchanger.feeRateForExchange(sUSD, sBTC), bipsCrypto); + }); + + describe('fee is caluclated correctly when rates spike or drop', () => { + it('.3% spike is below threshold', async () => { + await updateRates([sETH], [toUnit(100.3)]); + // spike + assert.bnEqual(await exchanger.feeRateForExchange(sUSD, sETH), bipsCrypto); + // control + assert.bnEqual(await exchanger.feeRateForExchange(sUSD, sBTC), bipsCrypto); + }); + + it('.3% drop is below threshold', async () => { + await updateRates([sETH], [toUnit(99.7)]); + // spike + assert.bnEqual(await exchanger.feeRateForExchange(sUSD, sETH), bipsCrypto); + // control + assert.bnEqual(await exchanger.feeRateForExchange(sUSD, sBTC), bipsCrypto); + }); + + it('1% spike result in correct dynamic fee', async () => { + await updateRates([sETH], [toUnit(101)]); + // base fee + (price diff ratio (1%)- threshold) + const expectedFee = bipsCrypto.add(toUnit(0.01).sub(threshold)); + assert.bnEqual(await exchanger.feeRateForExchange(sUSD, sETH), expectedFee); + // control + assert.bnEqual(await exchanger.feeRateForExchange(sUSD, sBTC), bipsCrypto); + }); + + it('1% drop result in correct dynamic fee', async () => { + await updateRates([sETH], [toUnit(99)]); + // base fee + (price diff ratio (1%)- threshold) + const expectedFee = bipsCrypto.add(toUnit(0.01).sub(threshold)); + assert.bnEqual(await exchanger.feeRateForExchange(sUSD, sETH), expectedFee); + // control + assert.bnEqual(await exchanger.feeRateForExchange(sUSD, sBTC), bipsCrypto); + }); + + it('10% spike result in correct dynamic fee', async () => { + await updateRates([sETH], [toUnit(110)]); + // base fee + (price diff ratio (10%)- threshold) + const expectedFee = bipsCrypto.add(toUnit(0.1).sub(threshold)); + assert.bnEqual(await exchanger.feeRateForExchange(sUSD, sETH), expectedFee); + // control + assert.bnEqual(await exchanger.feeRateForExchange(sUSD, sBTC), bipsCrypto); + }); + + it('10% drop result in correct dynamic fee', async () => { + await updateRates([sETH], [toUnit(90)]); + // base fee + (price diff ratio (10%)- threshold) + const expectedFee = bipsCrypto.add(toUnit(0.1).sub(threshold)); + assert.bnEqual(await exchanger.feeRateForExchange(sUSD, sETH), expectedFee); + // control + assert.bnEqual(await exchanger.feeRateForExchange(sUSD, sBTC), bipsCrypto); + }); + + it('20% spike result in correct dynamic fee', async () => { + await updateRates([sETH], [toUnit(120)]); + // base fee + (price diff ratio (20%)- threshold) + const expectedFee = bipsCrypto.add(toUnit(0.2).sub(threshold)); + assert.bnEqual(await exchanger.feeRateForExchange(sUSD, sETH), expectedFee); + // control + assert.bnEqual(await exchanger.feeRateForExchange(sUSD, sBTC), bipsCrypto); + }); + + it('20% drop result in correct dynamic fee', async () => { + await updateRates([sETH], [toUnit(80)]); + // base fee + (price diff ratio (20%)- threshold) + const expectedFee = bipsCrypto.add(toUnit(0.2).sub(threshold)); + assert.bnEqual(await exchanger.feeRateForExchange(sUSD, sETH), expectedFee); + // control + assert.bnEqual(await exchanger.feeRateForExchange(sUSD, sBTC), bipsCrypto); + }); + + it('trading between two spiked rates is correctly calculated ', async () => { + await updateRates([sETH, sBTC], [toUnit(110), toUnit(5500)]); + // base fee + (price diff ratio (10%)- threshold) * 2 + const expectedFee = bipsCrypto.add( + toUnit(0.1) + .sub(threshold) + .mul(toBN(2)) + ); + + assert.bnEqual(await exchanger.feeRateForExchange(sETH, sBTC), expectedFee); + // reverse direction is the same + assert.bnEqual(await exchanger.feeRateForExchange(sBTC, sETH), expectedFee); + }); + }); + + it('dynamic fee decays with time', async () => { + await updateRates([sETH], [toUnit(110)]); + // (price diff ratio (10%)- threshold) + let expectedDynamicFee = toUnit(0.1).sub(threshold); + assert.bnEqual( + await exchanger.feeRateForExchange(sUSD, sETH), + bipsCrypto.add(expectedDynamicFee) + ); + + const decay = toBN(EXCHANGE_DYNAMIC_FEE_WEIGHT_DECAY); + + // next round + await updateRates([sETH], [toUnit(110)]); + expectedDynamicFee = multiplyDecimal(expectedDynamicFee, decay); + assert.bnEqual( + await exchanger.feeRateForExchange(sUSD, sETH), + bipsCrypto.add(expectedDynamicFee) + ); + + // another round + await updateRates([sETH], [toUnit(110)]); + expectedDynamicFee = multiplyDecimal(expectedDynamicFee, decay); + assert.bnEqual( + await exchanger.feeRateForExchange(sUSD, sETH), + bipsCrypto.add(expectedDynamicFee) + ); + + // EXCHANGE_DYNAMIC_FEE_ROUNDS after spike dynamic fee is 0 + for (let i = 0; i < EXCHANGE_DYNAMIC_FEE_ROUNDS - 3; i++) { + await updateRates([sETH], [toUnit(110)]); + } + assert.bnEqual(await exchanger.feeRateForExchange(sUSD, sETH), bipsCrypto); + }); + }); }); }); }; @@ -2488,8 +2625,8 @@ contract('Exchanger (spec tests)', async accounts => { // CL aggregator with past price data const aggregator = await MockAggregator.new({ from: owner }); await exchangeRates.addAggregator(sETH, aggregator.address, { from: owner }); - // set prices with no volatility - for (let i = EXCHANGE_DYNAMIC_FEE_ROUNDS; i > 0; i--) { + // set prices with no volatility over the course of last 20 minutes + for (let i = 4; i > 0; i--) { await aggregator.setLatestAnswer(ethOnCL, (await currentTime()) - i * 5 * 60); } From e19345cdcb308985874cd719209112e095e1958d Mon Sep 17 00:00:00 2001 From: artdgn Date: Tue, 18 Jan 2022 22:59:42 +1100 Subject: [PATCH 128/133] set mainnet rounds to 0 instead of 1 --- publish/deployed/mainnet/params.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/publish/deployed/mainnet/params.json b/publish/deployed/mainnet/params.json index 99633d4367..55615d3800 100644 --- a/publish/deployed/mainnet/params.json +++ b/publish/deployed/mainnet/params.json @@ -1,7 +1,7 @@ [ { "name": "EXCHANGE_DYNAMIC_FEE_ROUNDS", - "value": "1" + "value": "0" }, { "name": "DEX_PRICE_AGGREGATOR", From 79a055cb5a84063915aedcbb6533bcd8cc111aba Mon Sep 17 00:00:00 2001 From: artdgn Date: Tue, 18 Jan 2022 23:06:28 +1100 Subject: [PATCH 129/133] add short circuit for rounds 0 or 1 --- contracts/ExchangeRates.sol | 14 +++++++++----- contracts/Exchanger.sol | 12 ++++++++---- 2 files changed, 17 insertions(+), 9 deletions(-) diff --git a/contracts/ExchangeRates.sol b/contracts/ExchangeRates.sol index 9a0bdf6bd4..eff307e3e7 100644 --- a/contracts/ExchangeRates.sol +++ b/contracts/ExchangeRates.sol @@ -483,8 +483,9 @@ contract ExchangeRates is Owned, MixinSystemSettings, IExchangeRates { function _rateIsStale(bytes32 currencyKey, uint _rateStalePeriod) internal view returns (bool) { // sUSD is a special case and is never stale (check before an SLOAD of getRateAndUpdatedTime) - if (currencyKey == sUSD) return false; - + if (currencyKey == sUSD) { + return false; + } return _rateIsStaleWithTime(_rateStalePeriod, _getUpdatedTime(currencyKey)); } @@ -494,8 +495,9 @@ contract ExchangeRates is Owned, MixinSystemSettings, IExchangeRates { uint _rateStalePeriod ) internal view returns (bool) { // sUSD is a special case and is never stale (check before an SLOAD of getRateAndUpdatedTime) - if (currencyKey == sUSD) return false; - + if (currencyKey == sUSD) { + return false; + } (, uint time) = _getRateAndTimestampAtRound(currencyKey, roundId); return _rateIsStaleWithTime(_rateStalePeriod, time); } @@ -506,7 +508,9 @@ contract ExchangeRates is Owned, MixinSystemSettings, IExchangeRates { function _rateIsFlagged(bytes32 currencyKey, FlagsInterface flags) internal view returns (bool) { // sUSD is a special case and is never invalid - if (currencyKey == sUSD) return false; + if (currencyKey == sUSD) { + return false; + } address aggregator = address(aggregators[currencyKey]); // when no aggregator or when the flags haven't been setup if (aggregator == address(0) || flags == FlagsInterface(0)) { diff --git a/contracts/Exchanger.sol b/contracts/Exchanger.sol index 53c444d5ce..b4f2f3ef93 100644 --- a/contracts/Exchanger.sol +++ b/contracts/Exchanger.sol @@ -842,8 +842,10 @@ contract Exchanger is Owned, MixinSystemSettings, IExchanger { /// @param config dynamic fee calculation configuration params /// @return The dyanmic dynamicFee function _dynamicFeeForCurrency(bytes32 currencyKey, DynamicFeeConfig memory config) internal view returns (uint) { - // No dynamic dynamicFee for sUSD - if (currencyKey == sUSD) return 0; + // no dynamic dynamicFee for sUSD or too few rounds + if (currencyKey == sUSD || config.rounds <= 1) { + return 0; + } uint[] memory prices; uint roundId = exchangeRates().getCurrentRoundId(currencyKey); (prices, ) = exchangeRates().ratesAndUpdatedTimeForCurrencyLastNRounds(currencyKey, config.rounds, roundId); @@ -860,8 +862,10 @@ contract Exchanger is Owned, MixinSystemSettings, IExchanger { uint roundId, DynamicFeeConfig memory config ) internal view returns (uint) { - // No dynamic dynamicFee for sUSD - if (currencyKey == sUSD) return 0; + // no dynamic dynamicFee for sUSD or too few rounds + if (currencyKey == sUSD || config.rounds <= 1) { + return 0; + } uint[] memory prices; (prices, ) = exchangeRates().ratesAndUpdatedTimeForCurrencyLastNRounds(currencyKey, config.rounds, roundId); return _dynamicFeeCalculation(prices, config.threshold, config.weightDecay); From 67f44f5e36ebd3206383f428fb81106bd74fc428 Mon Sep 17 00:00:00 2001 From: artdgn Date: Tue, 18 Jan 2022 23:15:39 +1100 Subject: [PATCH 130/133] revert test publish change --- test/contracts/ExchangeRates.js | 2 +- test/publish/index.js | 10 ---------- 2 files changed, 1 insertion(+), 11 deletions(-) diff --git a/test/contracts/ExchangeRates.js b/test/contracts/ExchangeRates.js index b1a1c18c3d..7ef19f9ac6 100644 --- a/test/contracts/ExchangeRates.js +++ b/test/contracts/ExchangeRates.js @@ -944,7 +944,7 @@ contract('Exchange Rates', async accounts => { assert.equal(await instance.getCurrentRoundId(sBNB), 0); }); - it('getCurrentRoundId() is 1 for sUSD', async () => { + it('getCurrentRoundId() is 0 for sUSD', async () => { assert.equal(await instance.getCurrentRoundId(sUSD), 0); }); diff --git a/test/publish/index.js b/test/publish/index.js index 318eaf80df..46fa1250fd 100644 --- a/test/publish/index.js +++ b/test/publish/index.js @@ -258,10 +258,6 @@ describe('publish scripts', () => { sBTCContract = getContract({ target: 'ProxysBTC', source: 'Synth' }); sETHContract = getContract({ target: 'ProxysETH', source: 'Synth' }); SystemSettings = getContract({ target: 'SystemSettings' }); - // Disable exchange dynamic fee so that we can neglect it - // Using 1 so that it wouldn't get override back to default by redeployment - const tx = await SystemSettings.setExchangeDynamicFeeRounds('1', overrides); - await tx.wait(); Liquidations = getContract({ target: 'Liquidations' }); @@ -334,7 +330,6 @@ describe('publish scripts', () => { let newRateForsUSD; let newMinimumStakeTime; let newDebtSnapshotStaleTime; - let newExchangeDynamicFeeRounds; beforeEach(async () => { newWaitingPeriod = '10'; @@ -351,7 +346,6 @@ describe('publish scripts', () => { newRateForsUSD = ethers.utils.parseEther('0.1').toString(); newMinimumStakeTime = '3999'; newDebtSnapshotStaleTime = '43200'; // Half a day - newExchangeDynamicFeeRounds = '1'; let tx; @@ -469,10 +463,6 @@ describe('publish scripts', () => { newAtomicTwapWindow ); assert.strictEqual((await Issuer.minimumStakeTime()).toString(), newMinimumStakeTime); - assert.strictEqual( - (await SystemSettings.exchangeDynamicFeeRounds()).toString(), - newExchangeDynamicFeeRounds - ); assert.strictEqual( ( await Exchanger.feeRateForExchange(toBytes32('(ignored)'), toBytes32('sUSD')) From ba3126b4d7c609c0fd3837f5190a2595082c1034 Mon Sep 17 00:00:00 2001 From: artdgn Date: Wed, 19 Jan 2022 11:51:23 +1100 Subject: [PATCH 131/133] Revert "update optimism core utils package" This reverts commit 29346419e5eb465f0ea6ab2fefaaad7eab29995c. --- package-lock.json | 79 +++++------------------------------------------ package.json | 2 +- 2 files changed, 8 insertions(+), 73 deletions(-) diff --git a/package-lock.json b/package-lock.json index 1b26055c5b..637621672f 100644 --- a/package-lock.json +++ b/package-lock.json @@ -25,7 +25,7 @@ "@chainlink/contracts-0.0.10": "npm:@chainlink/contracts@0.0.10", "@codechecks/client": "0.1.11", "@eth-optimism/contracts": "0.3.4", - "@eth-optimism/core-utils": "0.7.3", + "@eth-optimism/core-utils": "~0.4.5", "@eth-optimism/smock": "1.1.10", "@gnosis.pm/safe-core-sdk": "~0.3.1", "@gnosis.pm/safe-service-client": "~0.1.1", @@ -732,7 +732,7 @@ "ethers": "^5.0.0" } }, - "node_modules/@eth-optimism/contracts/node_modules/@eth-optimism/core-utils": { + "node_modules/@eth-optimism/core-utils": { "version": "0.4.7", "resolved": "https://registry.npmjs.org/@eth-optimism/core-utils/-/core-utils-0.4.7.tgz", "integrity": "sha512-H+NdGAyx6w4Wmjy3PaTF7xnBEeKh8BZK1jfU6yy52sd2E0oH7ABd0H5FtRY2SLPTH8AZ8gAgOhrTNoqE0Mlktw==", @@ -743,38 +743,6 @@ "lodash": "^4.17.21" } }, - "node_modules/@eth-optimism/core-utils": { - "version": "0.7.3", - "resolved": "https://registry.npmjs.org/@eth-optimism/core-utils/-/core-utils-0.7.3.tgz", - "integrity": "sha512-e531gfcMN6LWvGgJGfB37cU8HNUXfCkT6OO84KT8qKHMT4mOxyUqnApTYOHBxXGetLd3z/n67KwBCkJLZBr9jw==", - "dev": true, - "dependencies": { - "@ethersproject/abstract-provider": "^5.4.1", - "@ethersproject/bytes": "^5.5.0", - "@ethersproject/providers": "^5.4.5", - "@ethersproject/web": "^5.5.0", - "chai": "^4.3.4", - "ethers": "^5.4.5", - "lodash": "^4.17.21" - } - }, - "node_modules/@eth-optimism/core-utils/node_modules/chai": { - "version": "4.3.4", - "resolved": "https://registry.npmjs.org/chai/-/chai-4.3.4.tgz", - "integrity": "sha512-yS5H68VYOCtN1cjfwumDSuzn/9c+yza4f3reKXlE5rUg7SFcCEy90gJvydNgOYtblyf4Zi6jIWRnXOgErta0KA==", - "dev": true, - "dependencies": { - "assertion-error": "^1.1.0", - "check-error": "^1.0.2", - "deep-eql": "^3.0.1", - "get-func-name": "^2.0.0", - "pathval": "^1.1.1", - "type-detect": "^4.0.5" - }, - "engines": { - "node": ">=4" - } - }, "node_modules/@eth-optimism/smock": { "version": "1.1.10", "resolved": "https://registry.npmjs.org/@eth-optimism/smock/-/smock-1.1.10.tgz", @@ -29809,50 +29777,17 @@ "@ethersproject/abstract-signer": "^5.1.0", "@ethersproject/contracts": "^5.0.5", "glob": "^7.1.6" - }, - "dependencies": { - "@eth-optimism/core-utils": { - "version": "0.4.7", - "resolved": "https://registry.npmjs.org/@eth-optimism/core-utils/-/core-utils-0.4.7.tgz", - "integrity": "sha512-H+NdGAyx6w4Wmjy3PaTF7xnBEeKh8BZK1jfU6yy52sd2E0oH7ABd0H5FtRY2SLPTH8AZ8gAgOhrTNoqE0Mlktw==", - "dev": true, - "requires": { - "@ethersproject/abstract-provider": "^5.0.9", - "ethers": "^5.0.31", - "lodash": "^4.17.21" - } - } } }, "@eth-optimism/core-utils": { - "version": "0.7.3", - "resolved": "https://registry.npmjs.org/@eth-optimism/core-utils/-/core-utils-0.7.3.tgz", - "integrity": "sha512-e531gfcMN6LWvGgJGfB37cU8HNUXfCkT6OO84KT8qKHMT4mOxyUqnApTYOHBxXGetLd3z/n67KwBCkJLZBr9jw==", + "version": "0.4.7", + "resolved": "https://registry.npmjs.org/@eth-optimism/core-utils/-/core-utils-0.4.7.tgz", + "integrity": "sha512-H+NdGAyx6w4Wmjy3PaTF7xnBEeKh8BZK1jfU6yy52sd2E0oH7ABd0H5FtRY2SLPTH8AZ8gAgOhrTNoqE0Mlktw==", "dev": true, "requires": { - "@ethersproject/abstract-provider": "^5.4.1", - "@ethersproject/bytes": "^5.5.0", - "@ethersproject/providers": "^5.4.5", - "@ethersproject/web": "^5.5.0", - "chai": "^4.3.4", - "ethers": "^5.4.5", + "@ethersproject/abstract-provider": "^5.0.9", + "ethers": "^5.0.31", "lodash": "^4.17.21" - }, - "dependencies": { - "chai": { - "version": "4.3.4", - "resolved": "https://registry.npmjs.org/chai/-/chai-4.3.4.tgz", - "integrity": "sha512-yS5H68VYOCtN1cjfwumDSuzn/9c+yza4f3reKXlE5rUg7SFcCEy90gJvydNgOYtblyf4Zi6jIWRnXOgErta0KA==", - "dev": true, - "requires": { - "assertion-error": "^1.1.0", - "check-error": "^1.0.2", - "deep-eql": "^3.0.1", - "get-func-name": "^2.0.0", - "pathval": "^1.1.1", - "type-detect": "^4.0.5" - } - } } }, "@eth-optimism/smock": { diff --git a/package.json b/package.json index 1b362acb4c..ace72cdffe 100644 --- a/package.json +++ b/package.json @@ -75,7 +75,7 @@ "@chainlink/contracts-0.0.10": "npm:@chainlink/contracts@0.0.10", "@codechecks/client": "0.1.11", "@eth-optimism/contracts": "0.3.4", - "@eth-optimism/core-utils": "0.7.3", + "@eth-optimism/core-utils": "~0.4.5", "@eth-optimism/smock": "1.1.10", "@gnosis.pm/safe-core-sdk": "~0.3.1", "@gnosis.pm/safe-service-client": "~0.1.1", From 44c5ea5f334ab550199d82c0f7ef46a5032dc32d Mon Sep 17 00:00:00 2001 From: artdgn Date: Wed, 19 Jan 2022 11:51:43 +1100 Subject: [PATCH 132/133] Revert "update optimistm ops tool commit hash" This reverts commit ac868c2d4d51661b2a8fb570a938a6ac30978774. --- hardhat/tasks/task-ops.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/hardhat/tasks/task-ops.js b/hardhat/tasks/task-ops.js index c3714bab49..ab8e47c140 100644 --- a/hardhat/tasks/task-ops.js +++ b/hardhat/tasks/task-ops.js @@ -25,7 +25,7 @@ task('ops', 'Run Optimism chain') .addOptionalParam( 'optimismCommit', 'Commit to checkout', - '0a20266f85ce0b1f54d8ec327ae093401961411f' + 'f1631a5f7ddb6eb4a342bfbd7d46233a43412f9b' ) .setAction(async (taskArguments, hre, runSuper) => { taskArguments.maxMemory = true; From 6edd50f259762dbb238bc279bd9f3a0579da86be Mon Sep 17 00:00:00 2001 From: artdgn Date: Thu, 20 Jan 2022 12:55:06 +1100 Subject: [PATCH 133/133] add dynamic fee view --- contracts/Exchanger.sol | 42 ++++--- contracts/ExchangerWithFeeRecAlternatives.sol | 2 +- contracts/interfaces/IExchanger.sol | 5 + test/contracts/Exchanger.spec.js | 113 ++++++++++++------ 4 files changed, 110 insertions(+), 52 deletions(-) diff --git a/contracts/Exchanger.sol b/contracts/Exchanger.sol index b4f2f3ef93..9ff1a888f6 100644 --- a/contracts/Exchanger.sol +++ b/contracts/Exchanger.sol @@ -773,7 +773,7 @@ contract Exchanger is Owned, MixinSystemSettings, IExchanger { } /* ========== Exchange Related Fees ========== */ - /// @notice public function to get the fee for a given exchange + /// @notice public function to get the total fee rate for a given exchange /// @param sourceCurrencyKey The source currency key /// @param destinationCurrencyKey The destination currency key /// @return The exchange fee rate @@ -781,6 +781,18 @@ contract Exchanger is Owned, MixinSystemSettings, IExchanger { return _feeRateForExchange(sourceCurrencyKey, destinationCurrencyKey); } + /// @notice public function to get the dynamic fee rate for a given exchange + /// @param sourceCurrencyKey The source currency key + /// @param destinationCurrencyKey The destination currency key + /// @return The exchange dynamic fee rate + function dynamicFeeRateForExchange(bytes32 sourceCurrencyKey, bytes32 destinationCurrencyKey) + external + view + returns (uint) + { + return _dynamicFeeRateForExchange(sourceCurrencyKey, destinationCurrencyKey); + } + /// @notice Calculate the exchange fee for a given source and destination currency key /// @param sourceCurrencyKey The source currency key /// @param destinationCurrencyKey The destination currency key @@ -789,7 +801,7 @@ contract Exchanger is Owned, MixinSystemSettings, IExchanger { function _feeRateForExchange(bytes32 sourceCurrencyKey, bytes32 destinationCurrencyKey) internal view returns (uint) { // Get the exchange fee rate as per destination currencyKey uint baseRate = getExchangeFeeRate(destinationCurrencyKey); - uint feeRate = baseRate.add(_dynamicFeeForExchange(sourceCurrencyKey, destinationCurrencyKey)); + uint feeRate = baseRate.add(_dynamicFeeRateForExchange(sourceCurrencyKey, destinationCurrencyKey)); // cap fee rate to 100% to prevent negative amounts return feeRate > SafeDecimalMath.unit() ? SafeDecimalMath.unit() : feeRate; } @@ -810,29 +822,33 @@ contract Exchanger is Owned, MixinSystemSettings, IExchanger { // Get the exchange fee rate as per destination currencyKey uint baseRate = getExchangeFeeRate(destinationCurrencyKey); uint dynamicFee = - _dynamicFeeForExchangeAtRounds(sourceCurrencyKey, destinationCurrencyKey, roundIdForSrc, roundIdForDest); + _dynamicFeeRateForExchangeAtRounds(sourceCurrencyKey, destinationCurrencyKey, roundIdForSrc, roundIdForDest); uint feeRate = baseRate.add(dynamicFee); // cap fee rate to 100% to prevent negative amounts return feeRate > SafeDecimalMath.unit() ? SafeDecimalMath.unit() : feeRate; } - function _dynamicFeeForExchange(bytes32 sourceCurrencyKey, bytes32 destinationCurrencyKey) internal view returns (uint) { + function _dynamicFeeRateForExchange(bytes32 sourceCurrencyKey, bytes32 destinationCurrencyKey) + internal + view + returns (uint) + { DynamicFeeConfig memory config = getExchangeDynamicFeeConfig(); - uint dynamicFee = _dynamicFeeForCurrency(destinationCurrencyKey, config); - dynamicFee = dynamicFee.add(_dynamicFeeForCurrency(sourceCurrencyKey, config)); + uint dynamicFee = _dynamicFeeRateForCurrency(destinationCurrencyKey, config); + dynamicFee = dynamicFee.add(_dynamicFeeRateForCurrency(sourceCurrencyKey, config)); // cap to maxFee return dynamicFee > config.maxFee ? config.maxFee : dynamicFee; } - function _dynamicFeeForExchangeAtRounds( + function _dynamicFeeRateForExchangeAtRounds( bytes32 sourceCurrencyKey, bytes32 destinationCurrencyKey, uint roundIdForSrc, uint roundIdForDest ) internal view returns (uint) { DynamicFeeConfig memory config = getExchangeDynamicFeeConfig(); - uint dynamicFee = _dynamicFeeForCurrencyRound(destinationCurrencyKey, roundIdForDest, config); - dynamicFee = dynamicFee.add(_dynamicFeeForCurrencyRound(sourceCurrencyKey, roundIdForSrc, config)); + uint dynamicFee = _dynamicFeeRateForCurrencyRound(destinationCurrencyKey, roundIdForDest, config); + dynamicFee = dynamicFee.add(_dynamicFeeRateForCurrencyRound(sourceCurrencyKey, roundIdForSrc, config)); // cap to maxFee return dynamicFee > config.maxFee ? config.maxFee : dynamicFee; } @@ -841,15 +857,13 @@ contract Exchanger is Owned, MixinSystemSettings, IExchanger { /// @param currencyKey The given currency key /// @param config dynamic fee calculation configuration params /// @return The dyanmic dynamicFee - function _dynamicFeeForCurrency(bytes32 currencyKey, DynamicFeeConfig memory config) internal view returns (uint) { + function _dynamicFeeRateForCurrency(bytes32 currencyKey, DynamicFeeConfig memory config) internal view returns (uint) { // no dynamic dynamicFee for sUSD or too few rounds if (currencyKey == sUSD || config.rounds <= 1) { return 0; } - uint[] memory prices; uint roundId = exchangeRates().getCurrentRoundId(currencyKey); - (prices, ) = exchangeRates().ratesAndUpdatedTimeForCurrencyLastNRounds(currencyKey, config.rounds, roundId); - return _dynamicFeeCalculation(prices, config.threshold, config.weightDecay); + return _dynamicFeeRateForCurrencyRound(currencyKey, roundId, config); } /// @notice Get dynamicFee for a given currency key (SIP-184) @@ -857,7 +871,7 @@ contract Exchanger is Owned, MixinSystemSettings, IExchanger { /// @param roundId The round id /// @param config dynamic fee calculation configuration params /// @return The dyanmic dynamicFee - function _dynamicFeeForCurrencyRound( + function _dynamicFeeRateForCurrencyRound( bytes32 currencyKey, uint roundId, DynamicFeeConfig memory config diff --git a/contracts/ExchangerWithFeeRecAlternatives.sol b/contracts/ExchangerWithFeeRecAlternatives.sol index e47258a7c4..7c6d871cf1 100644 --- a/contracts/ExchangerWithFeeRecAlternatives.sol +++ b/contracts/ExchangerWithFeeRecAlternatives.sol @@ -279,7 +279,7 @@ contract ExchangerWithFeeRecAlternatives is MinimalProxyFactory, Exchanger { baseRate = getExchangeFeeRate(destinationCurrencyKey); } - return baseRate.add(_dynamicFeeForExchange(sourceCurrencyKey, destinationCurrencyKey)); + return baseRate.add(_dynamicFeeRateForExchange(sourceCurrencyKey, destinationCurrencyKey)); } function _getAmountsForAtomicExchangeMinusFees( diff --git a/contracts/interfaces/IExchanger.sol b/contracts/interfaces/IExchanger.sol index 2cd5323847..13113b35a8 100644 --- a/contracts/interfaces/IExchanger.sol +++ b/contracts/interfaces/IExchanger.sol @@ -53,6 +53,11 @@ interface IExchanger { view returns (uint exchangeFeeRate); + function dynamicFeeRateForExchange(bytes32 sourceCurrencyKey, bytes32 destinationCurrencyKey) + external + view + returns (uint); + function getAmountsForExchange( uint sourceAmount, bytes32 sourceCurrencyKey, diff --git a/test/contracts/Exchanger.spec.js b/test/contracts/Exchanger.spec.js index 95dc538221..b86bf3a591 100644 --- a/test/contracts/Exchanger.spec.js +++ b/test/contracts/Exchanger.spec.js @@ -581,6 +581,7 @@ contract('Exchanger (spec tests)', async accounts => { it('initial fee is correct', async () => { assert.bnEqual(await exchanger.feeRateForExchange(sUSD, sBTC), bipsCrypto); + assert.bnEqual(await exchanger.dynamicFeeRateForExchange(sUSD, sBTC), 0); }); describe('fee is caluclated correctly when rates spike or drop', () => { @@ -588,68 +589,82 @@ contract('Exchanger (spec tests)', async accounts => { await updateRates([sETH], [toUnit(100.3)]); // spike assert.bnEqual(await exchanger.feeRateForExchange(sUSD, sETH), bipsCrypto); + assert.bnEqual(await exchanger.dynamicFeeRateForExchange(sUSD, sETH), 0); // control assert.bnEqual(await exchanger.feeRateForExchange(sUSD, sBTC), bipsCrypto); + assert.bnEqual(await exchanger.dynamicFeeRateForExchange(sBTC, sBTC), 0); }); it('.3% drop is below threshold', async () => { await updateRates([sETH], [toUnit(99.7)]); // spike assert.bnEqual(await exchanger.feeRateForExchange(sUSD, sETH), bipsCrypto); + assert.bnEqual(await exchanger.dynamicFeeRateForExchange(sUSD, sETH), 0); // control assert.bnEqual(await exchanger.feeRateForExchange(sUSD, sBTC), bipsCrypto); + assert.bnEqual(await exchanger.dynamicFeeRateForExchange(sBTC, sBTC), 0); }); it('1% spike result in correct dynamic fee', async () => { await updateRates([sETH], [toUnit(101)]); - // base fee + (price diff ratio (1%)- threshold) - const expectedFee = bipsCrypto.add(toUnit(0.01).sub(threshold)); - assert.bnEqual(await exchanger.feeRateForExchange(sUSD, sETH), expectedFee); + // price diff ratio (1%)- threshold + const expectedDynamicFee = toUnit(0.01).sub(threshold); + assert.bnEqual( + await exchanger.feeRateForExchange(sUSD, sETH), + bipsCrypto.add(expectedDynamicFee) + ); + assert.bnEqual( + await exchanger.dynamicFeeRateForExchange(sUSD, sETH), + expectedDynamicFee + ); // control assert.bnEqual(await exchanger.feeRateForExchange(sUSD, sBTC), bipsCrypto); }); it('1% drop result in correct dynamic fee', async () => { await updateRates([sETH], [toUnit(99)]); - // base fee + (price diff ratio (1%)- threshold) - const expectedFee = bipsCrypto.add(toUnit(0.01).sub(threshold)); - assert.bnEqual(await exchanger.feeRateForExchange(sUSD, sETH), expectedFee); + // price diff ratio (1%)- threshold + const expectedDynamicFee = toUnit(0.01).sub(threshold); + assert.bnEqual( + await exchanger.feeRateForExchange(sUSD, sETH), + bipsCrypto.add(expectedDynamicFee) + ); + assert.bnEqual( + await exchanger.dynamicFeeRateForExchange(sUSD, sETH), + expectedDynamicFee + ); // control assert.bnEqual(await exchanger.feeRateForExchange(sUSD, sBTC), bipsCrypto); }); it('10% spike result in correct dynamic fee', async () => { await updateRates([sETH], [toUnit(110)]); - // base fee + (price diff ratio (10%)- threshold) - const expectedFee = bipsCrypto.add(toUnit(0.1).sub(threshold)); - assert.bnEqual(await exchanger.feeRateForExchange(sUSD, sETH), expectedFee); + // price diff ratio (10%)- threshold + const expectedDynamicFee = toUnit(0.1).sub(threshold); + assert.bnEqual( + await exchanger.feeRateForExchange(sUSD, sETH), + bipsCrypto.add(expectedDynamicFee) + ); + assert.bnEqual( + await exchanger.dynamicFeeRateForExchange(sUSD, sETH), + expectedDynamicFee + ); // control assert.bnEqual(await exchanger.feeRateForExchange(sUSD, sBTC), bipsCrypto); }); it('10% drop result in correct dynamic fee', async () => { await updateRates([sETH], [toUnit(90)]); - // base fee + (price diff ratio (10%)- threshold) - const expectedFee = bipsCrypto.add(toUnit(0.1).sub(threshold)); - assert.bnEqual(await exchanger.feeRateForExchange(sUSD, sETH), expectedFee); - // control - assert.bnEqual(await exchanger.feeRateForExchange(sUSD, sBTC), bipsCrypto); - }); - - it('20% spike result in correct dynamic fee', async () => { - await updateRates([sETH], [toUnit(120)]); - // base fee + (price diff ratio (20%)- threshold) - const expectedFee = bipsCrypto.add(toUnit(0.2).sub(threshold)); - assert.bnEqual(await exchanger.feeRateForExchange(sUSD, sETH), expectedFee); - // control - assert.bnEqual(await exchanger.feeRateForExchange(sUSD, sBTC), bipsCrypto); - }); - - it('20% drop result in correct dynamic fee', async () => { - await updateRates([sETH], [toUnit(80)]); - // base fee + (price diff ratio (20%)- threshold) - const expectedFee = bipsCrypto.add(toUnit(0.2).sub(threshold)); - assert.bnEqual(await exchanger.feeRateForExchange(sUSD, sETH), expectedFee); + // price diff ratio (10%)- threshold + const expectedDynamicFee = toUnit(0.1).sub(threshold); + assert.bnEqual( + await exchanger.feeRateForExchange(sUSD, sETH), + bipsCrypto.add(expectedDynamicFee) + ); + assert.bnEqual( + await exchanger.dynamicFeeRateForExchange(sUSD, sETH), + expectedDynamicFee + ); // control assert.bnEqual(await exchanger.feeRateForExchange(sUSD, sBTC), bipsCrypto); }); @@ -657,15 +672,26 @@ contract('Exchanger (spec tests)', async accounts => { it('trading between two spiked rates is correctly calculated ', async () => { await updateRates([sETH, sBTC], [toUnit(110), toUnit(5500)]); // base fee + (price diff ratio (10%)- threshold) * 2 - const expectedFee = bipsCrypto.add( - toUnit(0.1) - .sub(threshold) - .mul(toBN(2)) + const expectedDynamicFee = toUnit(0.1) + .sub(threshold) + .mul(toBN(2)); + assert.bnEqual( + await exchanger.feeRateForExchange(sBTC, sETH), + bipsCrypto.add(expectedDynamicFee) + ); + assert.bnEqual( + await exchanger.dynamicFeeRateForExchange(sBTC, sETH), + expectedDynamicFee ); - - assert.bnEqual(await exchanger.feeRateForExchange(sETH, sBTC), expectedFee); // reverse direction is the same - assert.bnEqual(await exchanger.feeRateForExchange(sBTC, sETH), expectedFee); + assert.bnEqual( + await exchanger.feeRateForExchange(sETH, sBTC), + bipsCrypto.add(expectedDynamicFee) + ); + assert.bnEqual( + await exchanger.dynamicFeeRateForExchange(sETH, sBTC), + expectedDynamicFee + ); }); }); @@ -677,6 +703,10 @@ contract('Exchanger (spec tests)', async accounts => { await exchanger.feeRateForExchange(sUSD, sETH), bipsCrypto.add(expectedDynamicFee) ); + assert.bnEqual( + await exchanger.dynamicFeeRateForExchange(sUSD, sETH), + expectedDynamicFee + ); const decay = toBN(EXCHANGE_DYNAMIC_FEE_WEIGHT_DECAY); @@ -687,6 +717,10 @@ contract('Exchanger (spec tests)', async accounts => { await exchanger.feeRateForExchange(sUSD, sETH), bipsCrypto.add(expectedDynamicFee) ); + assert.bnEqual( + await exchanger.dynamicFeeRateForExchange(sUSD, sETH), + expectedDynamicFee + ); // another round await updateRates([sETH], [toUnit(110)]); @@ -695,12 +729,17 @@ contract('Exchanger (spec tests)', async accounts => { await exchanger.feeRateForExchange(sUSD, sETH), bipsCrypto.add(expectedDynamicFee) ); + assert.bnEqual( + await exchanger.dynamicFeeRateForExchange(sUSD, sETH), + expectedDynamicFee + ); // EXCHANGE_DYNAMIC_FEE_ROUNDS after spike dynamic fee is 0 for (let i = 0; i < EXCHANGE_DYNAMIC_FEE_ROUNDS - 3; i++) { await updateRates([sETH], [toUnit(110)]); } assert.bnEqual(await exchanger.feeRateForExchange(sUSD, sETH), bipsCrypto); + assert.bnEqual(await exchanger.dynamicFeeRateForExchange(sUSD, sETH), 0); }); }); });