From 72a9c2d854766ec8085ab03936fe4eaace5f1e75 Mon Sep 17 00:00:00 2001 From: Taylor Brent Date: Tue, 10 Sep 2024 09:42:32 -0400 Subject: [PATCH] prevent batch auction cancellations during final 10% of auction (#1203) --- contracts/plugins/trading/GnosisTrade.sol | 10 +++++- test/integration/EasyAuction.test.ts | 41 +++++++++++++++++++++++ 2 files changed, 50 insertions(+), 1 deletion(-) diff --git a/contracts/plugins/trading/GnosisTrade.sol b/contracts/plugins/trading/GnosisTrade.sol index 794f5ff2b6..082287d917 100644 --- a/contracts/plugins/trading/GnosisTrade.sol +++ b/contracts/plugins/trading/GnosisTrade.sol @@ -24,6 +24,9 @@ contract GnosisTrade is ITrade, Versioned { TradeKind public constant KIND = TradeKind.BATCH_AUCTION; uint256 public constant FEE_DENOMINATOR = 1000; + // Can only cancel order in first 90% of the auction + uint192 public constant CANCEL_WINDOW = 9e17; // {1} first 90% of auction + // Upper bound for the max number of orders we're happy to have the auction clear in; // When we have good price information, this determines the minimum buy amount per order. uint96 public constant MAX_ORDERS = 5000; // bounded to avoid going beyond block gas limit @@ -141,10 +144,15 @@ contract GnosisTrade is ITrade, Versioned { // amount is > 0 and < type(uint256).max. AllowanceLib.safeApproveFallbackToMax(address(sell), address(gnosis), req.sellAmount); + // Can only cancel within the CANCEL_WINDOW + uint48 cancellationEndTime = uint48( + block.timestamp + (batchAuctionLength * CANCEL_WINDOW) / FIX_ONE + ); + auctionId = gnosis.initiateAuction( sell, buy, - endTime, + cancellationEndTime, endTime, _sellAmount, minBuyAmount, diff --git a/test/integration/EasyAuction.test.ts b/test/integration/EasyAuction.test.ts index 0a352269bc..92520911b4 100644 --- a/test/integration/EasyAuction.test.ts +++ b/test/integration/EasyAuction.test.ts @@ -363,6 +363,47 @@ describeFork(`Gnosis EasyAuction Mainnet Forking - P${IMPLEMENTATION}`, function expect(await rsr.balanceOf(backingManager.address)).to.equal(0) }) + it('cannot cancel in last 10% of auction', async () => { + // Place 2 orders + const bidAmt = buyAmt.add(1) + await token0.connect(addr1).approve(easyAuction.address, bidAmt.mul(3)) + await easyAuction + .connect(addr1) + .placeSellOrders(auctionId, [sellAmt], [bidAmt], [QUEUE_START], ethers.constants.HashZero) + await easyAuction + .connect(addr1) + .placeSellOrders( + auctionId, + [sellAmt], + [bidAmt.mul(2)], + [QUEUE_START], + ethers.constants.HashZero + ) + + // Advance halfway + await advanceTime(config.batchAuctionLength.div(2).toString()) + + // Cancel successfully + const OrderHelperFactory = await ethers.getContractFactory('IterableOrderedOrderSetWrapper') + const orderHelper = await OrderHelperFactory.deploy() + const userId = await easyAuction.callStatic.getUserId(addr1.address) + const order = await orderHelper.encodeOrder(userId, sellAmt, bidAmt) + await easyAuction.connect(addr1).cancelSellOrders(auctionId, [order]) + + // Advance near end + await advanceTime(config.batchAuctionLength.div(2).sub(10).toString()) + + // Cannot cancel + const order2 = await orderHelper.encodeOrder(userId, sellAmt, bidAmt.mul(2)) + await expect( + easyAuction.connect(addr1).cancelSellOrders(auctionId, [order2]) + ).to.be.revertedWith('no longer in order placement and cancelation phase') + + // End auction + await advanceTime(config.batchAuctionLength.div(2).toString()) + await easyAuction.settleAuction(auctionId) + }) + it('full volume -- bid at 2x price', async () => { const bidAmt = buyAmt.add(1) sellAmt = sellAmt.div(2)