diff --git a/markets/spot-market/contracts/modules/WrapperModule.sol b/markets/spot-market/contracts/modules/WrapperModule.sol index 01ff05598e..8e5857479c 100644 --- a/markets/spot-market/contracts/modules/WrapperModule.sol +++ b/markets/spot-market/contracts/modules/WrapperModule.sol @@ -87,7 +87,7 @@ contract WrapperModule is IWrapperModule { (amountToMint, fees, config) = MarketConfiguration.quoteWrap( marketId, wrapAmountD18, - Price.getCurrentPrice(marketId, Transaction.Type.WRAP, Price.Tolerance.STRICT) + Price.getCurrentPrice(marketId, Transaction.Type.WRAP, Price.Tolerance.ONE_MONTH) ); if (amountToMint < minAmountReceived) { @@ -147,7 +147,7 @@ contract WrapperModule is IWrapperModule { (returnCollateralAmountD18, fees, config) = MarketConfiguration.quoteUnwrap( marketId, unwrapAmount, - Price.getCurrentPrice(marketId, Transaction.Type.UNWRAP, Price.Tolerance.STRICT) + Price.getCurrentPrice(marketId, Transaction.Type.UNWRAP, Price.Tolerance.ONE_MONTH) ); uint8 collateralDecimals = ITokenModule(wrapperStore.wrapCollateralType).decimals(); diff --git a/markets/spot-market/contracts/storage/Price.sol b/markets/spot-market/contracts/storage/Price.sol index f8a09cadcb..023a278ae0 100644 --- a/markets/spot-market/contracts/storage/Price.sol +++ b/markets/spot-market/contracts/storage/Price.sol @@ -18,9 +18,12 @@ library Price { enum Tolerance { DEFAULT, - STRICT + STRICT, + ONE_MONTH } + uint256 private constant ONE_MONTH = 2592000; + struct Data { /** * @dev The oracle manager node id used for buy transactions. @@ -55,18 +58,18 @@ library Price { NodeOutput.Data memory output; - if (priceTolerance == Tolerance.STRICT) { + if (priceTolerance == Tolerance.DEFAULT) { + output = INodeModule(factory.oracle).process(feedId); + } else { bytes32[] memory runtimeKeys = new bytes32[](1); bytes32[] memory runtimeValues = new bytes32[](1); runtimeKeys[0] = bytes32("stalenessTolerance"); - runtimeValues[0] = bytes32(self.strictStalenessTolerance); + runtimeValues[0] = toleranceBytes(self, priceTolerance); output = INodeModule(factory.oracle).processWithRuntime( feedId, runtimeKeys, runtimeValues ); - } else { - output = INodeModule(factory.oracle).process(feedId); } price = output.price.toUint(); @@ -101,4 +104,17 @@ library Price { function scaleTo(int256 amount, uint256 decimals) internal pure returns (int256 scaledAmount) { return (decimals > 18 ? amount.upscale(decimals - 18) : amount.downscale(18 - decimals)); } + + function toleranceBytes( + Data storage self, + Tolerance tolerance + ) internal view returns (bytes32) { + if (tolerance == Tolerance.STRICT) { + return bytes32(self.strictStalenessTolerance); + } else if (tolerance == Tolerance.ONE_MONTH) { + return bytes32(ONE_MONTH); + } else { + return bytes32(0); + } + } } diff --git a/markets/spot-market/test/WrapperModule.test.ts b/markets/spot-market/test/WrapperModule.test.ts index 08d4df3356..d9e89d93c0 100644 --- a/markets/spot-market/test/WrapperModule.test.ts +++ b/markets/spot-market/test/WrapperModule.test.ts @@ -11,7 +11,7 @@ import { formatBytes32String } from 'ethers/lib/utils'; const bn = (n: number) => wei(n).toBN(); describe('WrapperModule', () => { - const { systems, signers, marketId } = bootstrapTraders( + const { systems, signers, marketId, aggregator } = bootstrapTraders( bootstrapWithSynth('Synthetic Ether', 'snxETH') ); @@ -367,4 +367,48 @@ describe('WrapperModule', () => { ); }); }); + + describe('ensure it uses monthly price tolerance', () => { + const BASE_PRICE = bn(900); + beforeEach('reset monthly price', async () => { + await aggregator().mockSetMonthlyTolerancePrice(BASE_PRICE); + }); + + it('trader1 wraps 1 eth', async () => { + await systems().CollateralMock.connect(trader1).approve(systems().SpotMarket.address, bn(1)); + + const original = await systems() + .SpotMarket.connect(trader1) + .callStatic.wrap(marketId(), bn(1), 0); + await aggregator().mockSetMonthlyTolerancePrice(BASE_PRICE.mul(2)); + const tx = await systems().SpotMarket.connect(trader1).callStatic.wrap(marketId(), bn(1), 0); + + assertBn.equal(tx.fees.wrapperFees, original.fees.wrapperFees.mul(2)); + }); + + it('trader1 unwraps 0.5 eth', async () => { + await synth.approve(systems().SpotMarket.address, bn(0.5)); + + const original = await systems() + .SpotMarket.connect(trader1) + .callStatic.unwrap(marketId(), bn(0.5), 0); + await aggregator().mockSetMonthlyTolerancePrice(BASE_PRICE.div(2)); + const tx = await systems() + .SpotMarket.connect(trader1) + .callStatic.unwrap(marketId(), bn(0.5), 0); + assertBn.equal(tx.fees.wrapperFees, original.fees.wrapperFees.div(2)); + }); + + it('strict price should have no effect', async () => { + await systems().CollateralMock.connect(trader1).approve(systems().SpotMarket.address, bn(1)); + + const original = await systems() + .SpotMarket.connect(trader1) + .callStatic.wrap(marketId(), bn(1), 0); + await aggregator().mockSetCurrentPrice(BASE_PRICE.mul(10)); + const tx = await systems().SpotMarket.connect(trader1).callStatic.wrap(marketId(), bn(1), 0); + + assertBn.equal(tx.fees.wrapperFees, original.fees.wrapperFees); + }); + }); });