From eee8a82e39988e1493b960a07d3302480f10e03d Mon Sep 17 00:00:00 2001 From: Schlagonia Date: Fri, 25 Oct 2024 10:45:26 -0600 Subject: [PATCH] feat: settle --- src/Auctions/DumperAuction.sol | 105 +++++++++++++++++++++----------- src/test/DumperAuction.t.sol | 106 +++++++++++++-------------------- 2 files changed, 111 insertions(+), 100 deletions(-) diff --git a/src/Auctions/DumperAuction.sol b/src/Auctions/DumperAuction.sol index 6d07a5a..0d53ac5 100644 --- a/src/Auctions/DumperAuction.sol +++ b/src/Auctions/DumperAuction.sol @@ -39,10 +39,9 @@ contract DumperAuction is Governance2Step, ReentrancyGuard { /// @notice Store all the auction specific information. struct AuctionInfo { - uint128 kicked; - uint128 scaler; + uint64 kicked; + uint64 scaler; uint128 initialAvailable; - uint128 currentAvailable; } uint256 internal constant WAD = 1e18; @@ -129,17 +128,39 @@ contract DumperAuction is Governance2Step, ReentrancyGuard { return wantInfo.tokenAddress; } - function available(address _from) external view virtual returns (uint256) { - if (auctions[_from].kicked + auctionLength > block.timestamp) { - return auctions[_from].currentAvailable; - } - return 0; + /** + * @notice Get the available amount for the auction. + * @param _from The address of the token to be auctioned. + * @return . The available amount for the auction. + */ + function available(address _from) public view virtual returns (uint256) { + if (!isActive(_from)) return 0; + + return + Maths.min( + auctions[_from].initialAvailable, + ERC20(_from).balanceOf(address(this)) + ); } + /** + * @notice Get the kicked timestamp for the auction. + * @param _from The address of the token to be auctioned. + * @return . The kicked timestamp for the auction. + */ function kicked(address _from) external view virtual returns (uint256) { return auctions[_from].kicked; } + /** + * @notice Check if the auction is active. + * @param _from The address of the token to be auctioned. + * @return . Whether the auction is active. + */ + function isActive(address _from) public view virtual returns (bool) { + return auctions[_from].kicked + auctionLength > block.timestamp; + } + /** * @notice Get all the enabled auctions. */ @@ -160,14 +181,28 @@ contract DumperAuction is Governance2Step, ReentrancyGuard { */ function kickable(address _from) external view virtual returns (uint256) { // If not enough time has passed then `kickable` is 0. - if (auctions[_from].kicked + auctionLength > block.timestamp) { - return 0; - } + if (isActive(_from)) return 0; // Use the full balance of this contract. return ERC20(_from).balanceOf(address(this)); } + /** + * @notice Gets the amount of `want` needed to buy the available amount of `from`. + * @param _from The address of the token to be auctioned. + * @return . The amount of `want` needed to fulfill the take amount. + */ + function getAmountNeeded( + address _from + ) external view virtual returns (uint256) { + return + _getAmountNeeded( + auctions[_from], + available(_from), + block.timestamp + ); + } + /** * @notice Gets the amount of `want` needed to buy a specific amount of `from`. * @param _from The address of the token to be auctioned. @@ -205,10 +240,6 @@ contract DumperAuction is Governance2Step, ReentrancyGuard { uint256 _amountToTake, uint256 _timestamp ) internal view virtual returns (uint256) { - _amountToTake = _amountToTake > _auction.currentAvailable - ? _auction.currentAvailable - : _amountToTake; - return // Scale _amountToTake to 1e18 (_amountToTake * @@ -301,7 +332,7 @@ contract DumperAuction is Governance2Step, ReentrancyGuard { require(decimals <= 18, "unsupported decimals"); // Store all needed info. - auctions[_from].scaler = uint128(WAD / 10 ** decimals); + auctions[_from].scaler = uint64(WAD / 10 ** decimals); ERC20(_from).safeApprove(VAULT_RELAYER, type(uint256).max); @@ -413,9 +444,8 @@ contract DumperAuction is Governance2Step, ReentrancyGuard { require(_available != 0, "nothing to kick"); // Update the auctions status. - auctions[_from].kicked = uint128(block.timestamp); + auctions[_from].kicked = uint64(block.timestamp); auctions[_from].initialAvailable = uint128(_available); - auctions[_from].currentAvailable = uint128(_available); emit AuctionKicked(_from, _available); } @@ -491,9 +521,8 @@ contract DumperAuction is Governance2Step, ReentrancyGuard { ); // Max amount that can be taken. - _amountTaken = auction.currentAvailable > _maxAmount - ? _maxAmount - : auction.currentAvailable; + uint256 available = available(_from); + _amountTaken = available > _maxAmount ? _maxAmount : available; // Get the amount needed uint256 needed = _getAmountNeeded( @@ -507,13 +536,6 @@ contract DumperAuction is Governance2Step, ReentrancyGuard { // Send `from`. ERC20(_from).safeTransfer(_receiver, _amountTaken); - // How much is left in this auction. - uint256 left; - unchecked { - left = auction.currentAvailable - _amountTaken; - } - auctions[_from].currentAvailable = uint128(left); - // If the caller has specified data. if (_data.length != 0) { // Do the callback. @@ -557,19 +579,32 @@ contract DumperAuction is Governance2Step, ReentrancyGuard { require(_hash == order.hash(COW_DOMAIN_SEPARATOR), "bad order"); require(paymentAmount > 0, "zero amount"); require(order.feeAmount == 0, "fee"); - require(order.validTo < block.timestamp + 1 days, "expired"); + require(order.validTo < auction.kicked + auctionLength, "expired"); require(order.appData == bytes32(0), "app data"); require(order.buyAmount >= paymentAmount, "bad price"); require(address(order.buyToken) == want(), "bad token"); require(order.receiver == receiver, "bad receiver"); - require(order.sellAmount == auction.currentAvailable, "bad amount"); - require( - order.sellToken.balanceOf(address(this)) >= - auction.currentAvailable, - "taken" - ); + require(order.sellAmount <= auction.initialAvailable, "bad amount"); // If all checks pass, return the magic value return this.isValidSignature.selector; } + + /** + * @notice Allows the auction to be stopped if the full amount is taken. + * @param _from The address of the token to be auctioned. + */ + function settle(address _from) external virtual { + require(isActive(_from), "!active"); + require(ERC20(_from).balanceOf(address(this)) == 0, "!empty"); + + auctions[_from].kicked = uint64(0); + } + + function sweep(address _token) external virtual onlyGovernance { + ERC20(_token).safeTransfer( + msg.sender, + ERC20(_token).balanceOf(address(this)) + ); + } } diff --git a/src/test/DumperAuction.t.sol b/src/test/DumperAuction.t.sol index dd936d7..f1f0ddf 100644 --- a/src/test/DumperAuction.t.sol +++ b/src/test/DumperAuction.t.sol @@ -86,17 +86,13 @@ contract DumperAuctionTest is Setup, ITaker { assertEq(auction.price(from), 0); assertEq(auction.receiver(), address(this)); - ( - uint128 _kicked, - uint128 _scaler, - uint128 _available, - uint128 _currentAvailable - ) = auction.auctions(from); + (uint128 _kicked, uint128 _scaler, uint128 _initialAvailable) = auction + .auctions(from); assertEq(_kicked, 0); assertEq(_scaler, 1e12); - assertEq(_available, 0); - assertEq(_currentAvailable, 0); + assertEq(_initialAvailable, 0); + assertEq(auction.available(from), 0); // Kicking it reverts vm.expectRevert("nothing to kick"); @@ -120,17 +116,13 @@ contract DumperAuctionTest is Setup, ITaker { assertEq(auction.getAllEnabledAuctions().length, 1); - ( - uint128 _kicked, - uint128 _scaler, - uint128 _available, - uint128 _currentAvailable - ) = auction.auctions(from); + (uint128 _kicked, uint128 _scaler, uint128 _initialAvailable) = auction + .auctions(from); assertEq(_kicked, 0); assertEq(_scaler, 1e12); - assertEq(_available, 0); - assertEq(_currentAvailable, 0); + assertEq(_initialAvailable, 0); + assertEq(auction.available(from), 0); vm.expectRevert("!governance"); vm.prank(management); @@ -142,14 +134,12 @@ contract DumperAuctionTest is Setup, ITaker { assertEq(auction.getAllEnabledAuctions().length, 0); - (_kicked, _scaler, _available, _currentAvailable) = auction.auctions( - from - ); + (_kicked, _scaler, _initialAvailable) = auction.auctions(from); assertEq(_kicked, 0); assertEq(_scaler, 0); - assertEq(_available, 0); - assertEq(_currentAvailable, 0); + assertEq(_initialAvailable, 0); + assertEq(auction.available(from), 0); } function test_kickAuction(uint256 _amount) public { @@ -166,32 +156,28 @@ contract DumperAuctionTest is Setup, ITaker { auction.enable(from); assertEq(auction.kickable(from), 0); - ( - uint128 _kicked, - uint128 _scaler, - uint128 _available, - uint128 _currentAvailable - ) = auction.auctions(from); + (uint128 _kicked, uint128 _scaler, uint128 _initialAvailable) = auction + .auctions(from); assertEq(_kicked, 0); assertEq(_scaler, 1e10); - assertEq(_available, 0); - assertEq(_currentAvailable, 0); + assertEq(_initialAvailable, 0); + assertEq(auction.available(from), 0); airdrop(ERC20(from), address(auction), _amount); assertEq(auction.kickable(from), _amount); - (_kicked, , _available, _currentAvailable) = auction.auctions(from); + (_kicked, , _initialAvailable) = auction.auctions(from); assertEq(_kicked, 0); - assertEq(_available, 0); + assertEq(_initialAvailable, 0); assertEq(auction.available(from), 0); uint256 available = auction.kick(from); assertEq(auction.kickable(from), 0); - (_kicked, , _available, _currentAvailable) = auction.auctions(from); + (_kicked, , _initialAvailable) = auction.auctions(from); assertEq(_kicked, block.timestamp); - assertEq(_available, _amount); + assertEq(_initialAvailable, _amount); assertEq(auction.available(from), _amount); uint256 startingPrice = ((auction.startingPrice() * (WAD / wantScaler)) * 1e18) / @@ -234,6 +220,7 @@ contract DumperAuctionTest is Setup, ITaker { assertEq(auction.price(from), 0); assertEq(auction.getAmountNeeded(from, _amount), 0); + assertEq(auction.available(from), 0); assertEq(auction.kickable(from), _amount); } @@ -259,16 +246,12 @@ contract DumperAuctionTest is Setup, ITaker { uint256 available = auction.kick(from); assertEq(auction.kickable(from), 0); - ( - uint128 _kicked, - uint128 _scaler, - uint128 _available, - uint128 _currentAvailable - ) = auction.auctions(from); + (uint128 _kicked, uint128 _scaler, uint128 _initialAvailable) = auction + .auctions(from); assertEq(_kicked, block.timestamp); assertEq(_scaler, 1e10); - assertEq(_available, _amount); - assertEq(_currentAvailable, _amount); + assertEq(_initialAvailable, _amount); + assertEq(auction.available(from), _amount); skip(auction.auctionLength() / 2); @@ -285,9 +268,9 @@ contract DumperAuctionTest is Setup, ITaker { assertEq(amountTaken, _amount); - (, , _available, _currentAvailable) = auction.auctions(from); - assertEq(_available, _amount); - assertEq(_currentAvailable, 0); + (, , _initialAvailable) = auction.auctions(from); + assertEq(_initialAvailable, _amount); + assertEq(auction.available(from), 0); assertEq(ERC20(asset).balanceOf(address(this)), beforeAsset); assertEq(ERC20(from).balanceOf(address(this)), before + _amount); @@ -318,15 +301,12 @@ contract DumperAuctionTest is Setup, ITaker { auction.kick(from); assertEq(auction.kickable(from), 0); - ( - uint256 _kicked, - uint256 _scaler, - uint256 _available, - uint256 _currentAvailable - ) = auction.auctions(from); + (uint256 _kicked, uint256 _scaler, uint256 _initialAvailable) = auction + .auctions(from); assertEq(_kicked, block.timestamp); assertEq(_scaler, 1e10); - assertEq(_available, _amount); + assertEq(_initialAvailable, _amount); + assertEq(auction.available(from), _amount); skip(auction.auctionLength() / 2); @@ -345,9 +325,9 @@ contract DumperAuctionTest is Setup, ITaker { assertEq(amountTaken, toTake); - (, , _available, _currentAvailable) = auction.auctions(from); - assertEq(_available, _amount); - assertEq(_currentAvailable, left); + (, , _initialAvailable) = auction.auctions(from); + assertEq(_initialAvailable, _amount); + assertEq(auction.available(from), left); assertEq(ERC20(asset).balanceOf(address(this)), beforeAsset); assertEq(ERC20(from).balanceOf(address(this)), before + toTake); assertEq(ERC20(from).balanceOf(address(auction)), left); @@ -376,16 +356,12 @@ contract DumperAuctionTest is Setup, ITaker { auction.kick(from); assertEq(auction.kickable(from), 0); - ( - uint256 _kicked, - uint256 _scaler, - uint256 _available, - uint256 _currentAvailable - ) = auction.auctions(from); + (uint256 _kicked, uint256 _scaler, uint256 _initialAvailable) = auction + .auctions(from); assertEq(_kicked, block.timestamp); assertEq(_scaler, 1e10); - assertEq(_available, _amount); - + assertEq(_initialAvailable, _amount); + assertEq(auction.available(from), _amount); skip(auction.auctionLength() / 2); uint256 toTake = _amount / 2; @@ -409,9 +385,9 @@ contract DumperAuctionTest is Setup, ITaker { assertTrue(callbackHit); assertEq(amountTaken, toTake); - (, , _available, _currentAvailable) = auction.auctions(from); - assertEq(_available, _amount); - assertEq(_currentAvailable, left); + (, , _initialAvailable) = auction.auctions(from); + assertEq(_initialAvailable, _amount); + assertEq(auction.available(from), left); assertEq(ERC20(asset).balanceOf(address(this)), beforeAsset); assertEq(ERC20(from).balanceOf(address(this)), before + toTake); assertEq(ERC20(from).balanceOf(address(auction)), left);