diff --git a/README.md b/README.md index d966289..8f3bb04 100644 --- a/README.md +++ b/README.md @@ -18,7 +18,7 @@ PoolTogether V5 uses the CGDA liquidator to sell yield for POOL tokens and contr ## LiquidationPair -The LiquidationPair sells one token for another using a periodic continuous gradual dutch auction. The pair does not hold liquidity, but rather prices liquidity held by a ILiquidationSource. The Liquidation Source makes liquidity available to the pair, which facilitates swaps. +The LiquidationPair sells one token for another using a periodic continuous gradual dutch auction. The pair does not hold liquidity, but rather prices liquidity held by a ILiquidationSource. The Liquidation Source makes liquidity available to the pair, which facilitates swaps. A continuous gradual dutch auction is an algorithm that: @@ -30,7 +30,7 @@ What you get, in a sense, is that a CGDA auction will drop the price until purch For more information read the origina Paradigm article on [Gradual Dutch Auctions](https://www.paradigm.xyz/2022/04/gda). -The LiquidationPair is *periodic*, in the sense that it runs a sequence of CGDAs. At the start of each auction period, the LiquidationPair will adjust the target price and emissions rate so that the available liquidity can be sold as efficiently as possible. +The LiquidationPair is _periodic_, in the sense that it runs a sequence of CGDAs. At the start of each auction period, the LiquidationPair will adjust the target price and emissions rate so that the available liquidity can be sold as efficiently as possible. Have questions or want the latest news?
Join the PoolTogether Discord or follow us on Twitter: @@ -54,7 +54,6 @@ Install dependencies: npm i ``` - ## Derivations ### Price Function @@ -94,4 +93,3 @@ $$ p = \frac{k}{l}\cdot\frac{\left(e^{\frac{lq}{r}}-1\right)}{e^{lt}} $$ $$ l\cdot p\cdot e^{lt} = k\cdot\left(e^{\frac{lq}{r}}-1\right) $$ $$ \frac{l\cdot p\cdot e^{lt}}{(e^{\frac{lq}{r}}-1)} = k $$ - diff --git a/src/LiquidationPairFactory.sol b/src/LiquidationPairFactory.sol index 082a601..14224d8 100644 --- a/src/LiquidationPairFactory.sol +++ b/src/LiquidationPairFactory.sol @@ -8,7 +8,6 @@ import { ILiquidationSource, LiquidationPair, SD59x18 } from "./LiquidationPair. /// @author G9 Software Inc. /// @notice Factory contract for deploying LiquidationPair contracts. contract LiquidationPairFactory { - /* ============ Events ============ */ /// @notice Emitted when a new LiquidationPair is created diff --git a/src/LiquidationRouter.sol b/src/LiquidationRouter.sol index 195a003..ded7e78 100644 --- a/src/LiquidationRouter.sol +++ b/src/LiquidationRouter.sol @@ -57,7 +57,7 @@ contract LiquidationRouter is IFlashSwapCallback { /// @notice Constructs a new LiquidationRouter /// @param liquidationPairFactory_ The factory that pairs will be verified to have been created by constructor(LiquidationPairFactory liquidationPairFactory_) { - if(address(liquidationPairFactory_) == address(0)) { + if (address(liquidationPairFactory_) == address(0)) { revert UndefinedLiquidationPairFactory(); } _liquidationPairFactory = liquidationPairFactory_; @@ -85,11 +85,24 @@ contract LiquidationRouter is IFlashSwapCallback { revert SwapExpired(_deadline); } - uint256 amountIn = _liquidationPair.swapExactAmountOut(address(this), _amountOut, _amountInMax, abi.encode(msg.sender)); + uint256 amountIn = _liquidationPair.swapExactAmountOut( + address(this), + _amountOut, + _amountInMax, + abi.encode(msg.sender) + ); IERC20(_liquidationPair.tokenOut()).safeTransfer(_receiver, _amountOut); - emit SwappedExactAmountOut(_liquidationPair, msg.sender, _receiver, _amountOut, _amountInMax, amountIn, _deadline); + emit SwappedExactAmountOut( + _liquidationPair, + msg.sender, + _receiver, + _amountOut, + _amountInMax, + amountIn, + _deadline + ); return amountIn; } @@ -100,7 +113,7 @@ contract LiquidationRouter is IFlashSwapCallback { uint256 _amountIn, uint256, bytes calldata _flashSwapData - ) external onlyTrustedLiquidationPair(LiquidationPair(msg.sender)) onlySelf(_sender) override { + ) external override onlyTrustedLiquidationPair(LiquidationPair(msg.sender)) onlySelf(_sender) { address _originalSender = abi.decode(_flashSwapData, (address)); IERC20(LiquidationPair(msg.sender).tokenIn()).safeTransferFrom( _originalSender, diff --git a/src/libraries/ContinuousGDA.sol b/src/libraries/ContinuousGDA.sol index f985771..61a1828 100644 --- a/src/libraries/ContinuousGDA.sol +++ b/src/libraries/ContinuousGDA.sol @@ -55,7 +55,9 @@ library ContinuousGDA { SD59x18 _price ) internal pure returns (SD59x18) { SD59x18 topE = _decayConstant.mul(_targetFirstSaleTime).safeExp(); - SD59x18 denominator = (_decayConstant.mul(_purchaseAmount).div(_emissionRate)).safeExp().sub(ONE); + SD59x18 denominator = (_decayConstant.mul(_purchaseAmount).div(_emissionRate)).safeExp().sub( + ONE + ); SD59x18 result = topE.div(denominator); SD59x18 multiplier = _decayConstant.mul(_price); return result.mul(multiplier); diff --git a/src/libraries/SafeSD59x18.sol b/src/libraries/SafeSD59x18.sol index 756ed16..cfd8bc7 100644 --- a/src/libraries/SafeSD59x18.sol +++ b/src/libraries/SafeSD59x18.sol @@ -4,12 +4,10 @@ pragma solidity 0.8.19; import { SD59x18, wrap } from "prb-math/SD59x18.sol"; library SafeSD59x18 { - - function safeExp(SD59x18 x) internal pure returns (SD59x18) { - if (x.unwrap() < -41.45e18) { - return wrap(0); - } - return x.exp(); + function safeExp(SD59x18 x) internal pure returns (SD59x18) { + if (x.unwrap() < -41.45e18) { + return wrap(0); } - -} \ No newline at end of file + return x.exp(); + } +} diff --git a/test/LiquidationPairFactory.t.sol b/test/LiquidationPairFactory.t.sol index 7f5cd6f..2d09981 100644 --- a/test/LiquidationPairFactory.t.sol +++ b/test/LiquidationPairFactory.t.sol @@ -56,8 +56,6 @@ contract LiquidationPairFactoryTest is Test { /* ============ createPair ============ */ function testCreatePair() public { - - vm.expectEmit(false, false, false, true); emit PairCreated( LiquidationPair(0x0000000000000000000000000000000000000000), @@ -114,5 +112,4 @@ contract LiquidationPairFactoryTest is Test { abi.encode(amount) ); } - } diff --git a/test/LiquidationRouter.t.sol b/test/LiquidationRouter.t.sol index c13e5b2..420e6ce 100644 --- a/test/LiquidationRouter.t.sol +++ b/test/LiquidationRouter.t.sol @@ -9,146 +9,158 @@ import { SafeERC20 } from "openzeppelin/token/ERC20/utils/SafeERC20.sol"; import { LiquidationPairFactory } from "../src/LiquidationPairFactory.sol"; import { LiquidationPair } from "../src/LiquidationPair.sol"; -import { - UnknownLiquidationPair, - UndefinedLiquidationPairFactory, - SwapExpired, - InvalidSender, - LiquidationRouter -} from "../src/LiquidationRouter.sol"; +import { UnknownLiquidationPair, UndefinedLiquidationPairFactory, SwapExpired, InvalidSender, LiquidationRouter } from "../src/LiquidationRouter.sol"; contract LiquidationRouterTest is Test { - using SafeERC20 for IERC20; - - IERC20 tokenIn; - IERC20 tokenOut; - address target; - LiquidationPairFactory factory; - LiquidationPair liquidationPair; - - LiquidationRouter router; - - event LiquidationRouterCreated(LiquidationPairFactory indexed liquidationPairFactory); - event SwappedExactAmountOut( - LiquidationPair indexed liquidationPair, - address indexed sender, - address indexed receiver, - uint256 amountOut, - uint256 amountInMax, - uint256 amountIn, - uint256 deadline + using SafeERC20 for IERC20; + + IERC20 tokenIn; + IERC20 tokenOut; + address target; + LiquidationPairFactory factory; + LiquidationPair liquidationPair; + + LiquidationRouter router; + + event LiquidationRouterCreated(LiquidationPairFactory indexed liquidationPairFactory); + event SwappedExactAmountOut( + LiquidationPair indexed liquidationPair, + address indexed sender, + address indexed receiver, + uint256 amountOut, + uint256 amountInMax, + uint256 amountIn, + uint256 deadline + ); + + function setUp() public { + factory = LiquidationPairFactory(makeAddr("LiquidationPairFactory")); + vm.etch(address(factory), "LiquidationPairFactory"); + + tokenIn = IERC20(makeAddr("tokenIn")); + vm.etch(address(tokenIn), "tokenIn"); + tokenOut = IERC20(makeAddr("tokenOut")); + vm.etch(address(tokenOut), "tokenOut"); + + liquidationPair = LiquidationPair(makeAddr("LiquidationPair")); + vm.etch(address(liquidationPair), "LiquidationPair"); + + vm.mockCall( + address(liquidationPair), + abi.encodeCall(liquidationPair.tokenIn, ()), + abi.encode(tokenIn) + ); + vm.mockCall( + address(liquidationPair), + abi.encodeCall(liquidationPair.tokenOut, ()), + abi.encode(tokenOut) + ); + vm.mockCall( + address(liquidationPair), + abi.encodeCall(liquidationPair.target, ()), + abi.encode(target) + ); + vm.mockCall( + address(factory), + abi.encodeCall(factory.deployedPairs, liquidationPair), + abi.encode(true) + ); + + router = new LiquidationRouter(factory); + } + + function testConstructor_badFactory() public { + vm.expectRevert(abi.encodeWithSelector(UndefinedLiquidationPairFactory.selector)); + new LiquidationRouter(LiquidationPairFactory(address(0))); + } + + function testSwapExactAmountOut_happyPath() public { + vm.warp(10 days); + address receiver = makeAddr("bob"); + uint256 amountOut = 1e18; + uint256 amountIn = 1.5e18; + uint256 amountInMax = 2e18; + uint256 deadline = block.timestamp; + + vm.mockCall( + address(liquidationPair), + abi.encodeCall(liquidationPair.computeExactAmountIn, (amountOut)), + abi.encode(amountIn) + ); + vm.mockCall( + address(tokenIn), + abi.encodeCall(tokenIn.transferFrom, (address(this), target, amountIn)), + abi.encode(true) + ); + vm.mockCall( + address(tokenOut), + abi.encodeCall(tokenOut.transfer, (address(this), amountOut)), + abi.encode(true) + ); + vm.mockCall( + address(liquidationPair), + abi.encodeCall( + liquidationPair.swapExactAmountOut, + (address(router), amountOut, amountInMax, abi.encode(address(this))) + ), + abi.encode(amountIn) ); - function setUp() public { - factory = LiquidationPairFactory(makeAddr("LiquidationPairFactory")); - vm.etch(address(factory), "LiquidationPairFactory"); - - tokenIn = IERC20(makeAddr("tokenIn")); - vm.etch(address(tokenIn), "tokenIn"); - tokenOut = IERC20(makeAddr("tokenOut")); - vm.etch(address(tokenOut), "tokenOut"); - - liquidationPair = LiquidationPair(makeAddr("LiquidationPair")); - vm.etch(address(liquidationPair), "LiquidationPair"); - - vm.mockCall(address(liquidationPair), abi.encodeCall(liquidationPair.tokenIn, ()), abi.encode(tokenIn)); - vm.mockCall(address(liquidationPair), abi.encodeCall(liquidationPair.tokenOut, ()), abi.encode(tokenOut)); - vm.mockCall(address(liquidationPair), abi.encodeCall(liquidationPair.target, ()), abi.encode(target)); - vm.mockCall(address(factory), abi.encodeCall(factory.deployedPairs, liquidationPair), abi.encode(true)); - - router = new LiquidationRouter(factory); - } - - function testConstructor_badFactory() public { - vm.expectRevert(abi.encodeWithSelector(UndefinedLiquidationPairFactory.selector)); - new LiquidationRouter(LiquidationPairFactory(address(0))); - } - - function testSwapExactAmountOut_happyPath() public { - vm.warp(10 days); - address receiver = makeAddr("bob"); - uint256 amountOut = 1e18; - uint256 amountIn = 1.5e18; - uint256 amountInMax = 2e18; - uint256 deadline = block.timestamp; - - vm.mockCall( - address(liquidationPair), - abi.encodeCall(liquidationPair.computeExactAmountIn, (amountOut)), - abi.encode(amountIn) - ); - vm.mockCall( - address(tokenIn), - abi.encodeCall(tokenIn.transferFrom, (address(this), target, amountIn)), - abi.encode(true) - ); - vm.mockCall( - address(tokenOut), - abi.encodeCall(tokenOut.transfer, (address(this), amountOut)), - abi.encode(true) - ); - vm.mockCall( - address(liquidationPair), - abi.encodeCall(liquidationPair.swapExactAmountOut, (address(router), amountOut, amountInMax, abi.encode(address(this)))), - abi.encode(amountIn) - ); - - vm.expectEmit(true, true, false, true); - emit SwappedExactAmountOut( - liquidationPair, - address(this), - receiver, - amountOut, - amountInMax, - amountIn, - deadline - ); - - router.swapExactAmountOut( - liquidationPair, - receiver, - amountOut, - amountInMax, - deadline - ); - } - - function testFlashSwapCallback_InvalidSender() public { - vm.expectRevert(abi.encodeWithSelector(InvalidSender.selector, address(this))); - vm.startPrank(address(liquidationPair)); - router.flashSwapCallback(address(this), 0, 0, abi.encode(address(this))); - vm.stopPrank(); - } - - function testFlashSwapCallback_UnknownLiquidationPair() public { - vm.mockCall(address(factory), abi.encodeCall(factory.deployedPairs, LiquidationPair(address(this))), abi.encode(false)); - vm.expectRevert(abi.encodeWithSelector(UnknownLiquidationPair.selector, address(this))); - router.flashSwapCallback(address(this), 0, 0, abi.encode(address(this))); - } - - function testFlashSwapCallback_success() public { - vm.mockCall(address(tokenIn), abi.encodeCall(tokenIn.transferFrom, (address(this), target, 11e18)), abi.encode(true)); - vm.startPrank(address(liquidationPair)); - router.flashSwapCallback(address(router), 11e18, 0, abi.encode(address(this))); - vm.stopPrank(); - } - - function testSwapExactAmountOut_SwapExpired() public { - vm.warp(10 days); - vm.expectRevert(abi.encodeWithSelector(SwapExpired.selector, 10 days - 1)); - router.swapExactAmountOut(liquidationPair, makeAddr("alice"), 1e18, 1e18, 10 days - 1); - } - - function testSwapExactAmountOut_UnknownLiquidationPair() public { - vm.mockCall(address(factory), abi.encodeCall(factory.deployedPairs, liquidationPair), abi.encode(false)); - vm.expectRevert(abi.encodeWithSelector(UnknownLiquidationPair.selector, liquidationPair)); - router.swapExactAmountOut( - liquidationPair, - address(this), - 1e18, - 2e18, - block.timestamp - ); - } + vm.expectEmit(true, true, false, true); + emit SwappedExactAmountOut( + liquidationPair, + address(this), + receiver, + amountOut, + amountInMax, + amountIn, + deadline + ); + router.swapExactAmountOut(liquidationPair, receiver, amountOut, amountInMax, deadline); + } + + function testFlashSwapCallback_InvalidSender() public { + vm.expectRevert(abi.encodeWithSelector(InvalidSender.selector, address(this))); + vm.startPrank(address(liquidationPair)); + router.flashSwapCallback(address(this), 0, 0, abi.encode(address(this))); + vm.stopPrank(); + } + + function testFlashSwapCallback_UnknownLiquidationPair() public { + vm.mockCall( + address(factory), + abi.encodeCall(factory.deployedPairs, LiquidationPair(address(this))), + abi.encode(false) + ); + vm.expectRevert(abi.encodeWithSelector(UnknownLiquidationPair.selector, address(this))); + router.flashSwapCallback(address(this), 0, 0, abi.encode(address(this))); + } + + function testFlashSwapCallback_success() public { + vm.mockCall( + address(tokenIn), + abi.encodeCall(tokenIn.transferFrom, (address(this), target, 11e18)), + abi.encode(true) + ); + vm.startPrank(address(liquidationPair)); + router.flashSwapCallback(address(router), 11e18, 0, abi.encode(address(this))); + vm.stopPrank(); + } + + function testSwapExactAmountOut_SwapExpired() public { + vm.warp(10 days); + vm.expectRevert(abi.encodeWithSelector(SwapExpired.selector, 10 days - 1)); + router.swapExactAmountOut(liquidationPair, makeAddr("alice"), 1e18, 1e18, 10 days - 1); + } + + function testSwapExactAmountOut_UnknownLiquidationPair() public { + vm.mockCall( + address(factory), + abi.encodeCall(factory.deployedPairs, liquidationPair), + abi.encode(false) + ); + vm.expectRevert(abi.encodeWithSelector(UnknownLiquidationPair.selector, liquidationPair)); + router.swapExactAmountOut(liquidationPair, address(this), 1e18, 2e18, block.timestamp); + } } diff --git a/test/fuzz/ContinuousGDAFuzz.t.sol b/test/fuzz/ContinuousGDAFuzz.t.sol index 862ca47..ebb459e 100644 --- a/test/fuzz/ContinuousGDAFuzz.t.sol +++ b/test/fuzz/ContinuousGDAFuzz.t.sol @@ -8,62 +8,52 @@ import { SD59x18, convert, wrap, unwrap } from "prb-math/SD59x18.sol"; import { ContinuousGDA } from "../../src/libraries/ContinuousGDA.sol"; contract ContinuousGDAFuzzTest is Test { + SD59x18 decayConstant = wrap(0.001e18); + SD59x18 auctionDuration = convert(1 days); + SD59x18 targetFirstSaleTime = convert(12 hours); - SD59x18 decayConstant = wrap(0.001e18); - SD59x18 auctionDuration = convert(1 days); - SD59x18 targetFirstSaleTime = convert(12 hours); - - /** + /** Fuzz exchange rates. POOL:USDC = 1e18:1e6 = 1e12 */ - function testComputeK_fuzzExchangeRate( - uint96 exchangeRate // amountIn:amountOut, 2**96 = exchange rates up to 1e28 - ) public { - vm.assume(exchangeRate > 0); - SD59x18 auctionSizeAmountIn = convert(1e12*1e18); // 1 trillion amount in "amount in" tokens - SD59x18 exchangeRateAmountInToAmountOut = convert(int(uint(exchangeRate))); - SD59x18 auctionAmount = auctionSizeAmountIn.div(exchangeRateAmountInToAmountOut); - SD59x18 emissionRate = auctionAmount.div(auctionDuration); - SD59x18 purchaseAmount = targetFirstSaleTime.mul(emissionRate); - SD59x18 price = exchangeRateAmountInToAmountOut.mul(purchaseAmount); - assertNotEq( - ContinuousGDA.computeK( - emissionRate, - decayConstant, - targetFirstSaleTime, - purchaseAmount, - price - ).unwrap(), - 0 - ); - } - - function testComputeK_fuzzAuctionAmount( - uint56 auctionAmount // For USDC at 1e6, this caps at ~72 billion - ) public { - vm.assume(auctionAmount > 0); - SD59x18 exchangeRateAmountInToAmountOut = convert(1e12); // Pretend it's USDC - // SD59x18 auctionAmount = auctionSizeAmountIn.div(exchangeRateAmountInToAmountOut); - SD59x18 emissionRate = convert(int(uint(auctionAmount))).div(auctionDuration); - SD59x18 purchaseAmount = targetFirstSaleTime.mul(emissionRate); - SD59x18 price = exchangeRateAmountInToAmountOut.mul(purchaseAmount); - assertNotEq( - ContinuousGDA.computeK( - emissionRate, - decayConstant, - targetFirstSaleTime, - purchaseAmount, - price - ).unwrap(), - 0 - ); - } - - function testUsdc() public { - // 1e18/1e6 => 1e12 is the exchange rate - testComputeK_fuzzExchangeRate(1e12); - } + function testComputeK_fuzzExchangeRate( + uint96 exchangeRate // amountIn:amountOut, 2**96 = exchange rates up to 1e28 + ) public { + vm.assume(exchangeRate > 0); + SD59x18 auctionSizeAmountIn = convert(1e12 * 1e18); // 1 trillion amount in "amount in" tokens + SD59x18 exchangeRateAmountInToAmountOut = convert(int(uint(exchangeRate))); + SD59x18 auctionAmount = auctionSizeAmountIn.div(exchangeRateAmountInToAmountOut); + SD59x18 emissionRate = auctionAmount.div(auctionDuration); + SD59x18 purchaseAmount = targetFirstSaleTime.mul(emissionRate); + SD59x18 price = exchangeRateAmountInToAmountOut.mul(purchaseAmount); + assertNotEq( + ContinuousGDA + .computeK(emissionRate, decayConstant, targetFirstSaleTime, purchaseAmount, price) + .unwrap(), + 0 + ); + } + + function testComputeK_fuzzAuctionAmount( + uint56 auctionAmount // For USDC at 1e6, this caps at ~72 billion + ) public { + vm.assume(auctionAmount > 0); + SD59x18 exchangeRateAmountInToAmountOut = convert(1e12); // Pretend it's USDC + // SD59x18 auctionAmount = auctionSizeAmountIn.div(exchangeRateAmountInToAmountOut); + SD59x18 emissionRate = convert(int(uint(auctionAmount))).div(auctionDuration); + SD59x18 purchaseAmount = targetFirstSaleTime.mul(emissionRate); + SD59x18 price = exchangeRateAmountInToAmountOut.mul(purchaseAmount); + assertNotEq( + ContinuousGDA + .computeK(emissionRate, decayConstant, targetFirstSaleTime, purchaseAmount, price) + .unwrap(), + 0 + ); + } + + function testUsdc() public { + // 1e18/1e6 => 1e12 is the exchange rate + testComputeK_fuzzExchangeRate(1e12); + } } - diff --git a/test/fuzz/LiquidationPairFuzz.t.sol b/test/fuzz/LiquidationPairFuzz.t.sol index e0f656c..672a114 100644 --- a/test/fuzz/LiquidationPairFuzz.t.sol +++ b/test/fuzz/LiquidationPairFuzz.t.sol @@ -8,54 +8,72 @@ import { SD59x18, convert, wrap, unwrap } from "prb-math/SD59x18.sol"; import { LiquidationPair, ILiquidationSource } from "../../src/LiquidationPair.sol"; contract LiquidationPairFuzzTest is Test { + LiquidationPair pair; - LiquidationPair pair; - - ILiquidationSource source; - address tokenIn; - address tokenOut; - - uint32 periodLength = 1 days; - uint32 firstPeriodStartsAt = 10 days; - uint32 targetFirstSaleTime = periodLength / 2; - SD59x18 decayConstant = wrap(0.001e18); - uint104 initialAmountIn = 1e18; - uint104 initialAmountOut = 1e18; - uint256 minimumAuctionAmount = 2e18; - - function setUp() public { - vm.warp(firstPeriodStartsAt); - source = ILiquidationSource(makeAddr("ILiquidationSource")); - // always have 1000 available - tokenIn = makeAddr("tokenIn"); - tokenOut = makeAddr("tokenOut"); - vm.mockCall(address(source), abi.encodeWithSelector(source.liquidatableBalanceOf.selector, tokenOut), abi.encode(1000e18)); - pair = new LiquidationPair( - source, - tokenIn, - tokenOut, - periodLength, - firstPeriodStartsAt, - targetFirstSaleTime, - decayConstant, - initialAmountIn, - initialAmountOut, - minimumAuctionAmount - ); - } + ILiquidationSource source; + address tokenIn; + address tokenOut; + + uint32 periodLength = 1 days; + uint32 firstPeriodStartsAt = 10 days; + uint32 targetFirstSaleTime = periodLength / 2; + SD59x18 decayConstant = wrap(0.001e18); + uint104 initialAmountIn = 1e18; + uint104 initialAmountOut = 1e18; + uint256 minimumAuctionAmount = 2e18; + + function setUp() public { + vm.warp(firstPeriodStartsAt); + source = ILiquidationSource(makeAddr("ILiquidationSource")); + // always have 1000 available + tokenIn = makeAddr("tokenIn"); + tokenOut = makeAddr("tokenOut"); + vm.mockCall( + address(source), + abi.encodeWithSelector(source.liquidatableBalanceOf.selector, tokenOut), + abi.encode(1000e18) + ); + pair = new LiquidationPair( + source, + tokenIn, + tokenOut, + periodLength, + firstPeriodStartsAt, + targetFirstSaleTime, + decayConstant, + initialAmountIn, + initialAmountOut, + minimumAuctionAmount + ); + } - function testSwapMaxAmountOut(uint104 liquidity, uint32 waitingTime) public { - vm.mockCall(address(source), abi.encodeWithSelector(source.liquidatableBalanceOf.selector, tokenOut), abi.encode(liquidity)); - - vm.warp(uint(firstPeriodStartsAt) + waitingTime); - uint amountOut = pair.maxAmountOut(); - if (amountOut > 0) { - uint amountIn = pair.computeExactAmountIn(amountOut); - if (amountIn > 0) { - vm.mockCall(address(source), abi.encodeCall(source.transferTokensOut, (address(this), address(this), tokenOut, amountOut)), abi.encode("somedata")); - vm.mockCall(address(source), abi.encodeCall(source.verifyTokensIn, (tokenIn, amountIn, "somedata")), abi.encode()); - pair.swapExactAmountOut(address(this), amountOut, amountIn, ""); - } - } + function testSwapMaxAmountOut(uint104 liquidity, uint32 waitingTime) public { + vm.mockCall( + address(source), + abi.encodeWithSelector(source.liquidatableBalanceOf.selector, tokenOut), + abi.encode(liquidity) + ); + + vm.warp(uint(firstPeriodStartsAt) + waitingTime); + uint amountOut = pair.maxAmountOut(); + if (amountOut > 0) { + uint amountIn = pair.computeExactAmountIn(amountOut); + if (amountIn > 0) { + vm.mockCall( + address(source), + abi.encodeCall( + source.transferTokensOut, + (address(this), address(this), tokenOut, amountOut) + ), + abi.encode("somedata") + ); + vm.mockCall( + address(source), + abi.encodeCall(source.verifyTokensIn, (tokenIn, amountIn, "somedata")), + abi.encode() + ); + pair.swapExactAmountOut(address(this), amountOut, amountIn, ""); + } } + } } diff --git a/test/libraries/ContinuousGDA.t.sol b/test/libraries/ContinuousGDA.t.sol index 121042d..a544a69 100644 --- a/test/libraries/ContinuousGDA.t.sol +++ b/test/libraries/ContinuousGDA.t.sol @@ -8,7 +8,6 @@ import { ContinuousGDA } from "../../src/libraries/ContinuousGDA.sol"; import { ContinuousGDAWrapper } from "./wrapper/ContinuousGDAWrapper.sol"; contract ContinuousGDATest is Test { - ContinuousGDAWrapper wrapper; function setUp() public { @@ -39,17 +38,17 @@ contract ContinuousGDATest is Test { SD59x18 decayConstant = wrap(0.001e18); SD59x18 elapsed = convert(12 hours); SD59x18 purchaseAmount = elapsed.mul(emissionRate); - SD59x18 initialPrice = wrapper.computeK(emissionRate, decayConstant, elapsed, purchaseAmount, purchaseAmount); + SD59x18 initialPrice = wrapper.computeK( + emissionRate, + decayConstant, + elapsed, + purchaseAmount, + purchaseAmount + ); SD59x18 amount = convert(1).mul(emissionRate).ceil(); console2.log("testPurchasePrice_minimum amount:", amount.unwrap()); assertApproxEqAbs( - wrapper.purchasePrice( - amount, - emissionRate, - initialPrice, - decayConstant, - convert(1) - ).unwrap(), + wrapper.purchasePrice(amount, emissionRate, initialPrice, decayConstant, convert(1)).unwrap(), 499750083312503833111582646342332181, 1e21 ); @@ -62,26 +61,16 @@ contract ContinuousGDATest is Test { SD59x18 initialPrice = convert(55); assertEq( - wrapper.purchasePrice( - convert(500), - emissionRate, - initialPrice, - decayConstant, - elapsed - ).unwrap(), + wrapper + .purchasePrice(convert(500), emissionRate, initialPrice, decayConstant, elapsed) + .unwrap(), 35644008052508359911616 ); } function testPurchasePrice_zero() public { assertApproxEqAbs( - wrapper.purchasePrice( - wrap(0), - convert(1), - convert(55), - wrap(0.001e18), - convert(1) - ).unwrap(), + wrapper.purchasePrice(wrap(0), convert(1), convert(55), wrap(0.001e18), convert(1)).unwrap(), 0, 1 ); @@ -93,7 +82,12 @@ contract ContinuousGDATest is Test { SD59x18 decayConstant = wrap(0.0005e18); SD59x18 targetTime = convert(100); - SD59x18 auctionStartingPrice = computeK(emissionRate, decayConstant, targetTime, exchangeRateAmountOutToAmountIn); + SD59x18 auctionStartingPrice = computeK( + emissionRate, + decayConstant, + targetTime, + exchangeRateAmountOutToAmountIn + ); SD59x18 amountOut = targetTime.mul(emissionRate); // console2.log("purchase price for", amountOut); SD59x18 amountIn = ContinuousGDA.purchasePrice( @@ -104,7 +98,11 @@ contract ContinuousGDATest is Test { targetTime ); - assertApproxEqAbs(amountIn.unwrap(), amountOut.div(exchangeRateAmountOutToAmountIn).unwrap(), 100); + assertApproxEqAbs( + amountIn.unwrap(), + amountOut.div(exchangeRateAmountOutToAmountIn).unwrap(), + 100 + ); } function testComputeK_overflow_regressionTest() public pure { @@ -129,13 +127,7 @@ contract ContinuousGDATest is Test { // console2.log("COMPUTE_K purchasedAmountOut", uint(convert(purchasedAmountOut))); // console2.log("COMPUTE_K priceAmountIn", uint(convert(priceAmountIn))); - return wrapper.computeK( - emissionRate, - decayConstant, - targetTime, - purchasedAmountOut, - priceAmountIn - ); + return + wrapper.computeK(emissionRate, decayConstant, targetTime, purchasedAmountOut, priceAmountIn); } - } diff --git a/test/libraries/SafeSD59x18.t.sol b/test/libraries/SafeSD59x18.t.sol index 6e10284..56c9938 100644 --- a/test/libraries/SafeSD59x18.t.sol +++ b/test/libraries/SafeSD59x18.t.sol @@ -7,19 +7,18 @@ import { SafeSD59x18, SD59x18, wrap } from "../../src/libraries/SafeSD59x18.sol" import { SafeSD59x18Wrapper } from "./wrapper/SafeSD59x18Wrapper.sol"; contract SafeSD59x18Test is Test { + SafeSD59x18Wrapper wrapper; - SafeSD59x18Wrapper wrapper; + function setUp() public { + wrapper = new SafeSD59x18Wrapper(); + } - function setUp() public { - wrapper = new SafeSD59x18Wrapper(); - } + function testUnsafeNum() public { + assertEq(wrapper.safeExp(wrap(-41.45e18)).unwrap(), 0); + assertEq(wrapper.safeExp(wrap(-100e18)).unwrap(), 0); + } - function testUnsafeNum() public { - assertEq(wrapper.safeExp(wrap(-41.45e18)).unwrap(), 0); - assertEq(wrapper.safeExp(wrap(-100e18)).unwrap(), 0); - } - - function testSafeNum() public { - assertEq(wrapper.safeExp(wrap(0)).unwrap(), 1e18); - } + function testSafeNum() public { + assertEq(wrapper.safeExp(wrap(0)).unwrap(), 1e18); + } } diff --git a/test/libraries/wrapper/ContinuousGDAWrapper.sol b/test/libraries/wrapper/ContinuousGDAWrapper.sol index f80e7cd..0f85f5f 100644 --- a/test/libraries/wrapper/ContinuousGDAWrapper.sol +++ b/test/libraries/wrapper/ContinuousGDAWrapper.sol @@ -5,7 +5,6 @@ import { ContinuousGDA, SD59x18 } from "../../../src/libraries/ContinuousGDA.sol // NOTE: taken from https://github.com/FrankieIsLost/gradual-dutch-auction/blob/master/src/ContinuousGDA.sol contract ContinuousGDAWrapper { - ///@notice calculate price to purchased _numTokens using exponential continuous GDA formula function purchasePrice( SD59x18 _amount, @@ -19,7 +18,8 @@ contract ContinuousGDAWrapper { _emissionRate, _initialPrice, _decayConstant, - _timeSinceLastAuctionStart); + _timeSinceLastAuctionStart + ); return result; } diff --git a/test/libraries/wrapper/SafeSD59x18Wrapper.sol b/test/libraries/wrapper/SafeSD59x18Wrapper.sol index 9b97bba..7aaef95 100644 --- a/test/libraries/wrapper/SafeSD59x18Wrapper.sol +++ b/test/libraries/wrapper/SafeSD59x18Wrapper.sol @@ -4,10 +4,8 @@ pragma solidity 0.8.19; import { SafeSD59x18, SD59x18 } from "../../../src/libraries/SafeSD59x18.sol"; contract SafeSD59x18Wrapper { - - function safeExp(SD59x18 x) external pure returns (SD59x18) { - SD59x18 result = SafeSD59x18.safeExp(x); - return result; - } - + function safeExp(SD59x18 x) external pure returns (SD59x18) { + SD59x18 result = SafeSD59x18.safeExp(x); + return result; + } }