From 68fb49a1458e95e0243a602a4ac0000752d981a8 Mon Sep 17 00:00:00 2001 From: Schlagonia Date: Wed, 23 Oct 2024 19:56:50 -0600 Subject: [PATCH 01/12] build: cow swap support --- src/Auctions/Auction.sol | 2 +- src/Auctions/DumperAuction.sol | 555 ++++++++++++++++++++++++ src/Bases/Auctioneer/BaseAuctioneer.sol | 2 +- src/interfaces/ITaker.sol | 2 +- src/test/Auction.t.sol | 8 +- 5 files changed, 562 insertions(+), 7 deletions(-) create mode 100644 src/Auctions/DumperAuction.sol diff --git a/src/Auctions/Auction.sol b/src/Auctions/Auction.sol index df883d9..e60e3ec 100644 --- a/src/Auctions/Auction.sol +++ b/src/Auctions/Auction.sol @@ -685,7 +685,7 @@ contract Auction is Governance, ReentrancyGuard { if (_data.length != 0) { // Do the callback. ITaker(_receiver).auctionTakeCallback( - _auctionId, + auction.fromInfo.tokenAddress, msg.sender, _amountTaken, needed, diff --git a/src/Auctions/DumperAuction.sol b/src/Auctions/DumperAuction.sol new file mode 100644 index 0000000..09894f9 --- /dev/null +++ b/src/Auctions/DumperAuction.sol @@ -0,0 +1,555 @@ +// SPDX-License-Identifier: AGPL-3.0 +pragma solidity >=0.8.18; + +import {Governance2Step} from "../utils/Governance2Step.sol"; +import {ERC20} from "@openzeppelin/contracts/token/ERC20/ERC20.sol"; +import {SafeERC20} from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol"; +import {ReentrancyGuard} from "@openzeppelin/contracts/security/ReentrancyGuard.sol"; +import {ITaker} from "../interfaces/ITaker.sol"; + +/** + * @title Auction + * @author yearn.fi + * @notice General use dutch auction contract for token sales. + */ +contract DumperAuction is Governance2Step, ReentrancyGuard { + using SafeERC20 for ERC20; + + /// @notice Emitted when a new auction is enabled + event AuctionEnabled(address indexed from, address indexed to); + + /// @notice Emitted when an auction is disabled. + event AuctionDisabled(address indexed from, address indexed to); + + /// @notice Emitted when auction has been kicked. + event AuctionKicked(address from, uint256 available); + + /// @dev Store address and scaler in one slot. + struct TokenInfo { + address tokenAddress; + uint96 scaler; + } + + /// @notice Store all the auction specific information. + struct AuctionInfo { + uint96 kicked; + address receiver; + uint64 scaler; + uint192 initialAvailable; + uint256 currentAvailable; + } + + struct GPv2OrderData { + ERC20 sellToken; + ERC20 buyToken; + address receiver; + uint256 sellAmount; + uint256 buyAmount; + uint32 validTo; + bytes32 appData; + uint256 feeAmount; + bytes32 kind; + bool partiallyFillable; + bytes32 sellTokenBalance; + bytes32 buyTokenBalance; + } + + uint256 internal constant WAD = 1e18; + + address internal constant COW_SETTLEMENT = + 0x9008D19f58AAbD9eD0D60971565AA8510560ab41; + + address internal constant VAULT_RELAYER = + 0xC92E8bdf79f0507f65a392b0ab4667716BFE0110; + + /// @notice Struct to hold the info for `want`. + TokenInfo internal wantInfo; + + /// @notice The amount to start the auction at. + uint256 public startingPrice; + + /// @notice The time that each auction lasts. + uint256 public auctionLength; + + /// @notice Mapping from `from` token to its struct. + mapping(address => AuctionInfo) public auctions; + + /// @notice Array of all the enabled auction for this contract. + address[] public enabledAuctions; + + constructor() Governance2Step(msg.sender) {} + + /** + * @notice Initializes the Auction contract with initial parameters. + * @param _want Address this auction is selling to. + * @param _governance Address of the contract governance. + * @param _auctionLength Duration of each auction in seconds. + * @param _startingPrice Starting price for each auction. + */ + function initialize( + address _want, + address _governance, + uint256 _auctionLength, + uint256 _startingPrice + ) external virtual { + require(auctionLength == 0, "initialized"); + require(_want != address(0), "ZERO ADDRESS"); + require(_auctionLength != 0, "length"); + require(_startingPrice != 0, "starting price"); + + // Cannot have more than 18 decimals. + uint256 decimals = ERC20(_want).decimals(); + require(decimals <= 18, "unsupported decimals"); + + // Set variables + wantInfo = TokenInfo({ + tokenAddress: _want, + scaler: uint96(WAD / 10 ** decimals) + }); + + governance = _governance; + auctionLength = _auctionLength; + startingPrice = _startingPrice; + } + + /*////////////////////////////////////////////////////////////// + VIEW METHODS + //////////////////////////////////////////////////////////////*/ + + /** + * @notice Get the address of this auctions want token. + * @return . The want token. + */ + function want() public view virtual returns (address) { + return wantInfo.tokenAddress; + } + + function available(address _from) external view virtual returns (uint256) { + if (auctions[_from].kicked + auctionLength > block.timestamp) { + return auctions[_from].initialAvailable; + } + return 0; + } + + function kicked(address _from) external view virtual returns (uint256) { + return auctions[_from].kicked; + } + + /** + * @notice Get the length of the enabled auctions array. + */ + function numberOfEnabledAuctions() external view virtual returns (uint256) { + return enabledAuctions.length; + } + + /** + * @notice Get the pending amount available for the next auction. + * @dev Defaults to the auctions balance of the from token if no hook. + * @param _from The address of the token to be auctioned. + * @return uint256 The amount that can be kicked into the auction. + */ + 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; + } + + // Use the full balance of this contract. + return ERC20(_from).balanceOf(address(this)); + } + + /** + * @notice Gets the amount of `want` needed to buy a specific amount of `from`. + * @param _from The address of the token to be auctioned. + * @param _amountToTake The amount of `from` to take in the auction. + * @return . The amount of `want` needed to fulfill the take amount. + */ + function getAmountNeeded( + address _from, + uint256 _amountToTake + ) external view virtual returns (uint256) { + return + _getAmountNeeded(auctions[_from], _amountToTake, block.timestamp); + } + + /** + * @notice Gets the amount of `want` needed to buy a specific amount of `from` at a specific timestamp. + * @param _from The address of the token to be auctioned. + * @param _amountToTake The amount `from` to take in the auction. + * @param _timestamp The specific timestamp for calculating the amount needed. + * @return . The amount of `want` needed to fulfill the take amount. + */ + function getAmountNeeded( + address _from, + uint256 _amountToTake, + uint256 _timestamp + ) external view virtual returns (uint256) { + return _getAmountNeeded(auctions[_from], _amountToTake, _timestamp); + } + + /** + * @dev Return the amount of `want` needed to buy `_amountToTake`. + */ + function _getAmountNeeded( + AuctionInfo memory _auction, + uint256 _amountToTake, + uint256 _timestamp + ) internal view virtual returns (uint256) { + _amountToTake = _amountToTake > _auction.currentAvailable + ? _auction.currentAvailable + : _amountToTake; + + return + // Scale _amountToTake to 1e18 + (_amountToTake * + _auction.scaler * + // Price is always 1e18 + _price( + _auction.kicked, + _auction.initialAvailable * _auction.scaler, + _timestamp + )) / + 1e18 / + // Scale back down to want. + wantInfo.scaler; + } + + /** + * @notice Gets the price of the auction at the current timestamp. + * @param _from The address of the token to be auctioned. + * @return . The price of the auction. + */ + function price(address _from) external view virtual returns (uint256) { + return price(_from, block.timestamp); + } + + /** + * @notice Gets the price of the auction at a specific timestamp. + * @param _from The address of the token to be auctioned. + * @param _timestamp The specific timestamp for calculating the price. + * @return . The price of the auction. + */ + function price( + address _from, + uint256 _timestamp + ) public view virtual returns (uint256) { + // Get unscaled price and scale it down. + return + _price( + auctions[_from].kicked, + auctions[_from].initialAvailable * auctions[_from].scaler, + _timestamp + ) / wantInfo.scaler; + } + + /** + * @dev Internal function to calculate the scaled price based on auction parameters. + * @param _kicked The timestamp the auction was kicked. + * @param _available The initial available amount scaled 1e18. + * @param _timestamp The specific timestamp for calculating the price. + * @return . The calculated price scaled to 1e18. + */ + function _price( + uint256 _kicked, + uint256 _available, + uint256 _timestamp + ) internal view virtual returns (uint256) { + if (_available == 0) return 0; + + uint256 secondsElapsed = _timestamp - _kicked; + + if (secondsElapsed > auctionLength) return 0; + + uint256 initialPrice = (startingPrice * WAD * WAD) / _available; + + return (initialPrice - + ((initialPrice * secondsElapsed) / auctionLength)); + } + + /*////////////////////////////////////////////////////////////// + SETTERS + //////////////////////////////////////////////////////////////*/ + + /** + * @notice Enables a new auction. + * @dev Uses governance as the receiver. + * @param _from The address of the token to be auctioned. + */ + function enable(address _from) external virtual { + return enable(_from, msg.sender); + } + + /** + * @notice Enables a new auction. + * @param _from The address of the token to be auctioned. + * @param _receiver The address that will receive the funds in the auction. + */ + function enable( + address _from, + address _receiver + ) public virtual onlyGovernance { + address _want = want(); + require(_from != address(0) && _from != _want, "ZERO ADDRESS"); + require( + _receiver != address(0) && _receiver != address(this), + "receiver" + ); + // Cannot have more than 18 decimals. + uint256 decimals = ERC20(_from).decimals(); + require(decimals <= 18, "unsupported decimals"); + + require(auctions[_from].receiver == address(0), "already enabled"); + + // Store all needed info. + auctions[_from].scaler = uint64(WAD / 10 ** decimals); + auctions[_from].receiver = _receiver; + + ERC20(_from).safeApprove(VAULT_RELAYER, type(uint256).max); + + // Add to the array. + enabledAuctions.push(_from); + + emit AuctionEnabled(_from, _want); + } + + /** + * @notice Disables an existing auction. + * @dev Only callable by governance. + * @param _from The address of the token being sold. + */ + function disable(address _from) external virtual { + disable(_from, 0); + } + + /** + * @notice Disables an existing auction. + * @dev Only callable by governance. + * @param _from The address of the token being sold. + * @param _index The index the auctionId is at in the array. + */ + function disable( + address _from, + uint256 _index + ) public virtual onlyGovernance { + // Make sure the auction was enabled. + require(auctions[_from].receiver != address(0), "not enabled"); + + // Remove the struct. + delete auctions[_from]; + + ERC20(_from).safeApprove(VAULT_RELAYER, 0); + + // Remove the auction ID from the array. + address[] memory _enabledAuctions = enabledAuctions; + if (_enabledAuctions[_index] != _from) { + // If the _index given is not the id find it. + for (uint256 i = 0; i < _enabledAuctions.length; ++i) { + if (_enabledAuctions[i] == _from) { + _index = i; + break; + } + } + } + + // Move the id to the last spot if not there. + if (_index < _enabledAuctions.length - 1) { + _enabledAuctions[_index] = _enabledAuctions[ + _enabledAuctions.length - 1 + ]; + // Update the array. + enabledAuctions = _enabledAuctions; + } + + // Pop the id off the array. + enabledAuctions.pop(); + + emit AuctionDisabled(_from, want()); + } + + /** + * @notice Sets the starting price for the auction. + * @param _startingPrice The new starting price for the auction. + */ + function setStartingPrice( + uint256 _startingPrice + ) external virtual onlyGovernance { + startingPrice = _startingPrice; + } + + /*////////////////////////////////////////////////////////////// + PARTICIPATE IN AUCTION + //////////////////////////////////////////////////////////////*/ + + /** + * @notice Kicks off an auction, updating its status and making funds available for bidding. + * @param _from The address of the token to be auctioned. + * @return _available The available amount for bidding on in the auction. + */ + function kick( + address _from + ) external virtual nonReentrant returns (uint256 _available) { + require(auctions[_from].receiver != address(0), "not enabled"); + require( + block.timestamp > auctions[_from].kicked + auctionLength, + "too soon" + ); + + // Just use current balance. + _available = ERC20(_from).balanceOf(address(this)); + + require(_available != 0, "nothing to kick"); + + // Update the auctions status. + auctions[_from].kicked = uint96(block.timestamp); + auctions[_from].initialAvailable = uint192(_available); + auctions[_from].currentAvailable = _available; + + emit AuctionKicked(_from, _available); + } + + /** + * @notice Take the token being sold in a live auction. + * @dev Defaults to taking the full amount and sending to the msg sender. + * @param _from The address of the token to be auctioned. + * @return . The amount of fromToken taken in the auction. + */ + function take(address _from) external virtual returns (uint256) { + return _take(_from, type(uint256).max, msg.sender, new bytes(0)); + } + + /** + * @notice Take the token being sold in a live auction with a specified maximum amount. + * @dev Uses the sender's address as the receiver. + * @param _from The address of the token to be auctioned. + * @param _maxAmount The maximum amount of fromToken to take in the auction. + * @return . The amount of fromToken taken in the auction. + */ + function take( + address _from, + uint256 _maxAmount + ) external virtual returns (uint256) { + return _take(_from, _maxAmount, msg.sender, new bytes(0)); + } + + /** + * @notice Take the token being sold in a live auction. + * @param _from The address of the token to be auctioned. + * @param _maxAmount The maximum amount of fromToken to take in the auction. + * @param _receiver The address that will receive the fromToken. + * @return _amountTaken The amount of fromToken taken in the auction. + */ + function take( + address _from, + uint256 _maxAmount, + address _receiver + ) external virtual returns (uint256) { + return _take(_from, _maxAmount, _receiver, new bytes(0)); + } + + /** + * @notice Take the token being sold in a live auction. + * @param _from The address of the token to be auctioned. + * @param _maxAmount The maximum amount of fromToken to take in the auction. + * @param _receiver The address that will receive the fromToken. + * @param _data The data signify the callback should be used and sent with it. + * @return _amountTaken The amount of fromToken taken in the auction. + */ + function take( + address _from, + uint256 _maxAmount, + address _receiver, + bytes calldata _data + ) external virtual returns (uint256) { + return _take(_from, _maxAmount, _receiver, _data); + } + + /// @dev Implements the take of the auction. + function _take( + address _from, + uint256 _maxAmount, + address _receiver, + bytes memory _data + ) internal virtual nonReentrant returns (uint256 _amountTaken) { + AuctionInfo memory auction = auctions[_from]; + // Make sure the auction is active. + require( + auction.kicked + auctionLength >= block.timestamp, + "not kicked" + ); + + // Max amount that can be taken. + _amountTaken = auction.currentAvailable > _maxAmount + ? _maxAmount + : auction.currentAvailable; + + // Get the amount needed + uint256 needed = _getAmountNeeded( + auction, + _amountTaken, + block.timestamp + ); + + require(needed != 0, "zero needed"); + + // Send `from`. + ERC20(_from).safeTransfer(_receiver, _amountTaken); + + // How much is left in this auction. + uint256 left; + unchecked { + left = auction.currentAvailable - _amountTaken; + } + auctions[_from].currentAvailable = left; + + // If the caller has specified data. + if (_data.length != 0) { + // Do the callback. + ITaker(_receiver).auctionTakeCallback( + _from, + msg.sender, + _amountTaken, + needed, + _data + ); + } + + // Cache the want address. + address _want = want(); + + // Pull `want`. + ERC20(_want).safeTransferFrom(msg.sender, auction.receiver, needed); + } + + function isValidSignature( + bytes32 _hash, + bytes calldata signature + ) external view returns (bytes4) { + require(!_reentrancyGuardEntered(), "ReentrancyGuard: reentrant call"); + + // Decode the signature into GPv2Order_Data + GPv2OrderData memory order = abi.decode(signature, (GPv2OrderData)); + + AuctionInfo memory auction = auctions[address(order.sellToken)]; + // Verify the auction is valid + uint256 paymentAmount = _getAmountNeeded( + auction, + order.sellAmount, + block.timestamp + ); + + // Verify order details + require(paymentAmount > 0, "zero amount"); + require(order.buyAmount >= paymentAmount, "bad price"); + require(address(order.buyToken) == want(), "bad token"); + require(order.receiver == auction.receiver, "bad receiver"); + require(order.sellAmount == auction.currentAvailable, "bad amount"); + require( + order.sellToken.balanceOf(address(this)) >= + auction.currentAvailable, + "taken" + ); + + // If all checks pass, return the magic value + return DumperAuction.isValidSignature.selector; + } +} diff --git a/src/Bases/Auctioneer/BaseAuctioneer.sol b/src/Bases/Auctioneer/BaseAuctioneer.sol index 78a3305..b7e5d8b 100644 --- a/src/Bases/Auctioneer/BaseAuctioneer.sol +++ b/src/Bases/Auctioneer/BaseAuctioneer.sol @@ -547,7 +547,7 @@ abstract contract BaseAuctioneer is BaseHealthCheck, ReentrancyGuard { if (_data.length != 0) { // Do the callback. ITaker(_receiver).auctionTakeCallback( - _auctionId, + auction.fromInfo.tokenAddress, msg.sender, _amountTaken, needed, diff --git a/src/interfaces/ITaker.sol b/src/interfaces/ITaker.sol index 9e0a582..66be85e 100644 --- a/src/interfaces/ITaker.sol +++ b/src/interfaces/ITaker.sol @@ -3,7 +3,7 @@ pragma solidity >=0.8.18; interface ITaker { function auctionTakeCallback( - bytes32 _auctionId, + address _from, address _sender, uint256 _amountTaken, uint256 _amountNeeded, diff --git a/src/test/Auction.t.sol b/src/test/Auction.t.sol index 826426e..407f3ae 100644 --- a/src/test/Auction.t.sol +++ b/src/test/Auction.t.sol @@ -33,7 +33,7 @@ contract AuctionTest is Setup, ITaker { ); event Callback( - bytes32 _auctionId, + address _from, address _sender, uint256 _amountTaken, uint256 _amountNeeded, @@ -419,7 +419,7 @@ contract AuctionTest is Setup, ITaker { bytes memory _data = new bytes(69); vm.expectEmit(true, true, true, true, address(this)); - emit Callback(id, address(this), toTake, needed, _data); + emit Callback(from, address(this), toTake, needed, _data); uint256 amountTaken = auction.take(id, toTake, address(this), _data); assertTrue(callbackHit); @@ -436,13 +436,13 @@ contract AuctionTest is Setup, ITaker { // Taker call back function function auctionTakeCallback( - bytes32 _auctionId, + address _from, address _sender, uint256 _amountTaken, uint256 _amountNeeded, bytes memory _data ) external { callbackHit = true; - emit Callback(_auctionId, _sender, _amountTaken, _amountNeeded, _data); + emit Callback(_from, _sender, _amountTaken, _amountNeeded, _data); } } From 403886901923bf2aca5db92da592559510687e86 Mon Sep 17 00:00:00 2001 From: Schlagonia Date: Wed, 23 Oct 2024 22:56:22 -0600 Subject: [PATCH 02/12] chore: hash siggy --- src/Auctions/DumperAuction.sol | 113 ++++++------ src/Auctions/DumperAuctionFactory.sol | 173 ++++++++++++++++++ src/libraries/GPv2Order.sol | 243 ++++++++++++++++++++++++++ 3 files changed, 472 insertions(+), 57 deletions(-) create mode 100644 src/Auctions/DumperAuctionFactory.sol create mode 100644 src/libraries/GPv2Order.sol diff --git a/src/Auctions/DumperAuction.sol b/src/Auctions/DumperAuction.sol index 09894f9..a61f5f3 100644 --- a/src/Auctions/DumperAuction.sol +++ b/src/Auctions/DumperAuction.sol @@ -6,6 +6,11 @@ import {ERC20} from "@openzeppelin/contracts/token/ERC20/ERC20.sol"; import {SafeERC20} from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol"; import {ReentrancyGuard} from "@openzeppelin/contracts/security/ReentrancyGuard.sol"; import {ITaker} from "../interfaces/ITaker.sol"; +import {GPv2Order} from "../libraries/GPv2Order.sol"; + +interface ICowSettlement { + function domainSeparator() external view returns (bytes32); +} /** * @title Auction @@ -13,6 +18,7 @@ import {ITaker} from "../interfaces/ITaker.sol"; * @notice General use dutch auction contract for token sales. */ contract DumperAuction is Governance2Step, ReentrancyGuard { + using GPv2Order for GPv2Order.Data; using SafeERC20 for ERC20; /// @notice Emitted when a new auction is enabled @@ -32,26 +38,10 @@ contract DumperAuction is Governance2Step, ReentrancyGuard { /// @notice Store all the auction specific information. struct AuctionInfo { - uint96 kicked; - address receiver; - uint64 scaler; - uint192 initialAvailable; - uint256 currentAvailable; - } - - struct GPv2OrderData { - ERC20 sellToken; - ERC20 buyToken; - address receiver; - uint256 sellAmount; - uint256 buyAmount; - uint32 validTo; - bytes32 appData; - uint256 feeAmount; - bytes32 kind; - bool partiallyFillable; - bytes32 sellTokenBalance; - bytes32 buyTokenBalance; + uint128 kicked; + uint128 scaler; + uint128 initialAvailable; + uint128 currentAvailable; } uint256 internal constant WAD = 1e18; @@ -62,9 +52,14 @@ contract DumperAuction is Governance2Step, ReentrancyGuard { address internal constant VAULT_RELAYER = 0xC92E8bdf79f0507f65a392b0ab4667716BFE0110; + bytes32 internal immutable COW_DOMAIN_SEPARATOR; + /// @notice Struct to hold the info for `want`. TokenInfo internal wantInfo; + /// @notice The address that will receive the funds in the auction. + address public receiver; + /// @notice The amount to start the auction at. uint256 public startingPrice; @@ -77,17 +72,21 @@ contract DumperAuction is Governance2Step, ReentrancyGuard { /// @notice Array of all the enabled auction for this contract. address[] public enabledAuctions; - constructor() Governance2Step(msg.sender) {} + constructor() Governance2Step(msg.sender) { + COW_DOMAIN_SEPARATOR = ICowSettlement(COW_SETTLEMENT).domainSeparator(); + } /** * @notice Initializes the Auction contract with initial parameters. * @param _want Address this auction is selling to. + * @param _receiver Address that will receive the funds from the auction. * @param _governance Address of the contract governance. * @param _auctionLength Duration of each auction in seconds. * @param _startingPrice Starting price for each auction. */ function initialize( address _want, + address _receiver, address _governance, uint256 _auctionLength, uint256 _startingPrice @@ -96,7 +95,7 @@ contract DumperAuction is Governance2Step, ReentrancyGuard { require(_want != address(0), "ZERO ADDRESS"); require(_auctionLength != 0, "length"); require(_startingPrice != 0, "starting price"); - + require(_receiver != address(0), "receiver"); // Cannot have more than 18 decimals. uint256 decimals = ERC20(_want).decimals(); require(decimals <= 18, "unsupported decimals"); @@ -107,6 +106,7 @@ contract DumperAuction is Governance2Step, ReentrancyGuard { scaler: uint96(WAD / 10 ** decimals) }); + receiver = _receiver; governance = _governance; auctionLength = _auctionLength; startingPrice = _startingPrice; @@ -270,39 +270,21 @@ contract DumperAuction is Governance2Step, ReentrancyGuard { SETTERS //////////////////////////////////////////////////////////////*/ - /** - * @notice Enables a new auction. - * @dev Uses governance as the receiver. - * @param _from The address of the token to be auctioned. - */ - function enable(address _from) external virtual { - return enable(_from, msg.sender); - } - /** * @notice Enables a new auction. * @param _from The address of the token to be auctioned. - * @param _receiver The address that will receive the funds in the auction. */ - function enable( - address _from, - address _receiver - ) public virtual onlyGovernance { + function enable(address _from) external virtual onlyGovernance { address _want = want(); require(_from != address(0) && _from != _want, "ZERO ADDRESS"); - require( - _receiver != address(0) && _receiver != address(this), - "receiver" - ); + require(auctions[_from].scaler == 0, "already enabled"); + // Cannot have more than 18 decimals. uint256 decimals = ERC20(_from).decimals(); require(decimals <= 18, "unsupported decimals"); - require(auctions[_from].receiver == address(0), "already enabled"); - // Store all needed info. - auctions[_from].scaler = uint64(WAD / 10 ** decimals); - auctions[_from].receiver = _receiver; + auctions[_from].scaler = uint128(WAD / 10 ** decimals); ERC20(_from).safeApprove(VAULT_RELAYER, type(uint256).max); @@ -332,7 +314,7 @@ contract DumperAuction is Governance2Step, ReentrancyGuard { uint256 _index ) public virtual onlyGovernance { // Make sure the auction was enabled. - require(auctions[_from].receiver != address(0), "not enabled"); + require(auctions[_from].scaler != 0, "not enabled"); // Remove the struct. delete auctions[_from]; @@ -373,9 +355,23 @@ contract DumperAuction is Governance2Step, ReentrancyGuard { function setStartingPrice( uint256 _startingPrice ) external virtual onlyGovernance { + require(_startingPrice != 0, "starting price"); startingPrice = _startingPrice; } + /** + * @notice Sets the auction length. + * @param _auctionLength The new auction length. + */ + function setAuctionLength( + uint256 _auctionLength + ) external virtual onlyGovernance { + require(_auctionLength != 0, "length"); + require(_auctionLength < 1 weeks, "too long"); + + auctionLength = _auctionLength; + } + /*////////////////////////////////////////////////////////////// PARTICIPATE IN AUCTION //////////////////////////////////////////////////////////////*/ @@ -388,7 +384,7 @@ contract DumperAuction is Governance2Step, ReentrancyGuard { function kick( address _from ) external virtual nonReentrant returns (uint256 _available) { - require(auctions[_from].receiver != address(0), "not enabled"); + require(auctions[_from].scaler != 0, "not enabled"); require( block.timestamp > auctions[_from].kicked + auctionLength, "too soon" @@ -400,9 +396,9 @@ contract DumperAuction is Governance2Step, ReentrancyGuard { require(_available != 0, "nothing to kick"); // Update the auctions status. - auctions[_from].kicked = uint96(block.timestamp); - auctions[_from].initialAvailable = uint192(_available); - auctions[_from].currentAvailable = _available; + auctions[_from].kicked = uint128(block.timestamp); + auctions[_from].initialAvailable = uint128(_available); + auctions[_from].currentAvailable = uint128(_available); emit AuctionKicked(_from, _available); } @@ -419,7 +415,7 @@ contract DumperAuction is Governance2Step, ReentrancyGuard { /** * @notice Take the token being sold in a live auction with a specified maximum amount. - * @dev Uses the sender's address as the receiver. + * @dev Will send the funds to the msg sender. * @param _from The address of the token to be auctioned. * @param _maxAmount The maximum amount of fromToken to take in the auction. * @return . The amount of fromToken taken in the auction. @@ -517,31 +513,34 @@ contract DumperAuction is Governance2Step, ReentrancyGuard { address _want = want(); // Pull `want`. - ERC20(_want).safeTransferFrom(msg.sender, auction.receiver, needed); + ERC20(_want).safeTransferFrom(msg.sender, receiver, needed); } + /// @dev Validates a COW order signature. function isValidSignature( bytes32 _hash, bytes calldata signature ) external view returns (bytes4) { require(!_reentrancyGuardEntered(), "ReentrancyGuard: reentrant call"); - // Decode the signature into GPv2Order_Data - GPv2OrderData memory order = abi.decode(signature, (GPv2OrderData)); + // Decode the signature to get the order. + GPv2Order.Data memory order = abi.decode(signature, (GPv2Order.Data)); AuctionInfo memory auction = auctions[address(order.sellToken)]; - // Verify the auction is valid + + // Get the current amount needed for the auction. uint256 paymentAmount = _getAmountNeeded( auction, order.sellAmount, block.timestamp ); - // Verify order details + // Verify the order details. + require(_hash == order.hash(COW_DOMAIN_SEPARATOR), "bad order"); require(paymentAmount > 0, "zero amount"); require(order.buyAmount >= paymentAmount, "bad price"); require(address(order.buyToken) == want(), "bad token"); - require(order.receiver == auction.receiver, "bad receiver"); + require(order.receiver == receiver, "bad receiver"); require(order.sellAmount == auction.currentAvailable, "bad amount"); require( order.sellToken.balanceOf(address(this)) >= @@ -550,6 +549,6 @@ contract DumperAuction is Governance2Step, ReentrancyGuard { ); // If all checks pass, return the magic value - return DumperAuction.isValidSignature.selector; + return this.isValidSignature.selector; } } diff --git a/src/Auctions/DumperAuctionFactory.sol b/src/Auctions/DumperAuctionFactory.sol new file mode 100644 index 0000000..5cc71c5 --- /dev/null +++ b/src/Auctions/DumperAuctionFactory.sol @@ -0,0 +1,173 @@ +// SPDX-License-Identifier: AGPL-3.0 +pragma solidity >=0.8.18; + +import {Auction} from "./Auction.sol"; +import {Clonable} from "../utils/Clonable.sol"; + +/// @title AuctionFactory +/// @notice Deploy a new Auction. +contract AuctionFactory is Clonable { + event DeployedNewAuction(address indexed auction, address indexed want); + + /// @notice The time that each auction lasts. + uint256 public constant DEFAULT_AUCTION_LENGTH = 1 days; + + /// @notice The amount to start the auction with. + uint256 public constant DEFAULT_STARTING_PRICE = 1_000_000; + + /// @notice Full array of all auctions deployed through this factory. + address[] public auctions; + + constructor() { + // Deploy the original + original = address(new Auction()); + } + + /** + * @notice Creates a new auction contract. + * @param _want Address of the token users will bid with. + * @return _newAuction Address of the newly created auction contract. + */ + function createNewAuction(address _want) external returns (address) { + return + _createNewAuction( + _want, + msg.sender, + msg.sender, + DEFAULT_AUCTION_LENGTH, + DEFAULT_STARTING_PRICE + ); + } + + /** + * @notice Creates a new auction contract. + * @param _want Address of the token users will bid with. + * @param _receiver Address that will receive the funds in the auction. + * @return _newAuction Address of the newly created auction contract. + */ + function createNewAuction( + address _want, + address _receiver + ) external returns (address) { + return + _createNewAuction( + _want, + _receiver, + msg.sender, + DEFAULT_AUCTION_LENGTH, + DEFAULT_STARTING_PRICE + ); + } + + /** + * @notice Creates a new auction contract. + * @param _want Address of the token users will bid with. + * @param _receiver Address that will receive the funds in the auction. + * @param _governance Address allowed to enable and disable auctions. + * @return _newAuction Address of the newly created auction contract. + */ + function createNewAuction( + address _want, + address _receiver, + address _governance + ) external returns (address) { + return + _createNewAuction( + _want, + _receiver, + _governance, + DEFAULT_AUCTION_LENGTH, + DEFAULT_STARTING_PRICE + ); + } + + /** + * @notice Creates a new auction contract. + * @param _want Address of the token users will bid with. + * @param _receiver Address that will receive the funds in the auction. + * @param _governance Address allowed to enable and disable auctions. + * @param _auctionLength Length of the auction in seconds. + * @return _newAuction Address of the newly created auction contract. + */ + function createNewAuction( + address _want, + address _receiver, + address _governance, + uint256 _auctionLength + ) external returns (address) { + return + _createNewAuction( + _want, + _receiver, + _governance, + _auctionLength, + DEFAULT_STARTING_PRICE + ); + } + + /** + * @notice Creates a new auction contract. + * @param _want Address of the token users will bid with. + * @param _receiver Address that will receive the funds in the auction. + * @param _governance Address allowed to enable and disable auctions. + * @param _auctionLength Length of the auction in seconds. + * @param _startingPrice Starting price for the auction (no decimals). + * NOTE: The starting price should be without decimals (1k == 1_000). + * @return _newAuction Address of the newly created auction contract. + */ + function createNewAuction( + address _want, + address _receiver, + address _governance, + uint256 _auctionLength, + uint256 _startingPrice + ) external returns (address) { + return + _createNewAuction( + _want, + _receiver, + _governance, + _auctionLength, + _startingPrice + ); + } + + /** + * @dev Deploys and initializes a new Auction + */ + function _createNewAuction( + address _want, + address _receiver, + address _governance, + uint256 _auctionLength, + uint256 _startingPrice + ) internal returns (address _newAuction) { + _newAuction = _clone(); + + Auction(_newAuction).initialize( + _want, + _receiver, + _governance, + _auctionLength, + _startingPrice + ); + + auctions.push(_newAuction); + + emit DeployedNewAuction(_newAuction, _want); + } + + /** + * @notice Get the full list of auctions deployed through this factory. + */ + function getAllAuctions() external view returns (address[] memory) { + return auctions; + } + + /** + * @notice Get the total number of auctions deployed through this factory. + */ + function numberOfAuctions() external view returns (uint256) { + return auctions.length; + } +} diff --git a/src/libraries/GPv2Order.sol b/src/libraries/GPv2Order.sol new file mode 100644 index 0000000..f3c2ef8 --- /dev/null +++ b/src/libraries/GPv2Order.sol @@ -0,0 +1,243 @@ +// SPDX-License-Identifier: LGPL-3.0-or-later +pragma solidity ^0.8.0; + +import {ERC20} from "@openzeppelin/contracts/token/ERC20/ERC20.sol"; + +/// @title Gnosis Protocol v2 Order Library +/// @author Gnosis Developers +library GPv2Order { + /// @dev The complete data for a Gnosis Protocol order. This struct contains + /// all order parameters that are signed for submitting to GP. + struct Data { + ERC20 sellToken; + ERC20 buyToken; + address receiver; + uint256 sellAmount; + uint256 buyAmount; + uint32 validTo; + bytes32 appData; + uint256 feeAmount; + bytes32 kind; + bool partiallyFillable; + bytes32 sellTokenBalance; + bytes32 buyTokenBalance; + } + + /// @dev The order EIP-712 type hash for the [`GPv2Order.Data`] struct. + /// + /// This value is pre-computed from the following expression: + /// ``` + /// keccak256( + /// "Order(" + + /// "address sellToken," + + /// "address buyToken," + + /// "address receiver," + + /// "uint256 sellAmount," + + /// "uint256 buyAmount," + + /// "uint32 validTo," + + /// "bytes32 appData," + + /// "uint256 feeAmount," + + /// "string kind," + + /// "bool partiallyFillable" + + /// "string sellTokenBalance" + + /// "string buyTokenBalance" + + /// ")" + /// ) + /// ``` + bytes32 internal constant TYPE_HASH = + hex"d5a25ba2e97094ad7d83dc28a6572da797d6b3e7fc6663bd93efb789fc17e489"; + + /// @dev The marker value for a sell order for computing the order struct + /// hash. This allows the EIP-712 compatible wallets to display a + /// descriptive string for the order kind (instead of 0 or 1). + /// + /// This value is pre-computed from the following expression: + /// ``` + /// keccak256("sell") + /// ``` + bytes32 internal constant KIND_SELL = + hex"f3b277728b3fee749481eb3e0b3b48980dbbab78658fc419025cb16eee346775"; + + /// @dev The OrderKind marker value for a buy order for computing the order + /// struct hash. + /// + /// This value is pre-computed from the following expression: + /// ``` + /// keccak256("buy") + /// ``` + bytes32 internal constant KIND_BUY = + hex"6ed88e868af0a1983e3886d5f3e95a2fafbd6c3450bc229e27342283dc429ccc"; + + /// @dev The TokenBalance marker value for using direct ERC20 balances for + /// computing the order struct hash. + /// + /// This value is pre-computed from the following expression: + /// ``` + /// keccak256("erc20") + /// ``` + bytes32 internal constant BALANCE_ERC20 = + hex"5a28e9363bb942b639270062aa6bb295f434bcdfc42c97267bf003f272060dc9"; + + /// @dev The TokenBalance marker value for using Balancer Vault external + /// balances (in order to re-use Vault ERC20 approvals) for computing the + /// order struct hash. + /// + /// This value is pre-computed from the following expression: + /// ``` + /// keccak256("external") + /// ``` + bytes32 internal constant BALANCE_EXTERNAL = + hex"abee3b73373acd583a130924aad6dc38cfdc44ba0555ba94ce2ff63980ea0632"; + + /// @dev The TokenBalance marker value for using Balancer Vault internal + /// balances for computing the order struct hash. + /// + /// This value is pre-computed from the following expression: + /// ``` + /// keccak256("internal") + /// ``` + bytes32 internal constant BALANCE_INTERNAL = + hex"4ac99ace14ee0a5ef932dc609df0943ab7ac16b7583634612f8dc35a4289a6ce"; + + /// @dev Marker address used to indicate that the receiver of the trade + /// proceeds should the owner of the order. + /// + /// This is chosen to be `address(0)` for gas efficiency as it is expected + /// to be the most common case. + address internal constant RECEIVER_SAME_AS_OWNER = address(0); + + /// @dev The byte length of an order unique identifier. + uint256 internal constant UID_LENGTH = 56; + + /// @dev Returns the actual receiver for an order. This function checks + /// whether or not the [`receiver`] field uses the marker value to indicate + /// it is the same as the order owner. + /// + /// @return receiver The actual receiver of trade proceeds. + function actualReceiver( + Data memory order, + address owner + ) internal pure returns (address receiver) { + if (order.receiver == RECEIVER_SAME_AS_OWNER) { + receiver = owner; + } else { + receiver = order.receiver; + } + } + + /// @dev Return the EIP-712 signing hash for the specified order. + /// + /// @param order The order to compute the EIP-712 signing hash for. + /// @param domainSeparator The EIP-712 domain separator to use. + /// @return orderDigest The 32 byte EIP-712 struct hash. + function hash( + Data memory order, + bytes32 domainSeparator + ) internal pure returns (bytes32 orderDigest) { + bytes32 structHash; + + // NOTE: Compute the EIP-712 order struct hash in place. As suggested + // in the EIP proposal, noting that the order struct has 10 fields, and + // including the type hash `(12 + 1) * 32 = 416` bytes to hash. + // + // solhint-disable-next-line no-inline-assembly + assembly { + let dataStart := sub(order, 32) + let temp := mload(dataStart) + mstore(dataStart, TYPE_HASH) + structHash := keccak256(dataStart, 416) + mstore(dataStart, temp) + } + + // NOTE: Now that we have the struct hash, compute the EIP-712 signing + // hash using scratch memory past the free memory pointer. The signing + // hash is computed from `"\x19\x01" || domainSeparator || structHash`. + // + // + // solhint-disable-next-line no-inline-assembly + assembly { + let freeMemoryPointer := mload(0x40) + mstore(freeMemoryPointer, "\x19\x01") + mstore(add(freeMemoryPointer, 2), domainSeparator) + mstore(add(freeMemoryPointer, 34), structHash) + orderDigest := keccak256(freeMemoryPointer, 66) + } + } + + /// @dev Packs order UID parameters into the specified memory location. The + /// result is equivalent to `abi.encodePacked(...)` with the difference that + /// it allows re-using the memory for packing the order UID. + /// + /// This function reverts if the order UID buffer is not the correct size. + /// + /// @param orderUid The buffer pack the order UID parameters into. + /// @param orderDigest The EIP-712 struct digest derived from the order + /// parameters. + /// @param owner The address of the user who owns this order. + /// @param validTo The epoch time at which the order will stop being valid. + function packOrderUidParams( + bytes memory orderUid, + bytes32 orderDigest, + address owner, + uint32 validTo + ) internal pure { + require(orderUid.length == UID_LENGTH, "GPv2: uid buffer overflow"); + + // NOTE: Write the order UID to the allocated memory buffer. The order + // parameters are written to memory in **reverse order** as memory + // operations write 32-bytes at a time and we want to use a packed + // encoding. This means, for example, that after writing the value of + // `owner` to bytes `20:52`, writing the `orderDigest` to bytes `0:32` + // will **overwrite** bytes `20:32`. This is desirable as addresses are + // only 20 bytes and `20:32` should be `0`s: + // + // | 1111111111222222222233333333334444444444555555 + // byte | 01234567890123456789012345678901234567890123456789012345 + // -------+--------------------------------------------------------- + // field | [.........orderDigest..........][......owner.......][vT] + // -------+--------------------------------------------------------- + // mstore | [000000000000000000000000000.vT] + // | [00000000000.......owner.......] + // | [.........orderDigest..........] + // + // Additionally, since Solidity `bytes memory` are length prefixed, + // 32 needs to be added to all the offsets. + // + // solhint-disable-next-line no-inline-assembly + assembly { + mstore(add(orderUid, 56), validTo) + mstore(add(orderUid, 52), owner) + mstore(add(orderUid, 32), orderDigest) + } + } + + /// @dev Extracts specific order information from the standardized unique + /// order id of the protocol. + /// + /// @param orderUid The unique identifier used to represent an order in + /// the protocol. This uid is the packed concatenation of the order digest, + /// the validTo order parameter and the address of the user who created the + /// order. It is used by the user to interface with the contract directly, + /// and not by calls that are triggered by the solvers. + /// @return orderDigest The EIP-712 signing digest derived from the order + /// parameters. + /// @return owner The address of the user who owns this order. + /// @return validTo The epoch time at which the order will stop being valid. + function extractOrderUidParams( + bytes calldata orderUid + ) + internal + pure + returns (bytes32 orderDigest, address owner, uint32 validTo) + { + require(orderUid.length == UID_LENGTH, "GPv2: invalid uid"); + + // Use assembly to efficiently decode packed calldata. + // solhint-disable-next-line no-inline-assembly + assembly { + orderDigest := calldataload(orderUid.offset) + owner := shr(96, calldataload(add(orderUid.offset, 32))) + validTo := shr(224, calldataload(add(orderUid.offset, 52))) + } + } +} From e8fa11a6a1e7be4487fd423ab30112817705f067 Mon Sep 17 00:00:00 2001 From: Schlagonia Date: Wed, 23 Oct 2024 23:05:14 -0600 Subject: [PATCH 03/12] fix: compiler --- src/Auctions/DumperAuction.sol | 2 +- src/Auctions/DumperAuctionFactory.sol | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/Auctions/DumperAuction.sol b/src/Auctions/DumperAuction.sol index a61f5f3..bd21840 100644 --- a/src/Auctions/DumperAuction.sol +++ b/src/Auctions/DumperAuction.sol @@ -495,7 +495,7 @@ contract DumperAuction is Governance2Step, ReentrancyGuard { unchecked { left = auction.currentAvailable - _amountTaken; } - auctions[_from].currentAvailable = left; + auctions[_from].currentAvailable = uint128(left); // If the caller has specified data. if (_data.length != 0) { diff --git a/src/Auctions/DumperAuctionFactory.sol b/src/Auctions/DumperAuctionFactory.sol index 5cc71c5..4461912 100644 --- a/src/Auctions/DumperAuctionFactory.sol +++ b/src/Auctions/DumperAuctionFactory.sol @@ -1,7 +1,7 @@ // SPDX-License-Identifier: AGPL-3.0 pragma solidity >=0.8.18; -import {Auction} from "./Auction.sol"; +import {DumperAuction} from "./DumperAuction.sol"; import {Clonable} from "../utils/Clonable.sol"; /// @title AuctionFactory @@ -20,7 +20,7 @@ contract AuctionFactory is Clonable { constructor() { // Deploy the original - original = address(new Auction()); + original = address(new DumperAuction()); } /** @@ -144,7 +144,7 @@ contract AuctionFactory is Clonable { ) internal returns (address _newAuction) { _newAuction = _clone(); - Auction(_newAuction).initialize( + DumperAuction(_newAuction).initialize( _want, _receiver, _governance, From e5136aa3d74ca71e37f80962695a86597d6b43ea Mon Sep 17 00:00:00 2001 From: Schlagonia Date: Wed, 23 Oct 2024 23:50:19 -0600 Subject: [PATCH 04/12] build: extra checks --- src/Auctions/DumperAuction.sol | 22 +++++++++++++++++++--- src/Auctions/DumperAuctionFactory.sol | 2 +- 2 files changed, 20 insertions(+), 4 deletions(-) diff --git a/src/Auctions/DumperAuction.sol b/src/Auctions/DumperAuction.sol index bd21840..9a020a0 100644 --- a/src/Auctions/DumperAuction.sol +++ b/src/Auctions/DumperAuction.sol @@ -1,6 +1,7 @@ // SPDX-License-Identifier: AGPL-3.0 pragma solidity >=0.8.18; +import {Maths} from "../libraries/Maths.sol"; import {Governance2Step} from "../utils/Governance2Step.sol"; import {ERC20} from "@openzeppelin/contracts/token/ERC20/ERC20.sol"; import {SafeERC20} from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol"; @@ -46,6 +47,10 @@ contract DumperAuction is Governance2Step, ReentrancyGuard { uint256 internal constant WAD = 1e18; + /// @notice Used for the price decay. + uint256 internal constant MINUTE_HALF_LIFE = + 0.988514020352896135_356867505 * 1e27; // 0.5^(1/60) + address internal constant COW_SETTLEMENT = 0x9008D19f58AAbD9eD0D60971565AA8510560ab41; @@ -260,10 +265,17 @@ contract DumperAuction is Governance2Step, ReentrancyGuard { if (secondsElapsed > auctionLength) return 0; - uint256 initialPrice = (startingPrice * WAD * WAD) / _available; + // Exponential decay from https://github.com/ajna-finance/ajna-core/blob/master/src/libraries/helpers/PoolHelper.sol + uint256 hoursComponent = 1e27 >> (secondsElapsed / 3600); + uint256 minutesComponent = Maths.rpow( + MINUTE_HALF_LIFE, + (secondsElapsed % 3600) / 60 + ); + uint256 initialPrice = Maths.wdiv(startingPrice * 1e18, _available); - return (initialPrice - - ((initialPrice * secondsElapsed) / auctionLength)); + return + (initialPrice * Maths.rmul(hoursComponent, minutesComponent)) / + 1e27; } /*////////////////////////////////////////////////////////////// @@ -521,6 +533,7 @@ contract DumperAuction is Governance2Step, ReentrancyGuard { bytes32 _hash, bytes calldata signature ) external view returns (bytes4) { + // Make sure `_take` has not already been entered. require(!_reentrancyGuardEntered(), "ReentrancyGuard: reentrant call"); // Decode the signature to get the order. @@ -538,6 +551,9 @@ contract DumperAuction is Governance2Step, ReentrancyGuard { // Verify the order details. 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.appData == bytes32(0), "app data"); require(order.buyAmount >= paymentAmount, "bad price"); require(address(order.buyToken) == want(), "bad token"); require(order.receiver == receiver, "bad receiver"); diff --git a/src/Auctions/DumperAuctionFactory.sol b/src/Auctions/DumperAuctionFactory.sol index 4461912..3e20e58 100644 --- a/src/Auctions/DumperAuctionFactory.sol +++ b/src/Auctions/DumperAuctionFactory.sol @@ -6,7 +6,7 @@ import {Clonable} from "../utils/Clonable.sol"; /// @title AuctionFactory /// @notice Deploy a new Auction. -contract AuctionFactory is Clonable { +contract DumperAuctionFactory is Clonable { event DeployedNewAuction(address indexed auction, address indexed want); /// @notice The time that each auction lasts. From f732617c7dd8acfd3e52c5f95a87416db6138193 Mon Sep 17 00:00:00 2001 From: Schlagonia Date: Thu, 24 Oct 2024 03:33:18 -0600 Subject: [PATCH 05/12] test: dumper auction --- script/DeployAuction.s.sol | 23 ++ src/Auctions/DumperAuction.sol | 13 +- src/test/DumperAuction.t.sol | 433 +++++++++++++++++++++++++++++++++ 3 files changed, 465 insertions(+), 4 deletions(-) create mode 100644 script/DeployAuction.s.sol create mode 100644 src/test/DumperAuction.t.sol diff --git a/script/DeployAuction.s.sol b/script/DeployAuction.s.sol new file mode 100644 index 0000000..4f574a8 --- /dev/null +++ b/script/DeployAuction.s.sol @@ -0,0 +1,23 @@ +// SPDX-License-Identifier: AGPL-3.0 +pragma solidity >=0.8.18; + +import "./BaseScript.s.sol"; + +// Deploy a contract to a deterministic address with create2 +contract DeployAprOracle is BaseScript { + + function run() external { + vm.startBroadcast(); + + // Get the bytecode + bytes memory bytecode = vm.getCode("DumperAuctionFactory.sol:DumperAuctionFactory"); + + bytes32 salt; + + address contractAddress = deployer.deployCreate2(salt, bytecode); + + console.log("Address is ", contractAddress); + + vm.stopBroadcast(); + } +} \ No newline at end of file diff --git a/src/Auctions/DumperAuction.sol b/src/Auctions/DumperAuction.sol index 9a020a0..6d07a5a 100644 --- a/src/Auctions/DumperAuction.sol +++ b/src/Auctions/DumperAuction.sol @@ -131,7 +131,7 @@ contract DumperAuction is Governance2Step, ReentrancyGuard { function available(address _from) external view virtual returns (uint256) { if (auctions[_from].kicked + auctionLength > block.timestamp) { - return auctions[_from].initialAvailable; + return auctions[_from].currentAvailable; } return 0; } @@ -141,10 +141,15 @@ contract DumperAuction is Governance2Step, ReentrancyGuard { } /** - * @notice Get the length of the enabled auctions array. + * @notice Get all the enabled auctions. */ - function numberOfEnabledAuctions() external view virtual returns (uint256) { - return enabledAuctions.length; + function getAllEnabledAuctions() + external + view + virtual + returns (address[] memory) + { + return enabledAuctions; } /** diff --git a/src/test/DumperAuction.t.sol b/src/test/DumperAuction.t.sol new file mode 100644 index 0000000..dd936d7 --- /dev/null +++ b/src/test/DumperAuction.t.sol @@ -0,0 +1,433 @@ +// SPDX-License-Identifier: AGPL-3.0 +pragma solidity >=0.8.18; + +import "forge-std/console.sol"; +import {Setup, IStrategy, SafeERC20, ERC20} from "./utils/Setup.sol"; + +import {ITaker} from "../interfaces/ITaker.sol"; +import {DumperAuction, DumperAuctionFactory} from "../Auctions/DumperAuctionFactory.sol"; + +contract DumperAuctionTest is Setup, ITaker { + using SafeERC20 for ERC20; + + event AuctionEnabled(address indexed from, address indexed to); + + event AuctionDisabled(address indexed from, address indexed to); + + event AuctionKicked(address indexed from, uint256 available); + + event Callback( + address indexed from, + address _sender, + uint256 _amountTaken, + uint256 _amountNeeded, + bytes _data + ); + + DumperAuction public auction; + DumperAuctionFactory public auctionFactory; + + uint256 public wantScaler; + uint256 public fromScaler; + + bool public callbackHit; + + function setUp() public override { + super.setUp(); + + auctionFactory = new DumperAuctionFactory(); + } + + function test_setup() public { + assertEq(auctionFactory.DEFAULT_AUCTION_LENGTH(), 1 days); + assertEq(auctionFactory.DEFAULT_STARTING_PRICE(), 1e6); + } + + function test_defaults() public { + auction = DumperAuction( + auctionFactory.createNewAuction(address(asset)) + ); + + vm.expectRevert("initialized"); + auction.initialize(address(asset), address(this), management, 1, 10); + + assertEq(auction.want(), address(asset)); + assertEq(auction.receiver(), address(this)); + assertEq(auction.governance(), address(this)); + assertEq( + auction.auctionLength(), + auctionFactory.DEFAULT_AUCTION_LENGTH() + ); + assertEq( + auction.startingPrice(), + auctionFactory.DEFAULT_STARTING_PRICE() + ); + } + + function test_enableAuction() public { + address from = tokenAddrs["USDC"]; + auction = DumperAuction( + auctionFactory.createNewAuction(address(asset)) + ); + + vm.expectRevert("!governance"); + vm.prank(management); + auction.enable(from); + + vm.expectEmit(true, true, true, true, address(auction)); + emit AuctionEnabled(from, address(asset)); + + auction.enable(from); + + assertEq(auction.getAllEnabledAuctions().length, 1); + assertEq(auction.enabledAuctions(0), from); + assertEq(auction.kickable(from), 0); + assertEq(auction.getAmountNeeded(from, 1e18), 0); + assertEq(auction.price(from), 0); + assertEq(auction.receiver(), address(this)); + + ( + uint128 _kicked, + uint128 _scaler, + uint128 _available, + uint128 _currentAvailable + ) = auction.auctions(from); + + assertEq(_kicked, 0); + assertEq(_scaler, 1e12); + assertEq(_available, 0); + assertEq(_currentAvailable, 0); + + // Kicking it reverts + vm.expectRevert("nothing to kick"); + auction.kick(from); + + // Can't re-enable + vm.expectRevert("already enabled"); + auction.enable(from); + } + + function test_disableAuction() public { + address from = tokenAddrs["USDC"]; + auction = DumperAuction( + auctionFactory.createNewAuction(address(asset)) + ); + + vm.expectRevert("not enabled"); + auction.disable(from); + + auction.enable(from); + + assertEq(auction.getAllEnabledAuctions().length, 1); + + ( + uint128 _kicked, + uint128 _scaler, + uint128 _available, + uint128 _currentAvailable + ) = auction.auctions(from); + + assertEq(_kicked, 0); + assertEq(_scaler, 1e12); + assertEq(_available, 0); + assertEq(_currentAvailable, 0); + + vm.expectRevert("!governance"); + vm.prank(management); + auction.disable(from); + + vm.expectEmit(true, true, true, true, address(auction)); + emit AuctionDisabled(from, address(asset)); + auction.disable(from); + + assertEq(auction.getAllEnabledAuctions().length, 0); + + (_kicked, _scaler, _available, _currentAvailable) = auction.auctions( + from + ); + + assertEq(_kicked, 0); + assertEq(_scaler, 0); + assertEq(_available, 0); + assertEq(_currentAvailable, 0); + } + + function test_kickAuction(uint256 _amount) public { + vm.assume(_amount >= minFuzzAmount && _amount <= maxFuzzAmount); + + address from = tokenAddrs["WBTC"]; + auction = DumperAuction( + auctionFactory.createNewAuction(address(asset)) + ); + + fromScaler = WAD / 10 ** ERC20(from).decimals(); + wantScaler = WAD / 10 ** ERC20(asset).decimals(); + + auction.enable(from); + + assertEq(auction.kickable(from), 0); + ( + uint128 _kicked, + uint128 _scaler, + uint128 _available, + uint128 _currentAvailable + ) = auction.auctions(from); + + assertEq(_kicked, 0); + assertEq(_scaler, 1e10); + assertEq(_available, 0); + assertEq(_currentAvailable, 0); + + airdrop(ERC20(from), address(auction), _amount); + + assertEq(auction.kickable(from), _amount); + (_kicked, , _available, _currentAvailable) = auction.auctions(from); + assertEq(_kicked, 0); + assertEq(_available, 0); + assertEq(auction.available(from), 0); + + uint256 available = auction.kick(from); + + assertEq(auction.kickable(from), 0); + (_kicked, , _available, _currentAvailable) = auction.auctions(from); + assertEq(_kicked, block.timestamp); + assertEq(_available, _amount); + assertEq(auction.available(from), _amount); + uint256 startingPrice = ((auction.startingPrice() * + (WAD / wantScaler)) * 1e18) / + _amount / + fromScaler; + assertEq(auction.price(from), startingPrice); + assertRelApproxEq( + auction.getAmountNeeded(from, _amount), + (startingPrice * fromScaler * _amount) / + (WAD / wantScaler) / + wantScaler, + MAX_BPS + ); + + uint256 expectedPrice = auction.price(from, block.timestamp + 100); + assertLt(expectedPrice, startingPrice); + uint256 expectedAmount = auction.getAmountNeeded( + from, + _amount, + block.timestamp + 100 + ); + assertLt( + expectedAmount, + (startingPrice * fromScaler * _amount) / + (WAD / wantScaler) / + wantScaler + ); + + skip(100); + + assertEq(auction.price(from), expectedPrice); + assertEq(auction.getAmountNeeded(from, _amount), expectedAmount); + + // Can't kick a new one yet + vm.expectRevert("too soon"); + auction.kick(from); + + // Skip full auction + skip(auction.auctionLength()); + + assertEq(auction.price(from), 0); + assertEq(auction.getAmountNeeded(from, _amount), 0); + + assertEq(auction.kickable(from), _amount); + } + + function test_takeAuction_all(uint256 _amount) public { + vm.assume(_amount >= minFuzzAmount && _amount <= maxFuzzAmount); + + address from = tokenAddrs["WBTC"]; + auction = DumperAuction( + auctionFactory.createNewAuction( + address(asset), + address(mockStrategy) + ) + ); + + fromScaler = WAD / 10 ** ERC20(from).decimals(); + wantScaler = WAD / 10 ** ERC20(asset).decimals(); + + auction.enable(from); + + airdrop(ERC20(from), address(auction), _amount); + + uint256 available = auction.kick(from); + + assertEq(auction.kickable(from), 0); + ( + uint128 _kicked, + uint128 _scaler, + uint128 _available, + uint128 _currentAvailable + ) = auction.auctions(from); + assertEq(_kicked, block.timestamp); + assertEq(_scaler, 1e10); + assertEq(_available, _amount); + assertEq(_currentAvailable, _amount); + + skip(auction.auctionLength() / 2); + + uint256 needed = auction.getAmountNeeded(from, _amount); + uint256 beforeAsset = ERC20(asset).balanceOf(address(this)); + + airdrop(ERC20(asset), address(this), needed); + + ERC20(asset).safeApprove(address(auction), needed); + + uint256 before = ERC20(from).balanceOf(address(this)); + + uint256 amountTaken = auction.take(from); + + assertEq(amountTaken, _amount); + + (, , _available, _currentAvailable) = auction.auctions(from); + assertEq(_available, _amount); + assertEq(_currentAvailable, 0); + + assertEq(ERC20(asset).balanceOf(address(this)), beforeAsset); + assertEq(ERC20(from).balanceOf(address(this)), before + _amount); + assertEq(ERC20(from).balanceOf(address(auction)), 0); + assertEq(ERC20(asset).balanceOf(address(mockStrategy)), needed); + assertEq(ERC20(asset).balanceOf(address(auction)), 0); + } + + function test_takeAuction_part(uint256 _amount, uint16 _percent) public { + vm.assume(_amount >= minFuzzAmount && _amount <= maxFuzzAmount); + _percent = uint16(bound(uint256(_percent), 1_000, MAX_BPS)); + + address from = tokenAddrs["WBTC"]; + auction = DumperAuction( + auctionFactory.createNewAuction( + address(asset), + address(mockStrategy) + ) + ); + + fromScaler = WAD / 10 ** ERC20(from).decimals(); + wantScaler = WAD / 10 ** ERC20(asset).decimals(); + + auction.enable(from); + + airdrop(ERC20(from), address(auction), _amount); + + auction.kick(from); + + assertEq(auction.kickable(from), 0); + ( + uint256 _kicked, + uint256 _scaler, + uint256 _available, + uint256 _currentAvailable + ) = auction.auctions(from); + assertEq(_kicked, block.timestamp); + assertEq(_scaler, 1e10); + assertEq(_available, _amount); + + skip(auction.auctionLength() / 2); + + uint256 toTake = (_amount * _percent) / MAX_BPS; + uint256 left = _amount - toTake; + uint256 needed = auction.getAmountNeeded(from, toTake); + uint256 beforeAsset = ERC20(asset).balanceOf(address(this)); + + airdrop(ERC20(asset), address(this), needed); + + ERC20(asset).safeApprove(address(auction), needed); + + uint256 before = ERC20(from).balanceOf(address(this)); + + uint256 amountTaken = auction.take(from, toTake); + + assertEq(amountTaken, toTake); + + (, , _available, _currentAvailable) = auction.auctions(from); + assertEq(_available, _amount); + assertEq(_currentAvailable, left); + assertEq(ERC20(asset).balanceOf(address(this)), beforeAsset); + assertEq(ERC20(from).balanceOf(address(this)), before + toTake); + assertEq(ERC20(from).balanceOf(address(auction)), left); + assertEq(ERC20(asset).balanceOf(address(mockStrategy)), needed); + assertEq(ERC20(asset).balanceOf(address(auction)), 0); + } + + function test_takeAuction_callback(uint256 _amount) public { + vm.assume(_amount >= minFuzzAmount && _amount <= maxFuzzAmount); + + address from = tokenAddrs["WBTC"]; + auction = DumperAuction( + auctionFactory.createNewAuction( + address(asset), + address(mockStrategy) + ) + ); + + fromScaler = WAD / 10 ** ERC20(from).decimals(); + wantScaler = WAD / 10 ** ERC20(asset).decimals(); + + auction.enable(from); + + airdrop(ERC20(from), address(auction), _amount); + + auction.kick(from); + + assertEq(auction.kickable(from), 0); + ( + uint256 _kicked, + uint256 _scaler, + uint256 _available, + uint256 _currentAvailable + ) = auction.auctions(from); + assertEq(_kicked, block.timestamp); + assertEq(_scaler, 1e10); + assertEq(_available, _amount); + + skip(auction.auctionLength() / 2); + + uint256 toTake = _amount / 2; + uint256 left = _amount - toTake; + uint256 needed = auction.getAmountNeeded(from, toTake); + uint256 beforeAsset = ERC20(asset).balanceOf(address(this)); + + airdrop(ERC20(asset), address(this), needed); + + ERC20(asset).safeApprove(address(auction), needed); + + uint256 before = ERC20(from).balanceOf(address(this)); + + callbackHit = false; + bytes memory _data = new bytes(69); + + vm.expectEmit(true, true, true, true, address(this)); + emit Callback(from, address(this), toTake, needed, _data); + uint256 amountTaken = auction.take(from, toTake, address(this), _data); + + assertTrue(callbackHit); + assertEq(amountTaken, toTake); + + (, , _available, _currentAvailable) = auction.auctions(from); + assertEq(_available, _amount); + assertEq(_currentAvailable, left); + assertEq(ERC20(asset).balanceOf(address(this)), beforeAsset); + assertEq(ERC20(from).balanceOf(address(this)), before + toTake); + assertEq(ERC20(from).balanceOf(address(auction)), left); + assertEq(ERC20(asset).balanceOf(address(mockStrategy)), needed); + assertEq(ERC20(asset).balanceOf(address(auction)), 0); + } + + // Taker call back function + function auctionTakeCallback( + address _from, + address _sender, + uint256 _amountTaken, + uint256 _amountNeeded, + bytes memory _data + ) external { + callbackHit = true; + emit Callback(_from, _sender, _amountTaken, _amountNeeded, _data); + } +} From d7c15ff4983a2de7563d1da319c015be2c8d9284 Mon Sep 17 00:00:00 2001 From: Schlagonia Date: Fri, 25 Oct 2024 10:45:26 -0600 Subject: [PATCH 06/12] 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); From 449bf8d3b931a57110ed43427263ac29155d07fc Mon Sep 17 00:00:00 2001 From: Schlagonia Date: Fri, 25 Oct 2024 11:15:54 -0600 Subject: [PATCH 07/12] chore: fixes --- src/Auctions/DumperAuction.sol | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/Auctions/DumperAuction.sol b/src/Auctions/DumperAuction.sol index 0d53ac5..ebb1965 100644 --- a/src/Auctions/DumperAuction.sol +++ b/src/Auctions/DumperAuction.sol @@ -158,7 +158,7 @@ contract DumperAuction is Governance2Step, ReentrancyGuard { * @return . Whether the auction is active. */ function isActive(address _from) public view virtual returns (bool) { - return auctions[_from].kicked + auctionLength > block.timestamp; + return auctions[_from].kicked + auctionLength >= block.timestamp; } /** @@ -579,6 +579,7 @@ 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.partialFillAllowed, "partial fill"); require(order.validTo < auction.kicked + auctionLength, "expired"); require(order.appData == bytes32(0), "app data"); require(order.buyAmount >= paymentAmount, "bad price"); From 9d588f11735163a1e79b4882759bd6fe8399064e Mon Sep 17 00:00:00 2001 From: Schlagonia Date: Fri, 25 Oct 2024 11:17:52 -0600 Subject: [PATCH 08/12] fix: name --- src/Auctions/DumperAuction.sol | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Auctions/DumperAuction.sol b/src/Auctions/DumperAuction.sol index ebb1965..720ee89 100644 --- a/src/Auctions/DumperAuction.sol +++ b/src/Auctions/DumperAuction.sol @@ -579,7 +579,7 @@ 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.partialFillAllowed, "partial fill"); + require(order.partiallyFillable, "partial fill"); require(order.validTo < auction.kicked + auctionLength, "expired"); require(order.appData == bytes32(0), "app data"); require(order.buyAmount >= paymentAmount, "bad price"); From 527a903e59a287dc3d0bc2daa9a8593e7895d883 Mon Sep 17 00:00:00 2001 From: Schlagonia Date: Fri, 25 Oct 2024 13:41:26 -0600 Subject: [PATCH 09/12] fix: available --- src/Auctions/DumperAuction.sol | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/src/Auctions/DumperAuction.sol b/src/Auctions/DumperAuction.sol index 720ee89..71457bf 100644 --- a/src/Auctions/DumperAuction.sol +++ b/src/Auctions/DumperAuction.sol @@ -301,7 +301,7 @@ contract DumperAuction is Governance2Step, ReentrancyGuard { if (secondsElapsed > auctionLength) return 0; - // Exponential decay from https://github.com/ajna-finance/ajna-core/blob/master/src/libraries/helpers/PoolHelper.sol + // Exponential step decay from https://github.com/ajna-finance/ajna-core/blob/master/src/libraries/helpers/PoolHelper.sol uint256 hoursComponent = 1e27 >> (secondsElapsed / 3600); uint256 minutesComponent = Maths.rpow( MINUTE_HALF_LIFE, @@ -521,8 +521,8 @@ contract DumperAuction is Governance2Step, ReentrancyGuard { ); // Max amount that can be taken. - uint256 available = available(_from); - _amountTaken = available > _maxAmount ? _maxAmount : available; + uint256 _available = available(_from); + _amountTaken = _available > _maxAmount ? _maxAmount : _available; // Get the amount needed uint256 needed = _getAmountNeeded( @@ -577,7 +577,8 @@ contract DumperAuction is Governance2Step, ReentrancyGuard { // Verify the order details. require(_hash == order.hash(COW_DOMAIN_SEPARATOR), "bad order"); - require(paymentAmount > 0, "zero amount"); + require(paymentAmount != 0, "zero amount"); + require(available(address(order.sellToken)) != 0, "zero available"); require(order.feeAmount == 0, "fee"); require(order.partiallyFillable, "partial fill"); require(order.validTo < auction.kicked + auctionLength, "expired"); From 6958548929ef47c5285be4c83f1212cd3689a3ac Mon Sep 17 00:00:00 2001 From: Schlagonia Date: Thu, 7 Nov 2024 15:02:37 -0700 Subject: [PATCH 10/12] chore: update ingerited --- script/DeployAuction.s.sol | 2 +- src/Auctions/Auction.sol | 525 +++++++---------- src/Auctions/AuctionFactory.sol | 69 +-- src/Auctions/DumperAuction.sol | 612 -------------------- src/Auctions/DumperAuctionFactory.sol | 173 ------ src/Bases/Auctioneer/BaseAuctioneer.sol | 609 +------------------ src/Bases/Auctioneer/IBaseAuctioneer.sol | 78 +-- src/swappers/AuctionSwapper.sol | 117 +--- src/swappers/interfaces/IAuctionSwapper.sol | 6 - src/test/Auction.t.sol | 301 ++++------ src/test/AuctionSwapper.t.sol | 370 +++++------- src/test/BaseAuctioneer.t.sol | 368 +++--------- src/test/DumperAuction.t.sol | 409 ------------- src/test/mocks/MockAuctionSwapper.sol | 41 +- src/test/mocks/MockAuctioneer.sol | 72 +-- 15 files changed, 637 insertions(+), 3115 deletions(-) delete mode 100644 src/Auctions/DumperAuction.sol delete mode 100644 src/Auctions/DumperAuctionFactory.sol delete mode 100644 src/test/DumperAuction.t.sol diff --git a/script/DeployAuction.s.sol b/script/DeployAuction.s.sol index 4f574a8..2de4f52 100644 --- a/script/DeployAuction.s.sol +++ b/script/DeployAuction.s.sol @@ -4,7 +4,7 @@ pragma solidity >=0.8.18; import "./BaseScript.s.sol"; // Deploy a contract to a deterministic address with create2 -contract DeployAprOracle is BaseScript { +contract DeployAuction is BaseScript { function run() external { vm.startBroadcast(); diff --git a/src/Auctions/Auction.sol b/src/Auctions/Auction.sol index e60e3ec..23a6e35 100644 --- a/src/Auctions/Auction.sol +++ b/src/Auctions/Auction.sol @@ -2,29 +2,15 @@ pragma solidity >=0.8.18; import {Maths} from "../libraries/Maths.sol"; -import {Governance} from "../utils/Governance.sol"; +import {ITaker} from "../interfaces/ITaker.sol"; +import {GPv2Order} from "../libraries/GPv2Order.sol"; +import {Governance2Step} from "../utils/Governance2Step.sol"; import {ERC20} from "@openzeppelin/contracts/token/ERC20/ERC20.sol"; import {SafeERC20} from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol"; import {ReentrancyGuard} from "@openzeppelin/contracts/security/ReentrancyGuard.sol"; -import {ITaker} from "../interfaces/ITaker.sol"; - -/// @notice Interface that the optional `hook` contract should implement if the non-standard logic is desired. -interface IHook { - function kickable(address _fromToken) external view returns (uint256); - - function auctionKicked(address _fromToken) external returns (uint256); - function preTake( - address _fromToken, - uint256 _amountToTake, - uint256 _amountToPay - ) external; - - function postTake( - address _toToken, - uint256 _amountTaken, - uint256 _amountPayed - ) external; +interface ICowSettlement { + function domainSeparator() external view returns (bytes32); } /** @@ -32,34 +18,21 @@ interface IHook { * @author yearn.fi * @notice General use dutch auction contract for token sales. */ -contract Auction is Governance, ReentrancyGuard { +contract Auction is Governance2Step, ReentrancyGuard { + using GPv2Order for GPv2Order.Data; using SafeERC20 for ERC20; /// @notice Emitted when a new auction is enabled - event AuctionEnabled( - bytes32 auctionId, - address indexed from, - address indexed to, - address indexed auctionAddress - ); + event AuctionEnabled(address indexed from, address indexed to); /// @notice Emitted when an auction is disabled. - event AuctionDisabled( - bytes32 auctionId, - address indexed from, - address indexed to, - address indexed auctionAddress - ); + event AuctionDisabled(address indexed from, address indexed to); /// @notice Emitted when auction has been kicked. - event AuctionKicked(bytes32 auctionId, uint256 available); + event AuctionKicked(address from, uint256 available); - /// @notice Emitted when any amount of an active auction was taken. - event AuctionTaken( - bytes32 auctionId, - uint256 amountTaken, - uint256 amountLeft - ); + /// @notice Emitted when the starting price is updated. + event UpdatedStartingPrice(uint256 startingPrice); /// @dev Store address and scaler in one slot. struct TokenInfo { @@ -69,20 +42,9 @@ contract Auction is Governance, ReentrancyGuard { /// @notice Store all the auction specific information. struct AuctionInfo { - TokenInfo fromInfo; - uint96 kicked; - address receiver; + uint64 kicked; + uint64 scaler; uint128 initialAvailable; - uint128 currentAvailable; - } - - /// @notice Store the hook address and each flag in one slot. - struct Hook { - address hook; - bool kickable; - bool kick; - bool preTake; - bool postTake; } uint256 internal constant WAD = 1e18; @@ -91,11 +53,19 @@ contract Auction is Governance, ReentrancyGuard { uint256 internal constant MINUTE_HALF_LIFE = 0.988514020352896135_356867505 * 1e27; // 0.5^(1/60) + address internal constant COW_SETTLEMENT = + 0x9008D19f58AAbD9eD0D60971565AA8510560ab41; + + address internal constant VAULT_RELAYER = + 0xC92E8bdf79f0507f65a392b0ab4667716BFE0110; + + bytes32 internal immutable COW_DOMAIN_SEPARATOR; + /// @notice Struct to hold the info for `want`. TokenInfo internal wantInfo; - /// @notice Contract to call during write functions. - Hook internal hook_; + /// @notice The address that will receive the funds in the auction. + address public receiver; /// @notice The amount to start the auction at. uint256 public startingPrice; @@ -103,40 +73,36 @@ contract Auction is Governance, ReentrancyGuard { /// @notice The time that each auction lasts. uint256 public auctionLength; - /// @notice The minimum time to wait between auction 'kicks'. - uint256 public auctionCooldown; - - /// @notice Mapping from an auction ID to its struct. - mapping(bytes32 => AuctionInfo) public auctions; + /// @notice Mapping from `from` token to its struct. + mapping(address => AuctionInfo) public auctions; /// @notice Array of all the enabled auction for this contract. - bytes32[] public enabledAuctions; + address[] public enabledAuctions; - constructor() Governance(msg.sender) {} + constructor() Governance2Step(msg.sender) { + COW_DOMAIN_SEPARATOR = ICowSettlement(COW_SETTLEMENT).domainSeparator(); + } /** * @notice Initializes the Auction contract with initial parameters. * @param _want Address this auction is selling to. - * @param _hook Address of the hook contract (optional). + * @param _receiver Address that will receive the funds from the auction. * @param _governance Address of the contract governance. * @param _auctionLength Duration of each auction in seconds. - * @param _auctionCooldown Cooldown period between auctions in seconds. * @param _startingPrice Starting price for each auction. */ function initialize( address _want, - address _hook, + address _receiver, address _governance, uint256 _auctionLength, - uint256 _auctionCooldown, uint256 _startingPrice - ) external virtual { + ) public virtual { require(auctionLength == 0, "initialized"); require(_want != address(0), "ZERO ADDRESS"); require(_auctionLength != 0, "length"); - require(_auctionLength <= _auctionCooldown, "cooldown"); require(_startingPrice != 0, "starting price"); - + require(_receiver != address(0), "receiver"); // Cannot have more than 18 decimals. uint256 decimals = ERC20(_want).decimals(); require(decimals <= 18, "unsupported decimals"); @@ -147,22 +113,11 @@ contract Auction is Governance, ReentrancyGuard { scaler: uint96(WAD / 10 ** decimals) }); - // If we are using a hook. - if (_hook != address(0)) { - // All flags default to true. - hook_ = Hook({ - hook: _hook, - kickable: true, - kick: true, - preTake: true, - postTake: true - }); - } - + receiver = _receiver; governance = _governance; auctionLength = _auctionLength; - auctionCooldown = _auctionCooldown; startingPrice = _startingPrice; + emit UpdatedStartingPrice(_startingPrice); } /*////////////////////////////////////////////////////////////// @@ -178,142 +133,107 @@ contract Auction is Governance, ReentrancyGuard { } /** - * @notice Get the address of the hook if any. - * @return . The hook. + * @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 hook() external view virtual returns (address) { - return hook_.hook; - } + function available(address _from) public view virtual returns (uint256) { + if (!isActive(_from)) return 0; - /** - * @notice Get the current status of which hooks are being used. - * @return . If the kickable hook is used. - * @return . If the kick hook is used. - * @return . If the preTake hook is used. - * @return . If the postTake hook is used. - */ - function getHookFlags() - external - view - virtual - returns (bool, bool, bool, bool) - { - Hook memory _hook = hook_; - return (_hook.kickable, _hook.kick, _hook.preTake, _hook.postTake); + return + Maths.min( + auctions[_from].initialAvailable, + ERC20(_from).balanceOf(address(this)) + ); } /** - * @notice Get the length of the enabled auctions array. + * @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 numberOfEnabledAuctions() external view virtual returns (uint256) { - return enabledAuctions.length; + function kicked(address _from) external view virtual returns (uint256) { + return auctions[_from].kicked; } /** - * @notice Get the unique auction identifier. - * @param _from The address of the token to sell. - * @return bytes32 A unique auction identifier. + * @notice Check if the auction is active. + * @param _from The address of the token to be auctioned. + * @return . Whether the auction is active. */ - function getAuctionId(address _from) public view virtual returns (bytes32) { - return keccak256(abi.encodePacked(_from, want(), address(this))); + function isActive(address _from) public view virtual returns (bool) { + return auctions[_from].kicked + auctionLength >= block.timestamp; } /** - * @notice Retrieves information about a specific auction. - * @param _auctionId The unique identifier of the auction. - * @return _from The address of the token to sell. - * @return _to The address of the token to buy. - * @return _kicked The timestamp of the last kick. - * @return _available The current available amount for the auction. + * @notice Get all the enabled auctions. */ - function auctionInfo( - bytes32 _auctionId - ) - public + function getAllEnabledAuctions() + external view virtual - returns ( - address _from, - address _to, - uint256 _kicked, - uint256 _available - ) + returns (address[] memory) { - AuctionInfo memory auction = auctions[_auctionId]; - - return ( - auction.fromInfo.tokenAddress, - want(), - auction.kicked, - auction.kicked + auctionLength > block.timestamp - ? auction.currentAvailable - : 0 - ); + return enabledAuctions; } /** * @notice Get the pending amount available for the next auction. * @dev Defaults to the auctions balance of the from token if no hook. - * @param _auctionId The unique identifier of the auction. + * @param _from The address of the token to be auctioned. * @return uint256 The amount that can be kicked into the auction. */ - function kickable( - bytes32 _auctionId - ) external view virtual returns (uint256) { + function kickable(address _from) external view virtual returns (uint256) { // If not enough time has passed then `kickable` is 0. - if (auctions[_auctionId].kicked + auctionCooldown > block.timestamp) { - return 0; - } + if (isActive(_from)) return 0; - // Check if we have a hook to call. - Hook memory _hook = hook_; - if (_hook.kickable) { - // If so default to the hooks logic. - return - IHook(_hook.hook).kickable( - auctions[_auctionId].fromInfo.tokenAddress - ); - } else { - // Else just use the full balance of this contract. - return - ERC20(auctions[_auctionId].fromInfo.tokenAddress).balanceOf( - address(this) - ); - } + // Use the full balance of this contract. + return ERC20(_from).balanceOf(address(this)); } /** - * @notice Gets the amount of `want` needed to buy a specific amount of `from`. - * @param _auctionId The unique identifier of the auction. - * @param _amountToTake The amount of `from` to take in the auction. + * @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( - bytes32 _auctionId, - uint256 _amountToTake + address _from ) external view virtual returns (uint256) { return _getAmountNeeded( - auctions[_auctionId], - _amountToTake, + 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. + * @param _amountToTake The amount of `from` to take in the auction. + * @return . The amount of `want` needed to fulfill the take amount. + */ + function getAmountNeeded( + address _from, + uint256 _amountToTake + ) external view virtual returns (uint256) { + return + _getAmountNeeded(auctions[_from], _amountToTake, block.timestamp); + } + /** * @notice Gets the amount of `want` needed to buy a specific amount of `from` at a specific timestamp. - * @param _auctionId The unique identifier of the auction. + * @param _from The address of the token to be auctioned. * @param _amountToTake The amount `from` to take in the auction. * @param _timestamp The specific timestamp for calculating the amount needed. * @return . The amount of `want` needed to fulfill the take amount. */ function getAmountNeeded( - bytes32 _auctionId, + address _from, uint256 _amountToTake, uint256 _timestamp ) external view virtual returns (uint256) { - return - _getAmountNeeded(auctions[_auctionId], _amountToTake, _timestamp); + return _getAmountNeeded(auctions[_from], _amountToTake, _timestamp); } /** @@ -327,11 +247,11 @@ contract Auction is Governance, ReentrancyGuard { return // Scale _amountToTake to 1e18 (_amountToTake * - _auction.fromInfo.scaler * + _auction.scaler * // Price is always 1e18 _price( _auction.kicked, - _auction.initialAvailable * _auction.fromInfo.scaler, + _auction.initialAvailable * _auction.scaler, _timestamp )) / 1e18 / @@ -341,29 +261,28 @@ contract Auction is Governance, ReentrancyGuard { /** * @notice Gets the price of the auction at the current timestamp. - * @param _auctionId The unique identifier of the auction. + * @param _from The address of the token to be auctioned. * @return . The price of the auction. */ - function price(bytes32 _auctionId) external view virtual returns (uint256) { - return price(_auctionId, block.timestamp); + function price(address _from) external view virtual returns (uint256) { + return price(_from, block.timestamp); } /** * @notice Gets the price of the auction at a specific timestamp. - * @param _auctionId The unique identifier of the auction. + * @param _from The address of the token to be auctioned. * @param _timestamp The specific timestamp for calculating the price. * @return . The price of the auction. */ function price( - bytes32 _auctionId, + address _from, uint256 _timestamp ) public view virtual returns (uint256) { // Get unscaled price and scale it down. return _price( - auctions[_auctionId].kicked, - auctions[_auctionId].initialAvailable * - auctions[_auctionId].fromInfo.scaler, + auctions[_from].kicked, + auctions[_from].initialAvailable * auctions[_from].scaler, _timestamp ) / wantInfo.scaler; } @@ -386,7 +305,7 @@ contract Auction is Governance, ReentrancyGuard { if (secondsElapsed > auctionLength) return 0; - // Exponential decay from https://github.com/ajna-finance/ajna-core/blob/master/src/libraries/helpers/PoolHelper.sol + // Exponential step decay from https://github.com/ajna-finance/ajna-core/blob/master/src/libraries/helpers/PoolHelper.sol uint256 hoursComponent = 1e27 >> (secondsElapsed / 3600); uint256 minutesComponent = Maths.rpow( MINUTE_HALF_LIFE, @@ -405,53 +324,26 @@ contract Auction is Governance, ReentrancyGuard { /** * @notice Enables a new auction. - * @dev Uses governance as the receiver. * @param _from The address of the token to be auctioned. - * @return . The unique identifier of the enabled auction. */ - function enable(address _from) external virtual returns (bytes32) { - return enable(_from, msg.sender); - } - - /** - * @notice Enables a new auction. - * @param _from The address of the token to be auctioned. - * @param _receiver The address that will receive the funds in the auction. - * @return _auctionId The unique identifier of the enabled auction. - */ - function enable( - address _from, - address _receiver - ) public virtual onlyGovernance returns (bytes32 _auctionId) { + function enable(address _from) external virtual onlyGovernance { address _want = want(); require(_from != address(0) && _from != _want, "ZERO ADDRESS"); - require( - _receiver != address(0) && _receiver != address(this), - "receiver" - ); + require(auctions[_from].scaler == 0, "already enabled"); + // Cannot have more than 18 decimals. uint256 decimals = ERC20(_from).decimals(); require(decimals <= 18, "unsupported decimals"); - // Calculate the id. - _auctionId = getAuctionId(_from); - - require( - auctions[_auctionId].fromInfo.tokenAddress == address(0), - "already enabled" - ); - // Store all needed info. - auctions[_auctionId].fromInfo = TokenInfo({ - tokenAddress: _from, - scaler: uint96(WAD / 10 ** decimals) - }); - auctions[_auctionId].receiver = _receiver; + auctions[_from].scaler = uint64(WAD / 10 ** decimals); + + ERC20(_from).safeApprove(VAULT_RELAYER, type(uint256).max); // Add to the array. - enabledAuctions.push(_auctionId); + enabledAuctions.push(_from); - emit AuctionEnabled(_auctionId, _from, _want, address(this)); + emit AuctionEnabled(_from, _want); } /** @@ -473,23 +365,20 @@ contract Auction is Governance, ReentrancyGuard { address _from, uint256 _index ) public virtual onlyGovernance { - bytes32 _auctionId = getAuctionId(_from); - // Make sure the auction was enabled. - require( - auctions[_auctionId].fromInfo.tokenAddress != address(0), - "not enabled" - ); + require(auctions[_from].scaler != 0, "not enabled"); // Remove the struct. - delete auctions[_auctionId]; + delete auctions[_from]; + + ERC20(_from).safeApprove(VAULT_RELAYER, 0); // Remove the auction ID from the array. - bytes32[] memory _enabledAuctions = enabledAuctions; - if (_enabledAuctions[_index] != _auctionId) { + address[] memory _enabledAuctions = enabledAuctions; + if (_enabledAuctions[_index] != _from) { // If the _index given is not the id find it. for (uint256 i = 0; i < _enabledAuctions.length; ++i) { - if (_enabledAuctions[i] == _auctionId) { + if (_enabledAuctions[i] == _from) { _index = i; break; } @@ -508,32 +397,27 @@ contract Auction is Governance, ReentrancyGuard { // Pop the id off the array. enabledAuctions.pop(); - emit AuctionDisabled(_auctionId, _from, want(), address(this)); + emit AuctionDisabled(_from, want()); } /** - * @notice Set the flags to be used with hook. - * @param _kickable If the kickable hook should be used. - * @param _kick If the kick hook should be used. - * @param _preTake If the preTake hook should be used. - * @param _postTake If the postTake should be used. + * @notice Sets the starting price for the auction. + * @param _startingPrice The new starting price for the auction. */ - function setHookFlags( - bool _kickable, - bool _kick, - bool _preTake, - bool _postTake + function setStartingPrice( + uint256 _startingPrice ) external virtual onlyGovernance { - address _hook = hook_.hook; - require(_hook != address(0), "no hook set"); - - hook_ = Hook({ - hook: _hook, - kickable: _kickable, - kick: _kick, - preTake: _preTake, - postTake: _postTake - }); + require(_startingPrice != 0, "starting price"); + + // Don't change the price when an auction is active. + address[] memory _enabledAuctions = enabledAuctions; + for (uint256 i = 0; i < _enabledAuctions.length; ++i) { + require(!isActive(_enabledAuctions[i]), "active auction"); + } + + startingPrice = _startingPrice; + + emit UpdatedStartingPrice(_startingPrice); } /*////////////////////////////////////////////////////////////// @@ -542,102 +426,94 @@ contract Auction is Governance, ReentrancyGuard { /** * @notice Kicks off an auction, updating its status and making funds available for bidding. - * @param _auctionId The unique identifier of the auction. - * @return available The available amount for bidding on in the auction. + * @param _from The address of the token to be auctioned. + * @return _available The available amount for bidding on in the auction. */ function kick( - bytes32 _auctionId - ) external virtual nonReentrant returns (uint256 available) { - address _fromToken = auctions[_auctionId].fromInfo.tokenAddress; - require(_fromToken != address(0), "not enabled"); + address _from + ) external virtual nonReentrant returns (uint256 _available) { + require(auctions[_from].scaler != 0, "not enabled"); require( - block.timestamp > auctions[_auctionId].kicked + auctionCooldown, + block.timestamp > auctions[_from].kicked + auctionLength, "too soon" ); - Hook memory _hook = hook_; - // Use hook if defined. - if (_hook.kick) { - available = IHook(_hook.hook).auctionKicked(_fromToken); - } else { - // Else just use current balance. - available = ERC20(_fromToken).balanceOf(address(this)); - } + // Just use current balance. + _available = ERC20(_from).balanceOf(address(this)); - require(available != 0, "nothing to kick"); + require(_available != 0, "nothing to kick"); // Update the auctions status. - auctions[_auctionId].kicked = uint96(block.timestamp); - auctions[_auctionId].initialAvailable = uint128(available); - auctions[_auctionId].currentAvailable = uint128(available); + auctions[_from].kicked = uint64(block.timestamp); + auctions[_from].initialAvailable = uint128(_available); - emit AuctionKicked(_auctionId, available); + emit AuctionKicked(_from, _available); } /** * @notice Take the token being sold in a live auction. * @dev Defaults to taking the full amount and sending to the msg sender. - * @param _auctionId The unique identifier of the auction. + * @param _from The address of the token to be auctioned. * @return . The amount of fromToken taken in the auction. */ - function take(bytes32 _auctionId) external virtual returns (uint256) { - return _take(_auctionId, type(uint256).max, msg.sender, new bytes(0)); + function take(address _from) external virtual returns (uint256) { + return _take(_from, type(uint256).max, msg.sender, new bytes(0)); } /** * @notice Take the token being sold in a live auction with a specified maximum amount. - * @dev Uses the sender's address as the receiver. - * @param _auctionId The unique identifier of the auction. + * @dev Will send the funds to the msg sender. + * @param _from The address of the token to be auctioned. * @param _maxAmount The maximum amount of fromToken to take in the auction. * @return . The amount of fromToken taken in the auction. */ function take( - bytes32 _auctionId, + address _from, uint256 _maxAmount ) external virtual returns (uint256) { - return _take(_auctionId, _maxAmount, msg.sender, new bytes(0)); + return _take(_from, _maxAmount, msg.sender, new bytes(0)); } /** * @notice Take the token being sold in a live auction. - * @param _auctionId The unique identifier of the auction. + * @param _from The address of the token to be auctioned. * @param _maxAmount The maximum amount of fromToken to take in the auction. * @param _receiver The address that will receive the fromToken. * @return _amountTaken The amount of fromToken taken in the auction. */ function take( - bytes32 _auctionId, + address _from, uint256 _maxAmount, address _receiver ) external virtual returns (uint256) { - return _take(_auctionId, _maxAmount, _receiver, new bytes(0)); + return _take(_from, _maxAmount, _receiver, new bytes(0)); } /** * @notice Take the token being sold in a live auction. - * @param _auctionId The unique identifier of the auction. + * @param _from The address of the token to be auctioned. * @param _maxAmount The maximum amount of fromToken to take in the auction. * @param _receiver The address that will receive the fromToken. * @param _data The data signify the callback should be used and sent with it. * @return _amountTaken The amount of fromToken taken in the auction. */ function take( - bytes32 _auctionId, + address _from, uint256 _maxAmount, address _receiver, bytes calldata _data ) external virtual returns (uint256) { - return _take(_auctionId, _maxAmount, _receiver, _data); + return _take(_from, _maxAmount, _receiver, _data); } /// @dev Implements the take of the auction. function _take( - bytes32 _auctionId, + address _from, uint256 _maxAmount, address _receiver, bytes memory _data ) internal virtual nonReentrant returns (uint256 _amountTaken) { - AuctionInfo memory auction = auctions[_auctionId]; + AuctionInfo memory auction = auctions[_from]; // Make sure the auction is active. require( auction.kicked + auctionLength >= block.timestamp, @@ -645,9 +521,8 @@ contract Auction is Governance, 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( @@ -658,34 +533,14 @@ contract Auction is Governance, ReentrancyGuard { require(needed != 0, "zero needed"); - // How much is left in this auction. - uint256 left; - unchecked { - left = auction.currentAvailable - _amountTaken; - } - auctions[_auctionId].currentAvailable = uint128(left); - - Hook memory _hook = hook_; - if (_hook.preTake) { - // Use hook if defined. - IHook(_hook.hook).preTake( - auction.fromInfo.tokenAddress, - _amountTaken, - needed - ); - } - // Send `from`. - ERC20(auction.fromInfo.tokenAddress).safeTransfer( - _receiver, - _amountTaken - ); + ERC20(_from).safeTransfer(_receiver, _amountTaken); // If the caller has specified data. if (_data.length != 0) { // Do the callback. ITaker(_receiver).auctionTakeCallback( - auction.fromInfo.tokenAddress, + _from, msg.sender, _amountTaken, needed, @@ -697,13 +552,61 @@ contract Auction is Governance, ReentrancyGuard { address _want = want(); // Pull `want`. - ERC20(_want).safeTransferFrom(msg.sender, auction.receiver, needed); + ERC20(_want).safeTransferFrom(msg.sender, receiver, needed); + } - // Post take hook if defined. - if (_hook.postTake) { - IHook(_hook.hook).postTake(_want, _amountTaken, needed); - } + /// @dev Validates a COW order signature. + function isValidSignature( + bytes32 _hash, + bytes calldata signature + ) external view returns (bytes4) { + // Make sure `_take` has not already been entered. + require(!_reentrancyGuardEntered(), "ReentrancyGuard: reentrant call"); - emit AuctionTaken(_auctionId, _amountTaken, left); + // Decode the signature to get the order. + GPv2Order.Data memory order = abi.decode(signature, (GPv2Order.Data)); + + AuctionInfo memory auction = auctions[address(order.sellToken)]; + + // Get the current amount needed for the auction. + uint256 paymentAmount = _getAmountNeeded( + auction, + order.sellAmount, + block.timestamp + ); + + // Verify the order details. + require(_hash == order.hash(COW_DOMAIN_SEPARATOR), "bad order"); + require(paymentAmount != 0, "zero amount"); + require(available(address(order.sellToken)) != 0, "zero available"); + require(order.feeAmount == 0, "fee"); + require(order.partiallyFillable, "partial fill"); + 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.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/Auctions/AuctionFactory.sol b/src/Auctions/AuctionFactory.sol index b379366..5cc71c5 100644 --- a/src/Auctions/AuctionFactory.sol +++ b/src/Auctions/AuctionFactory.sol @@ -12,9 +12,6 @@ contract AuctionFactory is Clonable { /// @notice The time that each auction lasts. uint256 public constant DEFAULT_AUCTION_LENGTH = 1 days; - /// @notice The minimum time to wait between auction 'kicks'. - uint256 public constant DEFAULT_AUCTION_COOLDOWN = 5 days; - /// @notice The amount to start the auction with. uint256 public constant DEFAULT_STARTING_PRICE = 1_000_000; @@ -35,10 +32,9 @@ contract AuctionFactory is Clonable { return _createNewAuction( _want, - address(0), + msg.sender, msg.sender, DEFAULT_AUCTION_LENGTH, - DEFAULT_AUCTION_COOLDOWN, DEFAULT_STARTING_PRICE ); } @@ -46,20 +42,19 @@ contract AuctionFactory is Clonable { /** * @notice Creates a new auction contract. * @param _want Address of the token users will bid with. - * @param _hook Address of the hook contract if any. + * @param _receiver Address that will receive the funds in the auction. * @return _newAuction Address of the newly created auction contract. */ function createNewAuction( address _want, - address _hook + address _receiver ) external returns (address) { return _createNewAuction( _want, - _hook, + _receiver, msg.sender, DEFAULT_AUCTION_LENGTH, - DEFAULT_AUCTION_COOLDOWN, DEFAULT_STARTING_PRICE ); } @@ -67,22 +62,21 @@ contract AuctionFactory is Clonable { /** * @notice Creates a new auction contract. * @param _want Address of the token users will bid with. - * @param _hook Address of the hook contract if any. + * @param _receiver Address that will receive the funds in the auction. * @param _governance Address allowed to enable and disable auctions. * @return _newAuction Address of the newly created auction contract. */ function createNewAuction( address _want, - address _hook, + address _receiver, address _governance ) external returns (address) { return _createNewAuction( _want, - _hook, + _receiver, _governance, DEFAULT_AUCTION_LENGTH, - DEFAULT_AUCTION_COOLDOWN, DEFAULT_STARTING_PRICE ); } @@ -90,51 +84,23 @@ contract AuctionFactory is Clonable { /** * @notice Creates a new auction contract. * @param _want Address of the token users will bid with. - * @param _hook Address of the hook contract if any. + * @param _receiver Address that will receive the funds in the auction. * @param _governance Address allowed to enable and disable auctions. * @param _auctionLength Length of the auction in seconds. * @return _newAuction Address of the newly created auction contract. */ function createNewAuction( address _want, - address _hook, + address _receiver, address _governance, uint256 _auctionLength ) external returns (address) { return _createNewAuction( _want, - _hook, - _governance, - _auctionLength, - DEFAULT_AUCTION_COOLDOWN, - DEFAULT_STARTING_PRICE - ); - } - - /** - * @notice Creates a new auction contract. - * @param _want Address of the token users will bid with. - * @param _hook Address of the hook contract if any. - * @param _governance Address allowed to enable and disable auctions. - * @param _auctionLength Length of the auction in seconds. - * @param _auctionCooldown Minimum time period between kicks in seconds. - * @return _newAuction Address of the newly created auction contract. - */ - function createNewAuction( - address _want, - address _hook, - address _governance, - uint256 _auctionLength, - uint256 _auctionCooldown - ) external returns (address) { - return - _createNewAuction( - _want, - _hook, + _receiver, _governance, _auctionLength, - _auctionCooldown, DEFAULT_STARTING_PRICE ); } @@ -142,29 +108,26 @@ contract AuctionFactory is Clonable { /** * @notice Creates a new auction contract. * @param _want Address of the token users will bid with. - * @param _hook Address of the hook contract if any. + * @param _receiver Address that will receive the funds in the auction. * @param _governance Address allowed to enable and disable auctions. * @param _auctionLength Length of the auction in seconds. - * @param _auctionCooldown Minimum time period between kicks in seconds. * @param _startingPrice Starting price for the auction (no decimals). * NOTE: The starting price should be without decimals (1k == 1_000). * @return _newAuction Address of the newly created auction contract. */ function createNewAuction( address _want, - address _hook, + address _receiver, address _governance, uint256 _auctionLength, - uint256 _auctionCooldown, uint256 _startingPrice ) external returns (address) { return _createNewAuction( _want, - _hook, + _receiver, _governance, _auctionLength, - _auctionCooldown, _startingPrice ); } @@ -174,20 +137,18 @@ contract AuctionFactory is Clonable { */ function _createNewAuction( address _want, - address _hook, + address _receiver, address _governance, uint256 _auctionLength, - uint256 _auctionCooldown, uint256 _startingPrice ) internal returns (address _newAuction) { _newAuction = _clone(); Auction(_newAuction).initialize( _want, - _hook, + _receiver, _governance, _auctionLength, - _auctionCooldown, _startingPrice ); diff --git a/src/Auctions/DumperAuction.sol b/src/Auctions/DumperAuction.sol deleted file mode 100644 index 71457bf..0000000 --- a/src/Auctions/DumperAuction.sol +++ /dev/null @@ -1,612 +0,0 @@ -// SPDX-License-Identifier: AGPL-3.0 -pragma solidity >=0.8.18; - -import {Maths} from "../libraries/Maths.sol"; -import {Governance2Step} from "../utils/Governance2Step.sol"; -import {ERC20} from "@openzeppelin/contracts/token/ERC20/ERC20.sol"; -import {SafeERC20} from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol"; -import {ReentrancyGuard} from "@openzeppelin/contracts/security/ReentrancyGuard.sol"; -import {ITaker} from "../interfaces/ITaker.sol"; -import {GPv2Order} from "../libraries/GPv2Order.sol"; - -interface ICowSettlement { - function domainSeparator() external view returns (bytes32); -} - -/** - * @title Auction - * @author yearn.fi - * @notice General use dutch auction contract for token sales. - */ -contract DumperAuction is Governance2Step, ReentrancyGuard { - using GPv2Order for GPv2Order.Data; - using SafeERC20 for ERC20; - - /// @notice Emitted when a new auction is enabled - event AuctionEnabled(address indexed from, address indexed to); - - /// @notice Emitted when an auction is disabled. - event AuctionDisabled(address indexed from, address indexed to); - - /// @notice Emitted when auction has been kicked. - event AuctionKicked(address from, uint256 available); - - /// @dev Store address and scaler in one slot. - struct TokenInfo { - address tokenAddress; - uint96 scaler; - } - - /// @notice Store all the auction specific information. - struct AuctionInfo { - uint64 kicked; - uint64 scaler; - uint128 initialAvailable; - } - - uint256 internal constant WAD = 1e18; - - /// @notice Used for the price decay. - uint256 internal constant MINUTE_HALF_LIFE = - 0.988514020352896135_356867505 * 1e27; // 0.5^(1/60) - - address internal constant COW_SETTLEMENT = - 0x9008D19f58AAbD9eD0D60971565AA8510560ab41; - - address internal constant VAULT_RELAYER = - 0xC92E8bdf79f0507f65a392b0ab4667716BFE0110; - - bytes32 internal immutable COW_DOMAIN_SEPARATOR; - - /// @notice Struct to hold the info for `want`. - TokenInfo internal wantInfo; - - /// @notice The address that will receive the funds in the auction. - address public receiver; - - /// @notice The amount to start the auction at. - uint256 public startingPrice; - - /// @notice The time that each auction lasts. - uint256 public auctionLength; - - /// @notice Mapping from `from` token to its struct. - mapping(address => AuctionInfo) public auctions; - - /// @notice Array of all the enabled auction for this contract. - address[] public enabledAuctions; - - constructor() Governance2Step(msg.sender) { - COW_DOMAIN_SEPARATOR = ICowSettlement(COW_SETTLEMENT).domainSeparator(); - } - - /** - * @notice Initializes the Auction contract with initial parameters. - * @param _want Address this auction is selling to. - * @param _receiver Address that will receive the funds from the auction. - * @param _governance Address of the contract governance. - * @param _auctionLength Duration of each auction in seconds. - * @param _startingPrice Starting price for each auction. - */ - function initialize( - address _want, - address _receiver, - address _governance, - uint256 _auctionLength, - uint256 _startingPrice - ) external virtual { - require(auctionLength == 0, "initialized"); - require(_want != address(0), "ZERO ADDRESS"); - require(_auctionLength != 0, "length"); - require(_startingPrice != 0, "starting price"); - require(_receiver != address(0), "receiver"); - // Cannot have more than 18 decimals. - uint256 decimals = ERC20(_want).decimals(); - require(decimals <= 18, "unsupported decimals"); - - // Set variables - wantInfo = TokenInfo({ - tokenAddress: _want, - scaler: uint96(WAD / 10 ** decimals) - }); - - receiver = _receiver; - governance = _governance; - auctionLength = _auctionLength; - startingPrice = _startingPrice; - } - - /*////////////////////////////////////////////////////////////// - VIEW METHODS - //////////////////////////////////////////////////////////////*/ - - /** - * @notice Get the address of this auctions want token. - * @return . The want token. - */ - function want() public view virtual returns (address) { - return wantInfo.tokenAddress; - } - - /** - * @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. - */ - function getAllEnabledAuctions() - external - view - virtual - returns (address[] memory) - { - return enabledAuctions; - } - - /** - * @notice Get the pending amount available for the next auction. - * @dev Defaults to the auctions balance of the from token if no hook. - * @param _from The address of the token to be auctioned. - * @return uint256 The amount that can be kicked into the auction. - */ - function kickable(address _from) external view virtual returns (uint256) { - // If not enough time has passed then `kickable` is 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. - * @param _amountToTake The amount of `from` to take in the auction. - * @return . The amount of `want` needed to fulfill the take amount. - */ - function getAmountNeeded( - address _from, - uint256 _amountToTake - ) external view virtual returns (uint256) { - return - _getAmountNeeded(auctions[_from], _amountToTake, block.timestamp); - } - - /** - * @notice Gets the amount of `want` needed to buy a specific amount of `from` at a specific timestamp. - * @param _from The address of the token to be auctioned. - * @param _amountToTake The amount `from` to take in the auction. - * @param _timestamp The specific timestamp for calculating the amount needed. - * @return . The amount of `want` needed to fulfill the take amount. - */ - function getAmountNeeded( - address _from, - uint256 _amountToTake, - uint256 _timestamp - ) external view virtual returns (uint256) { - return _getAmountNeeded(auctions[_from], _amountToTake, _timestamp); - } - - /** - * @dev Return the amount of `want` needed to buy `_amountToTake`. - */ - function _getAmountNeeded( - AuctionInfo memory _auction, - uint256 _amountToTake, - uint256 _timestamp - ) internal view virtual returns (uint256) { - return - // Scale _amountToTake to 1e18 - (_amountToTake * - _auction.scaler * - // Price is always 1e18 - _price( - _auction.kicked, - _auction.initialAvailable * _auction.scaler, - _timestamp - )) / - 1e18 / - // Scale back down to want. - wantInfo.scaler; - } - - /** - * @notice Gets the price of the auction at the current timestamp. - * @param _from The address of the token to be auctioned. - * @return . The price of the auction. - */ - function price(address _from) external view virtual returns (uint256) { - return price(_from, block.timestamp); - } - - /** - * @notice Gets the price of the auction at a specific timestamp. - * @param _from The address of the token to be auctioned. - * @param _timestamp The specific timestamp for calculating the price. - * @return . The price of the auction. - */ - function price( - address _from, - uint256 _timestamp - ) public view virtual returns (uint256) { - // Get unscaled price and scale it down. - return - _price( - auctions[_from].kicked, - auctions[_from].initialAvailable * auctions[_from].scaler, - _timestamp - ) / wantInfo.scaler; - } - - /** - * @dev Internal function to calculate the scaled price based on auction parameters. - * @param _kicked The timestamp the auction was kicked. - * @param _available The initial available amount scaled 1e18. - * @param _timestamp The specific timestamp for calculating the price. - * @return . The calculated price scaled to 1e18. - */ - function _price( - uint256 _kicked, - uint256 _available, - uint256 _timestamp - ) internal view virtual returns (uint256) { - if (_available == 0) return 0; - - uint256 secondsElapsed = _timestamp - _kicked; - - if (secondsElapsed > auctionLength) return 0; - - // Exponential step decay from https://github.com/ajna-finance/ajna-core/blob/master/src/libraries/helpers/PoolHelper.sol - uint256 hoursComponent = 1e27 >> (secondsElapsed / 3600); - uint256 minutesComponent = Maths.rpow( - MINUTE_HALF_LIFE, - (secondsElapsed % 3600) / 60 - ); - uint256 initialPrice = Maths.wdiv(startingPrice * 1e18, _available); - - return - (initialPrice * Maths.rmul(hoursComponent, minutesComponent)) / - 1e27; - } - - /*////////////////////////////////////////////////////////////// - SETTERS - //////////////////////////////////////////////////////////////*/ - - /** - * @notice Enables a new auction. - * @param _from The address of the token to be auctioned. - */ - function enable(address _from) external virtual onlyGovernance { - address _want = want(); - require(_from != address(0) && _from != _want, "ZERO ADDRESS"); - require(auctions[_from].scaler == 0, "already enabled"); - - // Cannot have more than 18 decimals. - uint256 decimals = ERC20(_from).decimals(); - require(decimals <= 18, "unsupported decimals"); - - // Store all needed info. - auctions[_from].scaler = uint64(WAD / 10 ** decimals); - - ERC20(_from).safeApprove(VAULT_RELAYER, type(uint256).max); - - // Add to the array. - enabledAuctions.push(_from); - - emit AuctionEnabled(_from, _want); - } - - /** - * @notice Disables an existing auction. - * @dev Only callable by governance. - * @param _from The address of the token being sold. - */ - function disable(address _from) external virtual { - disable(_from, 0); - } - - /** - * @notice Disables an existing auction. - * @dev Only callable by governance. - * @param _from The address of the token being sold. - * @param _index The index the auctionId is at in the array. - */ - function disable( - address _from, - uint256 _index - ) public virtual onlyGovernance { - // Make sure the auction was enabled. - require(auctions[_from].scaler != 0, "not enabled"); - - // Remove the struct. - delete auctions[_from]; - - ERC20(_from).safeApprove(VAULT_RELAYER, 0); - - // Remove the auction ID from the array. - address[] memory _enabledAuctions = enabledAuctions; - if (_enabledAuctions[_index] != _from) { - // If the _index given is not the id find it. - for (uint256 i = 0; i < _enabledAuctions.length; ++i) { - if (_enabledAuctions[i] == _from) { - _index = i; - break; - } - } - } - - // Move the id to the last spot if not there. - if (_index < _enabledAuctions.length - 1) { - _enabledAuctions[_index] = _enabledAuctions[ - _enabledAuctions.length - 1 - ]; - // Update the array. - enabledAuctions = _enabledAuctions; - } - - // Pop the id off the array. - enabledAuctions.pop(); - - emit AuctionDisabled(_from, want()); - } - - /** - * @notice Sets the starting price for the auction. - * @param _startingPrice The new starting price for the auction. - */ - function setStartingPrice( - uint256 _startingPrice - ) external virtual onlyGovernance { - require(_startingPrice != 0, "starting price"); - startingPrice = _startingPrice; - } - - /** - * @notice Sets the auction length. - * @param _auctionLength The new auction length. - */ - function setAuctionLength( - uint256 _auctionLength - ) external virtual onlyGovernance { - require(_auctionLength != 0, "length"); - require(_auctionLength < 1 weeks, "too long"); - - auctionLength = _auctionLength; - } - - /*////////////////////////////////////////////////////////////// - PARTICIPATE IN AUCTION - //////////////////////////////////////////////////////////////*/ - - /** - * @notice Kicks off an auction, updating its status and making funds available for bidding. - * @param _from The address of the token to be auctioned. - * @return _available The available amount for bidding on in the auction. - */ - function kick( - address _from - ) external virtual nonReentrant returns (uint256 _available) { - require(auctions[_from].scaler != 0, "not enabled"); - require( - block.timestamp > auctions[_from].kicked + auctionLength, - "too soon" - ); - - // Just use current balance. - _available = ERC20(_from).balanceOf(address(this)); - - require(_available != 0, "nothing to kick"); - - // Update the auctions status. - auctions[_from].kicked = uint64(block.timestamp); - auctions[_from].initialAvailable = uint128(_available); - - emit AuctionKicked(_from, _available); - } - - /** - * @notice Take the token being sold in a live auction. - * @dev Defaults to taking the full amount and sending to the msg sender. - * @param _from The address of the token to be auctioned. - * @return . The amount of fromToken taken in the auction. - */ - function take(address _from) external virtual returns (uint256) { - return _take(_from, type(uint256).max, msg.sender, new bytes(0)); - } - - /** - * @notice Take the token being sold in a live auction with a specified maximum amount. - * @dev Will send the funds to the msg sender. - * @param _from The address of the token to be auctioned. - * @param _maxAmount The maximum amount of fromToken to take in the auction. - * @return . The amount of fromToken taken in the auction. - */ - function take( - address _from, - uint256 _maxAmount - ) external virtual returns (uint256) { - return _take(_from, _maxAmount, msg.sender, new bytes(0)); - } - - /** - * @notice Take the token being sold in a live auction. - * @param _from The address of the token to be auctioned. - * @param _maxAmount The maximum amount of fromToken to take in the auction. - * @param _receiver The address that will receive the fromToken. - * @return _amountTaken The amount of fromToken taken in the auction. - */ - function take( - address _from, - uint256 _maxAmount, - address _receiver - ) external virtual returns (uint256) { - return _take(_from, _maxAmount, _receiver, new bytes(0)); - } - - /** - * @notice Take the token being sold in a live auction. - * @param _from The address of the token to be auctioned. - * @param _maxAmount The maximum amount of fromToken to take in the auction. - * @param _receiver The address that will receive the fromToken. - * @param _data The data signify the callback should be used and sent with it. - * @return _amountTaken The amount of fromToken taken in the auction. - */ - function take( - address _from, - uint256 _maxAmount, - address _receiver, - bytes calldata _data - ) external virtual returns (uint256) { - return _take(_from, _maxAmount, _receiver, _data); - } - - /// @dev Implements the take of the auction. - function _take( - address _from, - uint256 _maxAmount, - address _receiver, - bytes memory _data - ) internal virtual nonReentrant returns (uint256 _amountTaken) { - AuctionInfo memory auction = auctions[_from]; - // Make sure the auction is active. - require( - auction.kicked + auctionLength >= block.timestamp, - "not kicked" - ); - - // Max amount that can be taken. - uint256 _available = available(_from); - _amountTaken = _available > _maxAmount ? _maxAmount : _available; - - // Get the amount needed - uint256 needed = _getAmountNeeded( - auction, - _amountTaken, - block.timestamp - ); - - require(needed != 0, "zero needed"); - - // Send `from`. - ERC20(_from).safeTransfer(_receiver, _amountTaken); - - // If the caller has specified data. - if (_data.length != 0) { - // Do the callback. - ITaker(_receiver).auctionTakeCallback( - _from, - msg.sender, - _amountTaken, - needed, - _data - ); - } - - // Cache the want address. - address _want = want(); - - // Pull `want`. - ERC20(_want).safeTransferFrom(msg.sender, receiver, needed); - } - - /// @dev Validates a COW order signature. - function isValidSignature( - bytes32 _hash, - bytes calldata signature - ) external view returns (bytes4) { - // Make sure `_take` has not already been entered. - require(!_reentrancyGuardEntered(), "ReentrancyGuard: reentrant call"); - - // Decode the signature to get the order. - GPv2Order.Data memory order = abi.decode(signature, (GPv2Order.Data)); - - AuctionInfo memory auction = auctions[address(order.sellToken)]; - - // Get the current amount needed for the auction. - uint256 paymentAmount = _getAmountNeeded( - auction, - order.sellAmount, - block.timestamp - ); - - // Verify the order details. - require(_hash == order.hash(COW_DOMAIN_SEPARATOR), "bad order"); - require(paymentAmount != 0, "zero amount"); - require(available(address(order.sellToken)) != 0, "zero available"); - require(order.feeAmount == 0, "fee"); - require(order.partiallyFillable, "partial fill"); - 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.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/Auctions/DumperAuctionFactory.sol b/src/Auctions/DumperAuctionFactory.sol deleted file mode 100644 index 3e20e58..0000000 --- a/src/Auctions/DumperAuctionFactory.sol +++ /dev/null @@ -1,173 +0,0 @@ -// SPDX-License-Identifier: AGPL-3.0 -pragma solidity >=0.8.18; - -import {DumperAuction} from "./DumperAuction.sol"; -import {Clonable} from "../utils/Clonable.sol"; - -/// @title AuctionFactory -/// @notice Deploy a new Auction. -contract DumperAuctionFactory is Clonable { - event DeployedNewAuction(address indexed auction, address indexed want); - - /// @notice The time that each auction lasts. - uint256 public constant DEFAULT_AUCTION_LENGTH = 1 days; - - /// @notice The amount to start the auction with. - uint256 public constant DEFAULT_STARTING_PRICE = 1_000_000; - - /// @notice Full array of all auctions deployed through this factory. - address[] public auctions; - - constructor() { - // Deploy the original - original = address(new DumperAuction()); - } - - /** - * @notice Creates a new auction contract. - * @param _want Address of the token users will bid with. - * @return _newAuction Address of the newly created auction contract. - */ - function createNewAuction(address _want) external returns (address) { - return - _createNewAuction( - _want, - msg.sender, - msg.sender, - DEFAULT_AUCTION_LENGTH, - DEFAULT_STARTING_PRICE - ); - } - - /** - * @notice Creates a new auction contract. - * @param _want Address of the token users will bid with. - * @param _receiver Address that will receive the funds in the auction. - * @return _newAuction Address of the newly created auction contract. - */ - function createNewAuction( - address _want, - address _receiver - ) external returns (address) { - return - _createNewAuction( - _want, - _receiver, - msg.sender, - DEFAULT_AUCTION_LENGTH, - DEFAULT_STARTING_PRICE - ); - } - - /** - * @notice Creates a new auction contract. - * @param _want Address of the token users will bid with. - * @param _receiver Address that will receive the funds in the auction. - * @param _governance Address allowed to enable and disable auctions. - * @return _newAuction Address of the newly created auction contract. - */ - function createNewAuction( - address _want, - address _receiver, - address _governance - ) external returns (address) { - return - _createNewAuction( - _want, - _receiver, - _governance, - DEFAULT_AUCTION_LENGTH, - DEFAULT_STARTING_PRICE - ); - } - - /** - * @notice Creates a new auction contract. - * @param _want Address of the token users will bid with. - * @param _receiver Address that will receive the funds in the auction. - * @param _governance Address allowed to enable and disable auctions. - * @param _auctionLength Length of the auction in seconds. - * @return _newAuction Address of the newly created auction contract. - */ - function createNewAuction( - address _want, - address _receiver, - address _governance, - uint256 _auctionLength - ) external returns (address) { - return - _createNewAuction( - _want, - _receiver, - _governance, - _auctionLength, - DEFAULT_STARTING_PRICE - ); - } - - /** - * @notice Creates a new auction contract. - * @param _want Address of the token users will bid with. - * @param _receiver Address that will receive the funds in the auction. - * @param _governance Address allowed to enable and disable auctions. - * @param _auctionLength Length of the auction in seconds. - * @param _startingPrice Starting price for the auction (no decimals). - * NOTE: The starting price should be without decimals (1k == 1_000). - * @return _newAuction Address of the newly created auction contract. - */ - function createNewAuction( - address _want, - address _receiver, - address _governance, - uint256 _auctionLength, - uint256 _startingPrice - ) external returns (address) { - return - _createNewAuction( - _want, - _receiver, - _governance, - _auctionLength, - _startingPrice - ); - } - - /** - * @dev Deploys and initializes a new Auction - */ - function _createNewAuction( - address _want, - address _receiver, - address _governance, - uint256 _auctionLength, - uint256 _startingPrice - ) internal returns (address _newAuction) { - _newAuction = _clone(); - - DumperAuction(_newAuction).initialize( - _want, - _receiver, - _governance, - _auctionLength, - _startingPrice - ); - - auctions.push(_newAuction); - - emit DeployedNewAuction(_newAuction, _want); - } - - /** - * @notice Get the full list of auctions deployed through this factory. - */ - function getAllAuctions() external view returns (address[] memory) { - return auctions; - } - - /** - * @notice Get the total number of auctions deployed through this factory. - */ - function numberOfAuctions() external view returns (uint256) { - return auctions.length; - } -} diff --git a/src/Bases/Auctioneer/BaseAuctioneer.sol b/src/Bases/Auctioneer/BaseAuctioneer.sol index b7e5d8b..15a9d11 100644 --- a/src/Bases/Auctioneer/BaseAuctioneer.sol +++ b/src/Bases/Auctioneer/BaseAuctioneer.sol @@ -1,11 +1,7 @@ // SPDX-License-Identifier: AGPL-3.0 pragma solidity >=0.8.18; -import {Maths} from "../../libraries/Maths.sol"; -import {ERC20} from "@openzeppelin/contracts/token/ERC20/ERC20.sol"; -import {SafeERC20} from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol"; -import {ReentrancyGuard} from "@openzeppelin/contracts/security/ReentrancyGuard.sol"; -import {ITaker} from "../../interfaces/ITaker.sol"; +import {Auction} from "../../Auctions/Auction.sol"; import {BaseHealthCheck} from "../HealthCheck/BaseHealthCheck.sol"; /** @@ -13,607 +9,28 @@ import {BaseHealthCheck} from "../HealthCheck/BaseHealthCheck.sol"; * @author yearn.fi * @notice General use dutch auction contract for token sales. */ -abstract contract BaseAuctioneer is BaseHealthCheck, ReentrancyGuard { - using SafeERC20 for ERC20; - - /// @notice Emitted when a new auction is enabled - event AuctionEnabled( - bytes32 auctionId, - address indexed from, - address indexed to, - address indexed auctionAddress - ); - - /// @notice Emitted when an auction is disabled. - event AuctionDisabled( - bytes32 auctionId, - address indexed from, - address indexed to, - address indexed auctionAddress - ); - - /// @notice Emitted when auction has been kicked. - event AuctionKicked(bytes32 auctionId, uint256 available); - - /// @notice Emitted when any amount of an active auction was taken. - event AuctionTaken( - bytes32 auctionId, - uint256 amountTaken, - uint256 amountLeft - ); - - /// @dev Store address and scaler in one slot. - struct TokenInfo { - address tokenAddress; - uint96 scaler; - } - - /// @notice Store all the auction specific information. - struct AuctionInfo { - TokenInfo fromInfo; - uint96 kicked; - uint128 initialAvailable; - uint128 currentAvailable; - } - - uint256 internal constant WAD = 1e18; - - /// @notice Used for the price decay. - uint256 internal constant MINUTE_HALF_LIFE = - 0.988514020352896135_356867505 * 1e27; // 0.5^(1/60) - - /// @notice Struct to hold the info for `auctionWant`. - TokenInfo internal auctionWantInfo; - - /// @notice Mapping from an auction ID to its struct. - mapping(bytes32 => AuctionInfo) public auctions; - - /// @notice Array of all the enabled auction for this contract. - bytes32[] public enabledAuctions; - - /// @notice The amount to start the auction at. - uint256 public auctionStartingPrice; - - /// @notice The time that each auction lasts. - uint32 public auctionLength; - - /// @notice The minimum time to wait between auction 'kicks'. - uint32 public auctionCooldown; - +abstract contract BaseAuctioneer is BaseHealthCheck, Auction { /** * @notice Initializes the Auction contract with initial parameters. - * @param _auctionWant Address this auction is selling to. + * @param _asset Address of the asset this auction is selling. + * @param _name Name of the auction. + * @param _governance Address of the contract governance. * @param _auctionLength Duration of each auction in seconds. - * @param _auctionCooldown Cooldown period between auctions in seconds. * @param _auctionStartingPrice Starting price for each auction. */ constructor( address _asset, string memory _name, - address _auctionWant, - uint32 _auctionLength, - uint32 _auctionCooldown, + address _governance, + uint256 _auctionLength, uint256 _auctionStartingPrice ) BaseHealthCheck(_asset, _name) { - require(auctionLength == 0, "initialized"); - require(_auctionWant != address(0), "ZERO ADDRESS"); - require(_auctionLength != 0, "length"); - require(_auctionLength <= _auctionCooldown, "cooldown"); - require(_auctionStartingPrice != 0, "starting price"); - - // Cannot have more than 18 decimals. - uint256 decimals = ERC20(_auctionWant).decimals(); - require(decimals <= 18, "unsupported decimals"); - - // Set variables - auctionWantInfo = TokenInfo({ - tokenAddress: _auctionWant, - scaler: uint96(WAD / 10 ** decimals) - }); - - auctionLength = _auctionLength; - auctionCooldown = _auctionCooldown; - auctionStartingPrice = _auctionStartingPrice; - } - - /*////////////////////////////////////////////////////////////// - VIEW METHODS - //////////////////////////////////////////////////////////////*/ - - /** - * @notice Get the address of this auctions want token. - * @return . The want token. - */ - function auctionWant() public view virtual returns (address) { - return auctionWantInfo.tokenAddress; - } - - /** - * @notice Get the length of the enabled auctions array. - */ - function numberOfEnabledAuctions() external view virtual returns (uint256) { - return enabledAuctions.length; - } - - /** - * @notice Get the unique auction identifier. - * @param _from The address of the token to sell. - * @return bytes32 A unique auction identifier. - */ - function getAuctionId(address _from) public view virtual returns (bytes32) { - return keccak256(abi.encodePacked(_from, auctionWant(), address(this))); - } - - /** - * @notice Retrieves information about a specific auction. - * @param _auctionId The unique identifier of the auction. - * @return _from The address of the token to sell. - * @return _to The address of the token to buy. - * @return _kicked The timestamp of the last kick. - * @return _available The current available amount for the auction. - */ - function auctionInfo( - bytes32 _auctionId - ) - public - view - virtual - returns ( - address _from, - address _to, - uint256 _kicked, - uint256 _available - ) - { - AuctionInfo memory auction = auctions[_auctionId]; - - return ( - auction.fromInfo.tokenAddress, - auctionWant(), - auction.kicked, - auction.kicked + uint256(auctionLength) > block.timestamp - ? auction.currentAvailable - : 0 - ); - } - - /** - * @notice Get the pending amount available for the next auction. - * @dev Defaults to the auctions balance of the from token if no hook. - * @param _auctionId The unique identifier of the auction. - * @return uint256 The amount that can be kicked into the auction. - */ - function kickable( - bytes32 _auctionId - ) public view virtual returns (uint256) { - // If not enough time has passed then `kickable` is 0. - if ( - auctions[_auctionId].kicked + uint256(auctionCooldown) > - block.timestamp - ) { - return 0; - } - - return _kickable(auctions[_auctionId].fromInfo.tokenAddress); - } - - /** - * @notice Gets the amount of `auctionWant` needed to buy a specific amount of `from`. - * @param _auctionId The unique identifier of the auction. - * @param _amountToTake The amount of `from` to take in the auction. - * @return . The amount of `auctionWant` needed to fulfill the take amount. - */ - function getAmountNeeded( - bytes32 _auctionId, - uint256 _amountToTake - ) external view virtual returns (uint256) { - return - _getAmountNeeded( - auctions[_auctionId], - _amountToTake, - block.timestamp - ); - } - - /** - * @notice Gets the amount of `auctionWant` needed to buy a specific amount of `from` at a specific timestamp. - * @param _auctionId The unique identifier of the auction. - * @param _amountToTake The amount `from` to take in the auction. - * @param _timestamp The specific timestamp for calculating the amount needed. - * @return . The amount of `auctionWant` needed to fulfill the take amount. - */ - function getAmountNeeded( - bytes32 _auctionId, - uint256 _amountToTake, - uint256 _timestamp - ) external view virtual returns (uint256) { - return - _getAmountNeeded(auctions[_auctionId], _amountToTake, _timestamp); - } - - /** - * @dev Return the amount of `auctionWant` needed to buy `_amountToTake`. - */ - function _getAmountNeeded( - AuctionInfo memory _auction, - uint256 _amountToTake, - uint256 _timestamp - ) internal view virtual returns (uint256) { - return - // Scale _amountToTake to 1e18 - (_amountToTake * - _auction.fromInfo.scaler * - // Price is always 1e18 - _price( - _auction.kicked, - _auction.initialAvailable * _auction.fromInfo.scaler, - _timestamp - )) / - 1e18 / - // Scale back down to auctionWant. - auctionWantInfo.scaler; - } - - /** - * @notice Gets the price of the auction at the current timestamp. - * @param _auctionId The unique identifier of the auction. - * @return . The price of the auction. - */ - function price(bytes32 _auctionId) external view virtual returns (uint256) { - return price(_auctionId, block.timestamp); - } - - /** - * @notice Gets the price of the auction at a specific timestamp. - * @param _auctionId The unique identifier of the auction. - * @param _timestamp The specific timestamp for calculating the price. - * @return . The price of the auction. - */ - function price( - bytes32 _auctionId, - uint256 _timestamp - ) public view virtual returns (uint256) { - // Get unscaled price and scale it down. - return - _price( - auctions[_auctionId].kicked, - auctions[_auctionId].initialAvailable * - auctions[_auctionId].fromInfo.scaler, - _timestamp - ) / auctionWantInfo.scaler; - } - - /** - * @dev Internal function to calculate the scaled price based on auction parameters. - * @param _kicked The timestamp the auction was kicked. - * @param _available The initial available amount scaled 1e18. - * @param _timestamp The specific timestamp for calculating the price. - * @return . The calculated price scaled to 1e18. - */ - function _price( - uint256 _kicked, - uint256 _available, - uint256 _timestamp - ) internal view virtual returns (uint256) { - if (_available == 0) return 0; - - uint256 secondsElapsed = _timestamp - _kicked; - - if (secondsElapsed > auctionLength) return 0; - - // Exponential decay from https://github.com/ajna-finance/ajna-core/blob/master/src/libraries/helpers/PoolHelper.sol - uint256 hoursComponent = 1e27 >> (secondsElapsed / 3600); - uint256 minutesComponent = Maths.rpow( - MINUTE_HALF_LIFE, - (secondsElapsed % 3600) / 60 - ); - uint256 initialPrice = Maths.wdiv( - auctionStartingPrice * 1e18, - _available + initialize( + _asset, + address(this), + _governance, + _auctionLength, + _auctionStartingPrice ); - - return - (initialPrice * Maths.rmul(hoursComponent, minutesComponent)) / - 1e27; } - - /*////////////////////////////////////////////////////////////// - SETTERS - //////////////////////////////////////////////////////////////*/ - - /** - * @notice Enables a new auction. - * @param _from The address of the token to be auctioned. - * @return _auctionId The unique identifier of the enabled auction. - */ - function enableAuction( - address _from - ) public virtual onlyManagement returns (bytes32 _auctionId) { - address _auctionWant = auctionWant(); - require(_from != address(0) && _from != _auctionWant, "ZERO ADDRESS"); - // Cannot have more than 18 decimals. - uint256 decimals = ERC20(_from).decimals(); - require(decimals <= 18, "unsupported decimals"); - - // Calculate the id. - _auctionId = getAuctionId(_from); - - require( - auctions[_auctionId].fromInfo.tokenAddress == address(0), - "already enabled" - ); - - // Store all needed info. - auctions[_auctionId].fromInfo = TokenInfo({ - tokenAddress: _from, - scaler: uint96(WAD / 10 ** decimals) - }); - - // Add to the array. - enabledAuctions.push(_auctionId); - - emit AuctionEnabled(_auctionId, _from, _auctionWant, address(this)); - } - - /** - * @notice Disables an existing auction. - * @dev Only callable by governance. - * @param _from The address of the token being sold. - */ - function disableAuction(address _from) external virtual { - disableAuction(_from, 0); - } - - /** - * @notice Disables an existing auction. - * @dev Only callable by governance. - * @param _from The address of the token being sold. - * @param _index The index the auctionId is at in the array. - */ - function disableAuction( - address _from, - uint256 _index - ) public virtual onlyEmergencyAuthorized { - bytes32 _auctionId = getAuctionId(_from); - - // Make sure the auction was enabled. - require( - auctions[_auctionId].fromInfo.tokenAddress != address(0), - "not enabled" - ); - - // Remove the struct. - delete auctions[_auctionId]; - - // Remove the auction ID from the array. - bytes32[] memory _enabledAuctions = enabledAuctions; - if (_enabledAuctions[_index] != _auctionId) { - // If the _index given is not the id find it. - for (uint256 i = 0; i < _enabledAuctions.length; ++i) { - if (_enabledAuctions[i] == _auctionId) { - _index = i; - break; - } - } - } - - // Move the id to the last spot if not there. - if (_index < _enabledAuctions.length - 1) { - _enabledAuctions[_index] = _enabledAuctions[ - _enabledAuctions.length - 1 - ]; - // Update the array. - enabledAuctions = _enabledAuctions; - } - - // Pop the id off the array. - enabledAuctions.pop(); - - emit AuctionDisabled(_auctionId, _from, auctionWant(), address(this)); - } - - /*////////////////////////////////////////////////////////////// - PARTICIPATE IN AUCTION - //////////////////////////////////////////////////////////////*/ - - /** - * @notice Kicks off an auction, updating its status and making funds available for bidding. - * @param _auctionId The unique identifier of the auction. - * @return available The available amount for bidding on in the auction. - */ - function kick( - bytes32 _auctionId - ) external virtual nonReentrant returns (uint256 available) { - address _fromToken = auctions[_auctionId].fromInfo.tokenAddress; - require(_fromToken != address(0), "not enabled"); - require( - block.timestamp > - auctions[_auctionId].kicked + uint256(auctionCooldown), - "too soon" - ); - - available = _auctionKicked(_fromToken); - - require(available != 0, "nothing to kick"); - - // Update the auctions status. - auctions[_auctionId].kicked = uint96(block.timestamp); - auctions[_auctionId].initialAvailable = uint128(available); - auctions[_auctionId].currentAvailable = uint128(available); - - emit AuctionKicked(_auctionId, available); - } - - /** - * @notice Take the token being sold in a live auction. - * @dev Defaults to taking the full amount and sending to the msg sender. - * @param _auctionId The unique identifier of the auction. - * @return . The amount of fromToken taken in the auction. - */ - function take(bytes32 _auctionId) external virtual returns (uint256) { - return _take(_auctionId, type(uint256).max, msg.sender, new bytes(0)); - } - - /** - * @notice Take the token being sold in a live auction with a specified maximum amount. - * @dev Uses the sender's address as the receiver. - * @param _auctionId The unique identifier of the auction. - * @param _maxAmount The maximum amount of fromToken to take in the auction. - * @return . The amount of fromToken taken in the auction. - */ - function take( - bytes32 _auctionId, - uint256 _maxAmount - ) external virtual returns (uint256) { - return _take(_auctionId, _maxAmount, msg.sender, new bytes(0)); - } - - /** - * @notice Take the token being sold in a live auction. - * @param _auctionId The unique identifier of the auction. - * @param _maxAmount The maximum amount of fromToken to take in the auction. - * @param _receiver The address that will receive the fromToken. - * @return _amountTaken The amount of fromToken taken in the auction. - */ - function take( - bytes32 _auctionId, - uint256 _maxAmount, - address _receiver - ) external virtual returns (uint256) { - return _take(_auctionId, _maxAmount, _receiver, new bytes(0)); - } - - /** - * @notice Take the token being sold in a live auction. - * @param _auctionId The unique identifier of the auction. - * @param _maxAmount The maximum amount of fromToken to take in the auction. - * @param _receiver The address that will receive the fromToken. - * @param _data The data signify the callback should be used and sent with it. - * @return _amountTaken The amount of fromToken taken in the auction. - */ - function take( - bytes32 _auctionId, - uint256 _maxAmount, - address _receiver, - bytes calldata _data - ) external virtual returns (uint256) { - return _take(_auctionId, _maxAmount, _receiver, _data); - } - - /// @dev Implements the take of the auction. - function _take( - bytes32 _auctionId, - uint256 _maxAmount, - address _receiver, - bytes memory _data - ) internal virtual nonReentrant returns (uint256 _amountTaken) { - AuctionInfo memory auction = auctions[_auctionId]; - // Make sure the auction is active. - require( - auction.kicked + uint256(auctionLength) >= block.timestamp, - "not kicked" - ); - - // Max amount that can be taken. - _amountTaken = auction.currentAvailable > _maxAmount - ? _maxAmount - : auction.currentAvailable; - - // Get the amount needed - uint256 needed = _getAmountNeeded( - auction, - _amountTaken, - block.timestamp - ); - - require(needed != 0, "zero needed"); - - // How much is left in this auction. - uint256 left; - unchecked { - left = auction.currentAvailable - _amountTaken; - } - auctions[_auctionId].currentAvailable = uint128(left); - - _preTake(auction.fromInfo.tokenAddress, _amountTaken, needed); - - // Send `from`. - ERC20(auction.fromInfo.tokenAddress).safeTransfer( - _receiver, - _amountTaken - ); - - // If the caller has specified data. - if (_data.length != 0) { - // Do the callback. - ITaker(_receiver).auctionTakeCallback( - auction.fromInfo.tokenAddress, - msg.sender, - _amountTaken, - needed, - _data - ); - } - - // Cache the auctionWant address. - address _auctionWant = auctionWant(); - - // Pull `auctionWant`. - ERC20(_auctionWant).safeTransferFrom(msg.sender, address(this), needed); - - _postTake(_auctionWant, _amountTaken, needed); - - emit AuctionTaken(_auctionId, _amountTaken, left); - } - - /** - * @notice Return how much `_token` could currently be kicked into auction. - * @dev This can be overridden by a strategist to implement custom logic. - * @param _token Address of the `_from` token. - * @return . The amount of `_token` ready to be auctioned off. - */ - function _kickable(address _token) internal view virtual returns (uint256) { - return ERC20(_token).balanceOf(address(this)); - } - - /** - * @dev To override if something other than just sending the loose balance - * of `_token` to the auction is desired, such as accruing and and claiming rewards. - * - * @param _token Address of the token being auctioned off - */ - function _auctionKicked(address _token) internal virtual returns (uint256) { - return ERC20(_token).balanceOf(address(this)); - } - - /** - * @dev To override if something needs to be done before a take is completed. - * This can be used if the auctioned token only will be freed up when a `take` - * occurs. - * @param _token Address of the token being taken. - * @param _amountToTake Amount of `_token` needed. - * @param _amountToPay Amount of `auctionWant` that will be payed. - */ - function _preTake( - address _token, - uint256 _amountToTake, - uint256 _amountToPay - ) internal virtual {} - - /** - * @dev To override if a post take action is desired. - * - * This could be used to re-deploy the bought token back into the yield source, - * or in conjunction with {_preTake} to check that the price sold at was within - * some allowed range. - * - * @param _token Address of the token that the strategy was sent. - * @param _amountTaken Amount of the from token taken. - * @param _amountPayed Amount of `_token` that was sent to the strategy. - */ - function _postTake( - address _token, - uint256 _amountTaken, - uint256 _amountPayed - ) internal virtual {} } diff --git a/src/Bases/Auctioneer/IBaseAuctioneer.sol b/src/Bases/Auctioneer/IBaseAuctioneer.sol index 6b9a858..53d3564 100644 --- a/src/Bases/Auctioneer/IBaseAuctioneer.sol +++ b/src/Bases/Auctioneer/IBaseAuctioneer.sol @@ -9,89 +9,69 @@ interface IBaseAuctioneer is IBaseHealthCheck { uint96 scaler; } - function auctionStartingPrice() external view returns (uint256); + function auctions( + address _from + ) external view returns (uint64, uint64, uint128); - function auctionLength() external view returns (uint32); + function startingPrice() external view returns (uint256); - function auctionCooldown() external view returns (uint32); + function auctionLength() external view returns (uint256); - function auctions( - bytes32 - ) - external - view - returns ( - TokenInfo memory fromInfo, - uint96 kicked, - uint128 initialAvailable, - uint128 currentAvailable - ); - - function enabledAuctions() external view returns (bytes32[] memory); - - function auctionWant() external view returns (address); - - function numberOfEnabledAuctions() external view returns (uint256); - - function getAuctionId(address _from) external view returns (bytes32); - - function auctionInfo( - bytes32 _auctionId - ) - external - view - returns ( - address _from, - address _to, - uint256 _kicked, - uint256 _available - ); - - function kickable(bytes32 _auctionId) external view returns (uint256); + function enabledAuctions(uint256) external view returns (address); + + function want() external view returns (address); + + function getAllEnabledAuctions() external view returns (address[] memory); + + function available(address _from) external view returns (uint256); + + function kickable(address _from) external view returns (uint256); function getAmountNeeded( - bytes32 _auctionId, + address _from, uint256 _amountToTake ) external view returns (uint256); function getAmountNeeded( - bytes32 _auctionId, + address _from, uint256 _amountToTake, uint256 _timestamp ) external view returns (uint256); - function price(bytes32 _auctionId) external view returns (uint256); + function price(address _from) external view returns (uint256); function price( - bytes32 _auctionId, + address _from, uint256 _timestamp ) external view returns (uint256); - function enableAuction(address _from) external returns (bytes32); + function enableAuction(address _from) external; function disableAuction(address _from) external; function disableAuction(address _from, uint256 _index) external; - function kick(bytes32 _auctionId) external returns (uint256 available); + function kick(address _from) external returns (uint256 available); - function take(bytes32 _auctionId) external returns (uint256); + function take(address _from) external returns (uint256); - function take( - bytes32 _auctionId, - uint256 _maxAmount - ) external returns (uint256); + function take(address _from, uint256 _maxAmount) external returns (uint256); function take( - bytes32 _auctionId, + address _from, uint256 _maxAmount, address _receiver ) external returns (uint256); function take( - bytes32 _auctionId, + address _from, uint256 _maxAmount, address _receiver, bytes calldata _data ) external returns (uint256); + + function isValidSignature( + bytes32 _hash, + bytes memory _signature + ) external view returns (bytes4); } diff --git a/src/swappers/AuctionSwapper.sol b/src/swappers/AuctionSwapper.sol index 5403ffb..bd372d5 100644 --- a/src/swappers/AuctionSwapper.sol +++ b/src/swappers/AuctionSwapper.sol @@ -52,11 +52,8 @@ contract AuctionSwapper { AUCTION STARTING AND STOPPING //////////////////////////////////////////////////////////////*/ - function _enableAuction( - address _from, - address _want - ) internal virtual returns (bytes32) { - return _enableAuction(_from, _want, 1 days, 3 days, 1e6); + function _enableAuction(address _from, address _want) internal virtual { + _enableAuction(_from, _want, 1 days, 1e6); } /** @@ -68,15 +65,13 @@ contract AuctionSwapper { * * @param _from Token to sell * @param _want Token to buy. - * @return .The auction ID. */ function _enableAuction( address _from, address _want, uint256 _auctionLength, - uint256 _auctionCooldown, uint256 _startingPrice - ) internal virtual returns (bytes32) { + ) internal virtual { address _auction = auction; // If this is the first auction. @@ -87,7 +82,6 @@ contract AuctionSwapper { address(this), address(this), _auctionLength, - _auctionCooldown, _startingPrice ); // Store it for future use. @@ -98,7 +92,7 @@ contract AuctionSwapper { } // Enable new auction for `_from` token. - return Auction(_auction).enable(_from); + Auction(_auction).enable(_from); } /** @@ -109,107 +103,22 @@ contract AuctionSwapper { Auction(auction).disable(_from); } - /*////////////////////////////////////////////////////////////// - OPTIONAL AUCTION HOOKS - //////////////////////////////////////////////////////////////*/ - /** - * @notice Return how much `_token` could currently be kicked into auction. - * @dev This can be overridden by a strategist to implement custom logic. - * @param _token Address of the `_from` token. - * @return . The amount of `_token` ready to be auctioned off. + * @dev Return how much `_token` could currently be kicked into auction. + * @param _token The token that was being sold. + * @return The amount of `_token` ready to be auctioned off. */ function kickable(address _token) public view virtual returns (uint256) { return ERC20(_token).balanceOf(address(this)); } /** - * @dev To override if something other than just sending the loose balance - * of `_token` to the auction is desired, such as accruing and and claiming rewards. - * - * @param _token Address of the token being auctioned off - */ - function _auctionKicked(address _token) internal virtual returns (uint256) { - // Send any loose balance to the auction. - uint256 balance = ERC20(_token).balanceOf(address(this)); - if (balance != 0) ERC20(_token).safeTransfer(auction, balance); - return ERC20(_token).balanceOf(auction); - } - - /** - * @dev To override if something needs to be done before a take is completed. - * This can be used if the auctioned token only will be freed up when a `take` - * occurs. - * @param _token Address of the token being taken. - * @param _amountToTake Amount of `_token` needed. - * @param _amountToPay Amount of `want` that will be payed. - */ - function _preTake( - address _token, - uint256 _amountToTake, - uint256 _amountToPay - ) internal virtual {} - - /** - * @dev To override if a post take action is desired. - * - * This could be used to re-deploy the bought token back into the yield source, - * or in conjunction with {_preTake} to check that the price sold at was within - * some allowed range. - * - * @param _token Address of the token that the strategy was sent. - * @param _amountTaken Amount of the from token taken. - * @param _amountPayed Amount of `_token` that was sent to the strategy. - */ - function _postTake( - address _token, - uint256 _amountTaken, - uint256 _amountPayed - ) internal virtual {} - - /*////////////////////////////////////////////////////////////// - AUCTION HOOKS - //////////////////////////////////////////////////////////////*/ - - /** - * @notice External hook for the auction to call during a `kick`. - * @dev Will call the internal version for the strategist to override. - * @param _token Token being kicked into auction. - * @return . The amount of `_token` to be auctioned off. - */ - function auctionKicked( - address _token - ) external virtual onlyAuction returns (uint256) { - return _auctionKicked(_token); - } - - /** - * @notice External hook for the auction to call before a `take`. - * @dev Will call the internal version for the strategist to override. - * @param _token Token being taken in the auction. - * @param _amountToTake The amount of `_token` to be sent to the taker. - * @param _amountToPay Amount of `want` that will be payed. - */ - function preTake( - address _token, - uint256 _amountToTake, - uint256 _amountToPay - ) external virtual onlyAuction { - _preTake(_token, _amountToTake, _amountToPay); - } - - /** - * @notice External hook for the auction to call after a `take` completed. - * @dev Will call the internal version for the strategist to override. - * @param _token The `want` token that was sent to the strategy. - * @param _amountTaken Amount of the from token taken. - * @param _amountPayed Amount of `_token` that was sent to the strategy. + * @dev Kick an auction for a given token. + * @param _from The token that was being sold. */ - function postTake( - address _token, - uint256 _amountTaken, - uint256 _amountPayed - ) external virtual onlyAuction { - _postTake(_token, _amountTaken, _amountPayed); + function _kickAuction(address _from) internal virtual returns (uint256) { + uint256 _balance = ERC20(_from).balanceOf(address(this)); + ERC20(_from).safeTransfer(auction, _balance); + return Auction(auction).kick(_from); } } diff --git a/src/swappers/interfaces/IAuctionSwapper.sol b/src/swappers/interfaces/IAuctionSwapper.sol index 7c5728a..eb083f5 100644 --- a/src/swappers/interfaces/IAuctionSwapper.sol +++ b/src/swappers/interfaces/IAuctionSwapper.sol @@ -7,10 +7,4 @@ interface IAuctionSwapper { function auction() external view returns (address); function kickable(address _fromToken) external view returns (uint256); - - function auctionKicked(address _fromToken) external returns (uint256); - - function preTake(address _fromToken, uint256 _amountToTake) external; - - function postTake(address _toToken, uint256 _newAmount) external; } diff --git a/src/test/Auction.t.sol b/src/test/Auction.t.sol index 407f3ae..ade75d7 100644 --- a/src/test/Auction.t.sol +++ b/src/test/Auction.t.sol @@ -10,30 +10,14 @@ import {Auction, AuctionFactory} from "../Auctions/AuctionFactory.sol"; contract AuctionTest is Setup, ITaker { using SafeERC20 for ERC20; - event AuctionEnabled( - bytes32 auctionId, - address indexed from, - address indexed to, - address indexed strategy - ); - - event AuctionDisabled( - bytes32 auctionId, - address indexed from, - address indexed to, - address indexed strategy - ); + event AuctionEnabled(address indexed from, address indexed to); - event AuctionKicked(bytes32 auctionId, uint256 available); + event AuctionDisabled(address indexed from, address indexed to); - event AuctionTaken( - bytes32 auctionId, - uint256 amountTaken, - uint256 amountLeft - ); + event AuctionKicked(address indexed from, uint256 available); event Callback( - address _from, + address indexed from, address _sender, uint256 _amountTaken, uint256 _amountNeeded, @@ -55,7 +39,6 @@ contract AuctionTest is Setup, ITaker { } function test_setup() public { - assertEq(auctionFactory.DEFAULT_AUCTION_COOLDOWN(), 5 days); assertEq(auctionFactory.DEFAULT_AUCTION_LENGTH(), 1 days); assertEq(auctionFactory.DEFAULT_STARTING_PRICE(), 1e6); } @@ -64,19 +47,15 @@ contract AuctionTest is Setup, ITaker { auction = Auction(auctionFactory.createNewAuction(address(asset))); vm.expectRevert("initialized"); - auction.initialize(address(asset), address(0), management, 1, 10, 8); + auction.initialize(address(asset), address(this), management, 1, 10); assertEq(auction.want(), address(asset)); - assertEq(auction.hook(), address(0)); + assertEq(auction.receiver(), address(this)); assertEq(auction.governance(), address(this)); assertEq( auction.auctionLength(), auctionFactory.DEFAULT_AUCTION_LENGTH() ); - assertEq( - auction.auctionCooldown(), - auctionFactory.DEFAULT_AUCTION_COOLDOWN() - ); assertEq( auction.startingPrice(), auctionFactory.DEFAULT_STARTING_PRICE() @@ -87,51 +66,37 @@ contract AuctionTest is Setup, ITaker { address from = tokenAddrs["USDC"]; auction = Auction(auctionFactory.createNewAuction(address(asset))); - bytes32 expectedId = auction.getAuctionId(from); - vm.expectRevert("!governance"); vm.prank(management); auction.enable(from); vm.expectEmit(true, true, true, true, address(auction)); - emit AuctionEnabled(expectedId, from, address(asset), address(auction)); - - bytes32 id = auction.enable(from); - assertEq(id, expectedId); - - assertEq(auction.numberOfEnabledAuctions(), 1); - assertEq(auction.enabledAuctions(0), expectedId); - assertEq(auction.kickable(id), 0); - assertEq(auction.getAmountNeeded(id, 1e18), 0); - assertEq(auction.price(id), 0); - - ( - address _from, - address _to, - uint256 _kicked, - uint256 _available - ) = auction.auctionInfo(id); - - assertEq(_from, from); - assertEq(_to, address(asset)); - assertEq(_kicked, 0); - assertEq(_available, 0); + emit AuctionEnabled(from, address(asset)); + + auction.enable(from); - (Auction.TokenInfo memory _token, , address _receiver, , ) = auction - .auctions(id); - assertEq(_token.tokenAddress, from); - assertEq(_receiver, address(this)); + assertEq(auction.getAllEnabledAuctions().length, 1); + assertEq(auction.enabledAuctions(0), from); + assertEq(auction.kickable(from), 0); + assertEq(auction.getAmountNeeded(from, 1e18), 0); + assertEq(auction.price(from), 0); + assertEq(auction.receiver(), address(this)); + + (uint128 _kicked, uint128 _scaler, uint128 _initialAvailable) = auction + .auctions(from); + + assertEq(_kicked, 0); + assertEq(_scaler, 1e12); + assertEq(_initialAvailable, 0); + assertEq(auction.available(from), 0); // Kicking it reverts vm.expectRevert("nothing to kick"); - auction.kick(id); + auction.kick(from); // Can't re-enable vm.expectRevert("already enabled"); auction.enable(from); - - vm.expectRevert("already enabled"); - auction.enable(from, management); } function test_disableAuction() public { @@ -141,44 +106,34 @@ contract AuctionTest is Setup, ITaker { vm.expectRevert("not enabled"); auction.disable(from); - bytes32 id = auction.enable(from); + auction.enable(from); - assertEq(auction.numberOfEnabledAuctions(), 1); + assertEq(auction.getAllEnabledAuctions().length, 1); - ( - address _from, - address _to, - uint256 _kicked, - uint256 _available - ) = auction.auctionInfo(id); + (uint128 _kicked, uint128 _scaler, uint128 _initialAvailable) = auction + .auctions(from); - assertEq(_from, from); - assertEq(_to, address(asset)); assertEq(_kicked, 0); - assertEq(_available, 0); + assertEq(_scaler, 1e12); + assertEq(_initialAvailable, 0); + assertEq(auction.available(from), 0); vm.expectRevert("!governance"); vm.prank(management); auction.disable(from); vm.expectEmit(true, true, true, true, address(auction)); - emit AuctionDisabled(id, from, address(asset), address(auction)); + emit AuctionDisabled(from, address(asset)); auction.disable(from); - assertEq(auction.numberOfEnabledAuctions(), 0); + assertEq(auction.getAllEnabledAuctions().length, 0); - (_from, _to, _kicked, _available) = auction.auctionInfo(id); + (_kicked, _scaler, _initialAvailable) = auction.auctions(from); - assertEq(_from, address(0)); - assertEq(_to, address(asset)); assertEq(_kicked, 0); - assertEq(_available, 0); - - (Auction.TokenInfo memory _token, , address _receiver, , ) = auction - .auctions(id); - assertEq(_token.tokenAddress, address(0)); - assertEq(_token.scaler, 0); - assertEq(_receiver, address(0)); + assertEq(_scaler, 0); + assertEq(_initialAvailable, 0); + assertEq(auction.available(from), 0); } function test_kickAuction(uint256 _amount) public { @@ -190,53 +145,49 @@ contract AuctionTest is Setup, ITaker { fromScaler = WAD / 10 ** ERC20(from).decimals(); wantScaler = WAD / 10 ** ERC20(asset).decimals(); - bytes32 id = auction.enable(from); + auction.enable(from); - assertEq(auction.kickable(id), 0); - ( - address _from, - address _to, - uint256 _kicked, - uint256 _available - ) = auction.auctionInfo(id); + assertEq(auction.kickable(from), 0); + (uint128 _kicked, uint128 _scaler, uint128 _initialAvailable) = auction + .auctions(from); - assertEq(_from, from); - assertEq(_to, address(asset)); assertEq(_kicked, 0); - assertEq(_available, 0); + assertEq(_scaler, 1e10); + assertEq(_initialAvailable, 0); + assertEq(auction.available(from), 0); airdrop(ERC20(from), address(auction), _amount); - assertEq(auction.kickable(id), _amount); - (, , _kicked, _available) = auction.auctionInfo(id); + assertEq(auction.kickable(from), _amount); + (_kicked, , _initialAvailable) = auction.auctions(from); assertEq(_kicked, 0); - assertEq(_available, 0); + assertEq(_initialAvailable, 0); + assertEq(auction.available(from), 0); - vm.expectEmit(true, true, true, true, address(auction)); - emit AuctionKicked(id, _amount); - uint256 available = auction.kick(id); + uint256 available = auction.kick(from); - assertEq(auction.kickable(id), 0); - (, , _kicked, _available) = auction.auctionInfo(id); + assertEq(auction.kickable(from), 0); + (_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) / _amount / fromScaler; - assertEq(auction.price(id), startingPrice); + assertEq(auction.price(from), startingPrice); assertRelApproxEq( - auction.getAmountNeeded(id, _amount), + auction.getAmountNeeded(from, _amount), (startingPrice * fromScaler * _amount) / (WAD / wantScaler) / wantScaler, MAX_BPS ); - uint256 expectedPrice = auction.price(id, block.timestamp + 100); + uint256 expectedPrice = auction.price(from, block.timestamp + 100); assertLt(expectedPrice, startingPrice); uint256 expectedAmount = auction.getAmountNeeded( - id, + from, _amount, block.timestamp + 100 ); @@ -249,52 +200,54 @@ contract AuctionTest is Setup, ITaker { skip(100); - assertEq(auction.price(id), expectedPrice); - assertEq(auction.getAmountNeeded(id, _amount), expectedAmount); + assertEq(auction.price(from), expectedPrice); + assertEq(auction.getAmountNeeded(from, _amount), expectedAmount); + + // Can't kick a new one yet + vm.expectRevert("too soon"); + auction.kick(from); // Skip full auction skip(auction.auctionLength()); - assertEq(auction.price(id), 0); - assertEq(auction.getAmountNeeded(id, _amount), 0); + assertEq(auction.price(from), 0); + assertEq(auction.getAmountNeeded(from, _amount), 0); + assertEq(auction.available(from), 0); - // Can't kick a new one yet - vm.expectRevert("too soon"); - auction.kick(id); - - assertEq(auction.kickable(id), 0); + assertEq(auction.kickable(from), _amount); } function test_takeAuction_all(uint256 _amount) public { vm.assume(_amount >= minFuzzAmount && _amount <= maxFuzzAmount); address from = tokenAddrs["WBTC"]; - auction = Auction(auctionFactory.createNewAuction(address(asset))); + auction = Auction( + auctionFactory.createNewAuction( + address(asset), + address(mockStrategy) + ) + ); fromScaler = WAD / 10 ** ERC20(from).decimals(); wantScaler = WAD / 10 ** ERC20(asset).decimals(); - bytes32 id = auction.enable(from, address(mockStrategy)); + auction.enable(from); airdrop(ERC20(from), address(auction), _amount); - uint256 available = auction.kick(id); - - assertEq(auction.kickable(id), 0); - ( - address _from, - address _to, - uint256 _kicked, - uint256 _available - ) = auction.auctionInfo(id); - assertEq(_from, from); - assertEq(_to, address(asset)); + uint256 available = auction.kick(from); + + assertEq(auction.kickable(from), 0); + (uint128 _kicked, uint128 _scaler, uint128 _initialAvailable) = auction + .auctions(from); assertEq(_kicked, block.timestamp); - assertEq(_available, _amount); + assertEq(_scaler, 1e10); + assertEq(_initialAvailable, _amount); + assertEq(auction.available(from), _amount); skip(auction.auctionLength() / 2); - uint256 needed = auction.getAmountNeeded(id, _amount); + uint256 needed = auction.getAmountNeeded(from, _amount); uint256 beforeAsset = ERC20(asset).balanceOf(address(this)); airdrop(ERC20(asset), address(this), needed); @@ -303,14 +256,13 @@ contract AuctionTest is Setup, ITaker { uint256 before = ERC20(from).balanceOf(address(this)); - vm.expectEmit(true, true, true, true, address(auction)); - emit AuctionTaken(id, _amount, 0); - uint256 amountTaken = auction.take(id); + uint256 amountTaken = auction.take(from); assertEq(amountTaken, _amount); - (, , , _available) = auction.auctionInfo(id); - assertEq(_available, 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); @@ -324,34 +276,35 @@ contract AuctionTest is Setup, ITaker { _percent = uint16(bound(uint256(_percent), 1_000, MAX_BPS)); address from = tokenAddrs["WBTC"]; - auction = Auction(auctionFactory.createNewAuction(address(asset))); + auction = Auction( + auctionFactory.createNewAuction( + address(asset), + address(mockStrategy) + ) + ); fromScaler = WAD / 10 ** ERC20(from).decimals(); wantScaler = WAD / 10 ** ERC20(asset).decimals(); - bytes32 id = auction.enable(from, address(mockStrategy)); + auction.enable(from); airdrop(ERC20(from), address(auction), _amount); - auction.kick(id); - - assertEq(auction.kickable(id), 0); - ( - address _from, - address _to, - uint256 _kicked, - uint256 _available - ) = auction.auctionInfo(id); - assertEq(_from, from); - assertEq(_to, address(asset)); + auction.kick(from); + + assertEq(auction.kickable(from), 0); + (uint256 _kicked, uint256 _scaler, uint256 _initialAvailable) = auction + .auctions(from); assertEq(_kicked, block.timestamp); - assertEq(_available, _amount); + assertEq(_scaler, 1e10); + assertEq(_initialAvailable, _amount); + assertEq(auction.available(from), _amount); skip(auction.auctionLength() / 2); uint256 toTake = (_amount * _percent) / MAX_BPS; uint256 left = _amount - toTake; - uint256 needed = auction.getAmountNeeded(id, toTake); + uint256 needed = auction.getAmountNeeded(from, toTake); uint256 beforeAsset = ERC20(asset).balanceOf(address(this)); airdrop(ERC20(asset), address(this), needed); @@ -360,14 +313,13 @@ contract AuctionTest is Setup, ITaker { uint256 before = ERC20(from).balanceOf(address(this)); - vm.expectEmit(true, true, true, true, address(auction)); - emit AuctionTaken(id, toTake, left); - uint256 amountTaken = auction.take(id, toTake); + uint256 amountTaken = auction.take(from, toTake); assertEq(amountTaken, toTake); - (, , , _available) = auction.auctionInfo(id); - assertEq(_available, 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); @@ -379,34 +331,34 @@ contract AuctionTest is Setup, ITaker { vm.assume(_amount >= minFuzzAmount && _amount <= maxFuzzAmount); address from = tokenAddrs["WBTC"]; - auction = Auction(auctionFactory.createNewAuction(address(asset))); + auction = Auction( + auctionFactory.createNewAuction( + address(asset), + address(mockStrategy) + ) + ); fromScaler = WAD / 10 ** ERC20(from).decimals(); wantScaler = WAD / 10 ** ERC20(asset).decimals(); - bytes32 id = auction.enable(from, address(mockStrategy)); + auction.enable(from); airdrop(ERC20(from), address(auction), _amount); - auction.kick(id); - - assertEq(auction.kickable(id), 0); - ( - address _from, - address _to, - uint256 _kicked, - uint256 _available - ) = auction.auctionInfo(id); - assertEq(_from, from); - assertEq(_to, address(asset)); - assertEq(_kicked, block.timestamp); - assertEq(_available, _amount); + auction.kick(from); + assertEq(auction.kickable(from), 0); + (uint256 _kicked, uint256 _scaler, uint256 _initialAvailable) = auction + .auctions(from); + assertEq(_kicked, block.timestamp); + assertEq(_scaler, 1e10); + assertEq(_initialAvailable, _amount); + assertEq(auction.available(from), _amount); skip(auction.auctionLength() / 2); uint256 toTake = _amount / 2; uint256 left = _amount - toTake; - uint256 needed = auction.getAmountNeeded(id, toTake); + uint256 needed = auction.getAmountNeeded(from, toTake); uint256 beforeAsset = ERC20(asset).balanceOf(address(this)); airdrop(ERC20(asset), address(this), needed); @@ -420,13 +372,14 @@ contract AuctionTest is Setup, ITaker { vm.expectEmit(true, true, true, true, address(this)); emit Callback(from, address(this), toTake, needed, _data); - uint256 amountTaken = auction.take(id, toTake, address(this), _data); + uint256 amountTaken = auction.take(from, toTake, address(this), _data); assertTrue(callbackHit); assertEq(amountTaken, toTake); - (, , , _available) = auction.auctionInfo(id); - assertEq(_available, 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); diff --git a/src/test/AuctionSwapper.t.sol b/src/test/AuctionSwapper.t.sol index cc6be41..a6a4ce5 100644 --- a/src/test/AuctionSwapper.t.sol +++ b/src/test/AuctionSwapper.t.sol @@ -15,24 +15,14 @@ contract AuctionSwapperTest is Setup { event DeployedNewAuction(address indexed auction, address indexed want); - event AuctionEnabled( - bytes32 auctionId, - address indexed from, - address indexed to, - address indexed strategy - ); + event AuctionEnabled(address indexed from, address indexed to); - event AuctionDisabled( - bytes32 auctionId, - address indexed from, - address indexed to, - address indexed strategy - ); + event AuctionDisabled(address indexed from, address indexed to); - event AuctionKicked(bytes32 auctionId, uint256 available); + event AuctionKicked(address indexed token, uint256 available); event AuctionTaken( - bytes32 auctionId, + address indexed token, uint256 amountTaken, uint256 amountLeft ); @@ -61,35 +51,25 @@ contract AuctionSwapperTest is Setup { address from = tokenAddrs["USDC"]; assertEq(swapper.auction(), address(0)); - bytes32 id = swapper.enableAuction(from, address(asset)); + swapper.enableAuction(from, address(asset)); auction = Auction(swapper.auction()); assertNeq(address(auction), address(0)); - assertEq(auction.kickable(id), 0); - assertEq(auction.getAmountNeeded(id, 1e18), 0); - assertEq(auction.price(id), 0); - ( - address _from, - address _to, - uint256 _kicked, - uint256 _available - ) = auction.auctionInfo(id); - - assertEq(_from, from); - assertEq(_to, address(asset)); + assertEq(auction.kickable(from), 0); + assertEq(auction.getAmountNeeded(from, 1e18), 0); + assertEq(auction.price(from), 0); + + (uint64 _kicked, uint64 _scaler, uint128 _initialAvailable) = auction + .auctions(from); + assertEq(_kicked, 0); - assertEq(_available, 0); - assertEq(auction.hook(), address(swapper)); - (bool hook1, bool hook2, bool hook3, bool hook4) = auction - .getHookFlags(); - assertTrue(hook1); - assertTrue(hook2); - assertTrue(hook3); - assertTrue(hook4); + assertEq(_scaler, 1e12); + assertEq(_initialAvailable, 0); + assertEq(auction.available(from), 0); // Kicking it reverts vm.expectRevert("nothing to kick"); - auction.kick(id); + auction.kick(from); // Can't re-enable vm.expectRevert("already enabled"); @@ -100,89 +80,73 @@ contract AuctionSwapperTest is Setup { address from = tokenAddrs["USDC"]; assertEq(swapper.auction(), address(0)); - bytes32 id = swapper.enableAuction(from, address(asset)); + swapper.enableAuction(from, address(asset)); auction = Auction(swapper.auction()); assertNeq(address(auction), address(0)); - assertEq(auction.kickable(id), 0); - assertEq(auction.getAmountNeeded(id, 1e18), 0); - assertEq(auction.price(id), 0); - ( - address _from, - address _to, - uint256 _kicked, - uint256 _available - ) = auction.auctionInfo(id); - - assertEq(_from, from); - assertEq(_to, address(asset)); + assertEq(auction.kickable(from), 0); + assertEq(auction.getAmountNeeded(from, 1e18), 0); + assertEq(auction.price(from), 0); + + (uint64 _kicked, uint64 _scaler, uint128 _initialAvailable) = auction + .auctions(from); + assertEq(_kicked, 0); - assertEq(_available, 0); + assertEq(_scaler, 1e12); + assertEq(_initialAvailable, 0); + assertEq(auction.available(from), 0); address secondFrom = tokenAddrs["WETH"]; vm.expectRevert("wrong want"); swapper.enableAuction(secondFrom, from); - bytes32 expectedId = auction.getAuctionId(secondFrom); - vm.expectEmit(true, true, true, true, address(auction)); - emit AuctionEnabled( - expectedId, - secondFrom, - address(asset), - address(auction) - ); - bytes32 secondId = swapper.enableAuction(secondFrom, address(asset)); + emit AuctionEnabled(secondFrom, address(asset)); + swapper.enableAuction(secondFrom, address(asset)); - assertEq(expectedId, secondId); assertEq(swapper.auction(), address(auction)); - assertEq(auction.kickable(secondId), 0); - assertEq(auction.getAmountNeeded(secondId, 1e18), 0); - assertEq(auction.price(secondId), 0); - (_from, _to, _kicked, _available) = auction.auctionInfo(secondId); + assertEq(auction.kickable(secondFrom), 0); + assertEq(auction.getAmountNeeded(secondFrom, 1e18), 0); + assertEq(auction.price(secondFrom), 0); + (_kicked, _scaler, _initialAvailable) = auction.auctions(secondFrom); - assertEq(_from, secondFrom); - assertEq(_to, address(asset)); assertEq(_kicked, 0); - assertEq(_available, 0); + assertEq(_scaler, 1e12); + assertEq(_initialAvailable, 0); + assertEq(auction.available(secondFrom), 0); } function test_disableAuction() public { address from = tokenAddrs["USDC"]; assertEq(swapper.auction(), address(0)); - - bytes32 id = swapper.enableAuction(from, address(asset)); + swapper.enableAuction(from, address(asset)); auction = Auction(swapper.auction()); assertNeq(address(auction), address(0)); - assertEq(auction.kickable(id), 0); - assertEq(auction.getAmountNeeded(id, 1e18), 0); - assertEq(auction.price(id), 0); - ( - address _from, - address _to, - uint256 _kicked, - uint256 _available - ) = auction.auctionInfo(id); - - assertEq(_from, from); - assertEq(_to, address(asset)); + assertEq(auction.kickable(from), 0); + assertEq(auction.getAmountNeeded(from, 1e18), 0); + assertEq(auction.price(from), 0); + (uint64 _kicked, uint64 _scaler, uint128 _initialAvailable) = auction + .auctions(from); + assertEq(_kicked, 0); - assertEq(_available, 0); + assertEq(_scaler, 1e12); + assertEq(_initialAvailable, 0); + assertEq(auction.available(from), 0); vm.expectEmit(true, true, true, true, address(auction)); - emit AuctionDisabled(id, from, address(asset), address(auction)); + emit AuctionDisabled(from, address(asset)); swapper.disableAuction(from); - (_from, _to, _kicked, _available) = auction.auctionInfo(id); + (_kicked, _scaler, _initialAvailable) = auction.auctions(from); - assertEq(_from, address(0)); - assertEq(_to, address(asset)); assertEq(_kicked, 0); - assertEq(_available, 0); + assertEq(_scaler, 0); + assertEq(_initialAvailable, 0); + assertEq(auction.available(from), 0); } function test_kickAuction_default(uint256 _amount) public { @@ -193,58 +157,53 @@ contract AuctionSwapperTest is Setup { fromScaler = WAD / 10 ** ERC20(from).decimals(); wantScaler = WAD / 10 ** ERC20(asset).decimals(); - bytes32 id = swapper.enableAuction(from, address(asset)); + swapper.enableAuction(from, address(asset)); auction = Auction(swapper.auction()); - assertEq(auction.kickable(id), 0); - ( - address _from, - address _to, - uint256 _kicked, - uint256 _available - ) = auction.auctionInfo(id); + assertEq(auction.kickable(from), 0); + (uint64 _kicked, uint64 _scaler, uint128 _initialAvailable) = auction + .auctions(from); - assertEq(_from, from); - assertEq(_to, address(asset)); assertEq(_kicked, 0); - assertEq(_available, 0); + assertEq(_scaler, fromScaler); + assertEq(_initialAvailable, 0); airdrop(ERC20(from), address(swapper), _amount); - assertEq(auction.kickable(id), _amount); - (, , _kicked, _available) = auction.auctionInfo(id); + assertEq(auction.kickable(from), _amount); + (, _kicked, _initialAvailable) = auction.auctions(from); assertEq(_kicked, 0); - assertEq(_available, 0); + assertEq(_initialAvailable, 0); vm.expectEmit(true, true, true, true, address(auction)); - emit AuctionKicked(id, _amount); - uint256 available = auction.kick(id); + emit AuctionKicked(from, _amount); + uint256 available = auction.kick(from); assertEq(ERC20(from).balanceOf(address(swapper)), 0); assertEq(ERC20(from).balanceOf(address(auction)), _amount); - assertEq(auction.kickable(id), 0); - (, , _kicked, _available) = auction.auctionInfo(id); + assertEq(auction.kickable(from), 0); + (, _kicked, _initialAvailable) = auction.auctions(from); assertEq(_kicked, block.timestamp); - assertEq(_available, _amount); + assertEq(_initialAvailable, _amount); uint256 startingPrice = ((auction.startingPrice() * (WAD / wantScaler)) * 1e18) / _amount / fromScaler; - assertEq(auction.price(id), startingPrice); + assertEq(auction.price(from), startingPrice); assertRelApproxEq( - auction.getAmountNeeded(id, _amount), + auction.getAmountNeeded(from, _amount), (startingPrice * fromScaler * _amount) / (WAD / wantScaler) / wantScaler, MAX_BPS ); - uint256 expectedPrice = auction.price(id, block.timestamp + 100); + uint256 expectedPrice = auction.price(from, block.timestamp + 100); assertLt(expectedPrice, startingPrice); uint256 expectedAmount = auction.getAmountNeeded( - id, + from, _amount, block.timestamp + 100 ); @@ -257,20 +216,20 @@ contract AuctionSwapperTest is Setup { skip(100); - assertEq(auction.price(id), expectedPrice); - assertEq(auction.getAmountNeeded(id, _amount), expectedAmount); + assertEq(auction.price(from), expectedPrice); + assertEq(auction.getAmountNeeded(from, _amount), expectedAmount); // Skip full auction skip(auction.auctionLength()); - assertEq(auction.price(id), 0); - assertEq(auction.getAmountNeeded(id, _amount), 0); + assertEq(auction.price(from), 0); + assertEq(auction.getAmountNeeded(from, _amount), 0); // Can't kick a new one yet vm.expectRevert("too soon"); - auction.kick(id); + auction.kick(from); - assertEq(auction.kickable(id), 0); + assertEq(auction.kickable(from), 0); } function test_takeAuction_default(uint256 _amount, uint16 _percent) public { @@ -282,25 +241,20 @@ contract AuctionSwapperTest is Setup { fromScaler = WAD / 10 ** ERC20(from).decimals(); wantScaler = WAD / 10 ** ERC20(asset).decimals(); - bytes32 id = swapper.enableAuction(from, address(asset)); + swapper.enableAuction(from, address(asset)); auction = Auction(swapper.auction()); airdrop(ERC20(from), address(swapper), _amount); - auction.kick(id); - - assertEq(auction.kickable(id), 0); - ( - address _from, - address _to, - uint256 _kicked, - uint256 _available - ) = auction.auctionInfo(id); - assertEq(_from, from); - assertEq(_to, address(asset)); + auction.kick(from); + + assertEq(auction.kickable(from), 0); + (uint64 _kicked, uint64 _scaler, uint128 _initialAvailable) = auction + .auctions(from); assertEq(_kicked, block.timestamp); - assertEq(_available, _amount); + assertEq(_scaler, fromScaler); + assertEq(_initialAvailable, _amount); assertEq(ERC20(from).balanceOf(address(swapper)), 0); assertEq(ERC20(from).balanceOf(address(auction)), _amount); @@ -308,7 +262,7 @@ contract AuctionSwapperTest is Setup { uint256 toTake = (_amount * _percent) / MAX_BPS; uint256 left = _amount - toTake; - uint256 needed = auction.getAmountNeeded(id, toTake); + uint256 needed = auction.getAmountNeeded(from, toTake); uint256 beforeAsset = ERC20(asset).balanceOf(address(this)); airdrop(ERC20(asset), address(this), needed); @@ -318,13 +272,14 @@ contract AuctionSwapperTest is Setup { uint256 before = ERC20(from).balanceOf(address(this)); vm.expectEmit(true, true, true, true, address(auction)); - emit AuctionTaken(id, toTake, left); - uint256 amountTaken = auction.take(id, toTake); + emit AuctionTaken(from, toTake, left); + uint256 amountTaken = auction.take(from, toTake); assertEq(amountTaken, toTake); - (, , , _available) = auction.auctionInfo(id); - assertEq(_available, left); + (, _kicked, _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); @@ -341,65 +296,60 @@ contract AuctionSwapperTest is Setup { fromScaler = WAD / 10 ** ERC20(from).decimals(); wantScaler = WAD / 10 ** ERC20(asset).decimals(); - bytes32 id = swapper.enableAuction(from, address(asset)); + swapper.enableAuction(from, address(asset)); auction = Auction(swapper.auction()); - assertEq(auction.kickable(id), 0); - ( - address _from, - address _to, - uint256 _kicked, - uint256 _available - ) = auction.auctionInfo(id); + assertEq(auction.kickable(from), 0); + (uint64 _kicked, uint64 _scaler, uint128 _initialAvailable) = auction + .auctions(from); - assertEq(_from, from); - assertEq(_to, address(asset)); assertEq(_kicked, 0); - assertEq(_available, 0); + assertEq(_scaler, fromScaler); + assertEq(_initialAvailable, 0); airdrop(ERC20(from), address(swapper), _amount); swapper.setUseDefault(false); - assertEq(auction.kickable(id), 0); + assertEq(auction.kickable(from), 0); uint256 kickable = _amount / 10; swapper.setLetKick(kickable); - assertEq(auction.kickable(id), kickable); - (, , _kicked, _available) = auction.auctionInfo(id); + assertEq(auction.kickable(from), kickable); + (, _kicked, _initialAvailable) = auction.auctions(from); assertEq(_kicked, 0); - assertEq(_available, 0); + assertEq(_initialAvailable, 0); vm.expectEmit(true, true, true, true, address(auction)); - emit AuctionKicked(id, kickable); - uint256 available = auction.kick(id); + emit AuctionKicked(from, kickable); + uint256 available = auction.kick(from); assertEq(ERC20(from).balanceOf(address(swapper)), _amount - kickable); assertEq(ERC20(from).balanceOf(address(auction)), kickable); - assertEq(auction.kickable(id), 0); - (, , _kicked, _available) = auction.auctionInfo(id); + assertEq(auction.kickable(from), 0); + (, _kicked, _initialAvailable) = auction.auctions(from); assertEq(_kicked, block.timestamp); - assertEq(_available, kickable); + assertEq(_initialAvailable, kickable); uint256 startingPrice = ((auction.startingPrice() * (WAD / wantScaler)) * 1e18) / kickable / fromScaler; - assertEq(auction.price(id), startingPrice); + assertEq(auction.price(from), startingPrice); assertRelApproxEq( - auction.getAmountNeeded(id, kickable), + auction.getAmountNeeded(from, kickable), (startingPrice * fromScaler * kickable) / (WAD / wantScaler) / wantScaler, MAX_BPS ); - uint256 expectedPrice = auction.price(id, block.timestamp + 100); + uint256 expectedPrice = auction.price(from, block.timestamp + 100); assertLt(expectedPrice, startingPrice); uint256 expectedAmount = auction.getAmountNeeded( - id, + from, kickable, block.timestamp + 100 ); @@ -412,20 +362,20 @@ contract AuctionSwapperTest is Setup { skip(100); - assertEq(auction.price(id), expectedPrice); - assertEq(auction.getAmountNeeded(id, kickable), expectedAmount); + assertEq(auction.price(from), expectedPrice); + assertEq(auction.getAmountNeeded(from, kickable), expectedAmount); // Skip full auction skip(auction.auctionLength()); - assertEq(auction.price(id), 0); - assertEq(auction.getAmountNeeded(id, kickable), 0); + assertEq(auction.price(from), 0); + assertEq(auction.getAmountNeeded(from, kickable), 0); // Can't kick a new one yet vm.expectRevert("too soon"); - auction.kick(id); + auction.kick(from); - assertEq(auction.kickable(id), 0); + assertEq(auction.kickable(from), 0); } function test_takeAuction_custom(uint256 _amount, uint16 _percent) public { @@ -437,7 +387,7 @@ contract AuctionSwapperTest is Setup { fromScaler = WAD / 10 ** ERC20(from).decimals(); wantScaler = WAD / 10 ** ERC20(asset).decimals(); - bytes32 id = swapper.enableAuction(from, address(asset)); + swapper.enableAuction(from, address(asset)); auction = Auction(swapper.auction()); @@ -445,24 +395,20 @@ contract AuctionSwapperTest is Setup { swapper.setUseDefault(false); - assertEq(auction.kickable(id), 0); + assertEq(auction.kickable(from), 0); uint256 kickable = _amount / 10; swapper.setLetKick(kickable); - auction.kick(id); - - assertEq(auction.kickable(id), 0); - ( - address _from, - address _to, - uint256 _kicked, - uint256 _available - ) = auction.auctionInfo(id); - assertEq(_from, from); - assertEq(_to, address(asset)); + auction.kick(from); + + assertEq(auction.kickable(from), 0); + (uint64 _kicked, uint64 _scaler, uint128 _initialAvailable) = auction + .auctions(from); + assertEq(_kicked, block.timestamp); - assertEq(_available, kickable); + assertEq(_scaler, fromScaler); + assertEq(_initialAvailable, kickable); assertEq(ERC20(from).balanceOf(address(swapper)), _amount - kickable); assertEq(ERC20(from).balanceOf(address(auction)), kickable); @@ -470,7 +416,7 @@ contract AuctionSwapperTest is Setup { uint256 toTake = (kickable * _percent) / MAX_BPS; uint256 left = kickable - toTake; - uint256 needed = auction.getAmountNeeded(id, toTake); + uint256 needed = auction.getAmountNeeded(from, toTake); uint256 beforeAsset = ERC20(asset).balanceOf(address(this)); airdrop(ERC20(asset), address(this), needed); @@ -483,75 +429,17 @@ contract AuctionSwapperTest is Setup { emit PreTake(from, toTake, needed); vm.expectEmit(true, true, true, true, address(swapper)); emit PostTake(address(asset), toTake, needed); - uint256 amountTaken = auction.take(id, toTake); + uint256 amountTaken = auction.take(from, toTake); assertEq(amountTaken, toTake); - (, , , _available) = auction.auctionInfo(id); - assertEq(_available, left); + (, _kicked, _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); assertEq(ERC20(asset).balanceOf(address(swapper)), needed); assertEq(ERC20(asset).balanceOf(address(auction)), 0); } - - function test_setFlags(uint256 _amount, uint16 _percent) public { - vm.assume(_amount >= minFuzzAmount && _amount <= maxFuzzAmount); - _percent = uint16(bound(uint256(_percent), 1_000, MAX_BPS)); - - address from = tokenAddrs["WBTC"]; - - fromScaler = WAD / 10 ** ERC20(from).decimals(); - wantScaler = WAD / 10 ** ERC20(asset).decimals(); - - bytes32 id = swapper.enableAuction(from, address(asset)); - - auction = Auction(swapper.auction()); - - airdrop(ERC20(from), address(swapper), _amount); - - assertEq(auction.kickable(id), _amount); - - vm.prank(address(swapper)); - auction.setHookFlags(false, false, true, true); - - assertEq(auction.kickable(id), 0); - - vm.expectRevert("nothing to kick"); - auction.kick(id); - - vm.prank(address(swapper)); - auction.setHookFlags(false, true, true, true); - - auction.kick(id); - - assertEq(ERC20(from).balanceOf(address(auction)), _amount); - - swapper.setShouldRevert(true); - - skip(auction.auctionLength() / 2); - - uint256 toTake = (_amount * _percent) / MAX_BPS; - uint256 left = _amount - toTake; - uint256 needed = auction.getAmountNeeded(id, toTake); - - airdrop(ERC20(asset), address(this), needed); - - ERC20(asset).safeApprove(address(auction), needed); - - vm.expectRevert("pre take revert"); - auction.take(id, toTake); - - vm.prank(address(swapper)); - auction.setHookFlags(false, true, false, true); - - vm.expectRevert("post take revert"); - auction.take(id, toTake); - - vm.prank(address(swapper)); - auction.setHookFlags(false, true, false, false); - - auction.take(id, toTake); - } } diff --git a/src/test/BaseAuctioneer.t.sol b/src/test/BaseAuctioneer.t.sol index ea9df23..321d61a 100644 --- a/src/test/BaseAuctioneer.t.sol +++ b/src/test/BaseAuctioneer.t.sol @@ -14,24 +14,14 @@ contract BaseAuctioneerTest is Setup { event DeployedNewAuction(address indexed auction, address indexed want); - event AuctionEnabled( - bytes32 auctionId, - address indexed from, - address indexed to, - address indexed strategy - ); + event AuctionEnabled(address indexed from, address indexed to); - event AuctionDisabled( - bytes32 auctionId, - address indexed from, - address indexed to, - address indexed strategy - ); + event AuctionDisabled(address indexed from, address indexed to); - event AuctionKicked(bytes32 auctionId, uint256 available); + event AuctionKicked(address indexed from, uint256 available); event AuctionTaken( - bytes32 auctionId, + address indexed from, uint256 amountTaken, uint256 amountLeft ); @@ -54,26 +44,23 @@ contract BaseAuctioneerTest is Setup { function test_enableAuction() public { address from = tokenAddrs["USDC"]; - bytes32 id = auctioneer.enableAuction(from); + auctioneer.enableAuction(from); + + assertEq(auctioneer.kickable(from), 0); + assertEq(auctioneer.getAmountNeeded(from, 1e18), 0); + assertEq(auctioneer.price(from), 0); - assertEq(auctioneer.kickable(id), 0); - assertEq(auctioneer.getAmountNeeded(id, 1e18), 0); - assertEq(auctioneer.price(id), 0); - ( - address _from, - address _to, - uint256 _kicked, - uint256 _available - ) = auctioneer.auctionInfo(id); + (uint64 _kicked, uint64 _scaler, uint128 _initialAvailable) = auctioneer + .auctions(from); - assertEq(_from, from); - assertEq(_to, address(asset)); assertEq(_kicked, 0); - assertEq(_available, 0); + assertEq(_scaler, 1e12); + assertEq(_initialAvailable, 0); + assertEq(auctioneer.available(from), 0); // Kicking it reverts vm.expectRevert("nothing to kick"); - auctioneer.kick(id); + auctioneer.kick(from); // Can't re-enable vm.expectRevert("already enabled"); @@ -83,78 +70,62 @@ contract BaseAuctioneerTest is Setup { function test_enableSecondAuction() public { address from = tokenAddrs["USDC"]; - bytes32 id = auctioneer.enableAuction(from); + auctioneer.enableAuction(from); - assertEq(auctioneer.kickable(id), 0); - assertEq(auctioneer.getAmountNeeded(id, 1e18), 0); - assertEq(auctioneer.price(id), 0); - ( - address _from, - address _to, - uint256 _kicked, - uint256 _available - ) = auctioneer.auctionInfo(id); + assertEq(auctioneer.kickable(from), 0); + assertEq(auctioneer.getAmountNeeded(from, 1e18), 0); + assertEq(auctioneer.price(from), 0); + (uint64 _kicked, uint64 _scaler, uint128 _initialAvailable) = auctioneer + .auctions(from); - assertEq(_from, from); - assertEq(_to, address(asset)); assertEq(_kicked, 0); - assertEq(_available, 0); + assertEq(_scaler, 1e12); + assertEq(_initialAvailable, 0); + assertEq(auctioneer.available(from), 0); address secondFrom = tokenAddrs["WETH"]; - bytes32 expectedId = auctioneer.getAuctionId(secondFrom); - vm.expectEmit(true, true, true, true, address(auctioneer)); - emit AuctionEnabled( - expectedId, - secondFrom, - address(asset), - address(auctioneer) - ); - bytes32 secondId = auctioneer.enableAuction(secondFrom); + emit AuctionEnabled(secondFrom, address(asset)); + auctioneer.enableAuction(secondFrom); - assertEq(expectedId, secondId); - assertEq(auctioneer.kickable(secondId), 0); - assertEq(auctioneer.getAmountNeeded(secondId, 1e18), 0); - assertEq(auctioneer.price(secondId), 0); - (_from, _to, _kicked, _available) = auctioneer.auctionInfo(secondId); + assertEq(auctioneer.kickable(secondFrom), 0); + assertEq(auctioneer.getAmountNeeded(secondFrom, 1e18), 0); + assertEq(auctioneer.price(secondFrom), 0); + (_kicked, _scaler, _initialAvailable) = auctioneer.auctions(secondFrom); - assertEq(_from, secondFrom); - assertEq(_to, address(asset)); assertEq(_kicked, 0); - assertEq(_available, 0); + assertEq(_scaler, 1e12); + assertEq(_initialAvailable, 0); + assertEq(auctioneer.available(secondFrom), 0); } function test_disableAuction() public { address from = tokenAddrs["USDC"]; - bytes32 id = auctioneer.enableAuction(from); + auctioneer.enableAuction(from); - assertEq(auctioneer.kickable(id), 0); - assertEq(auctioneer.getAmountNeeded(id, 1e18), 0); - assertEq(auctioneer.price(id), 0); - ( - address _from, - address _to, - uint256 _kicked, - uint256 _available - ) = auctioneer.auctionInfo(id); + assertEq(auctioneer.kickable(from), 0); + assertEq(auctioneer.getAmountNeeded(from, 1e18), 0); + assertEq(auctioneer.price(from), 0); + (uint64 _kicked, uint64 _scaler, uint128 _initialAvailable) = auctioneer + .auctions(from); - assertEq(_from, from); - assertEq(_to, address(asset)); assertEq(_kicked, 0); - assertEq(_available, 0); + assertEq(_scaler, 1e12); + assertEq(_initialAvailable, 0); + assertEq(auctioneer.available(from), 0); vm.expectEmit(true, true, true, true, address(auctioneer)); - emit AuctionDisabled(id, from, address(asset), address(auctioneer)); + emit AuctionDisabled(from, address(asset)); auctioneer.disableAuction(from); - (_from, _to, _kicked, _available) = auctioneer.auctionInfo(id); + (_kicked, _scaler, _initialAvailable) = auctioneer.auctions(from); - assertEq(_from, address(0)); - assertEq(_to, address(asset)); assertEq(_kicked, 0); - assertEq(_available, 0); + assertEq(_scaler, 0); + assertEq(_initialAvailable, 0); + assertEq(auctioneer.available(from), 0); } function test_kickAuction_default(uint256 _amount) public { @@ -165,56 +136,52 @@ contract BaseAuctioneerTest is Setup { fromScaler = WAD / 10 ** ERC20(from).decimals(); wantScaler = WAD / 10 ** ERC20(asset).decimals(); - bytes32 id = auctioneer.enableAuction(from); + auctioneer.enableAuction(from); - assertEq(auctioneer.kickable(id), 0); - ( - address _from, - address _to, - uint256 _kicked, - uint256 _available - ) = auctioneer.auctionInfo(id); + assertEq(auctioneer.kickable(from), 0); + (uint64 _kicked, uint64 _scaler, uint128 _initialAvailable) = auctioneer + .auctions(from); - assertEq(_from, from); - assertEq(_to, address(asset)); assertEq(_kicked, 0); - assertEq(_available, 0); + assertEq(_scaler, 1e12); + assertEq(_initialAvailable, 0); + assertEq(auctioneer.available(from), 0); airdrop(ERC20(from), address(auctioneer), _amount); - assertEq(auctioneer.kickable(id), _amount); - (, , _kicked, _available) = auctioneer.auctionInfo(id); + assertEq(auctioneer.kickable(from), _amount); + (, _kicked, _initialAvailable) = auctioneer.auctions(from); assertEq(_kicked, 0); - assertEq(_available, 0); + assertEq(_initialAvailable, _amount); vm.expectEmit(true, true, true, true, address(auctioneer)); - emit AuctionKicked(id, _amount); - uint256 available = auctioneer.kick(id); + emit AuctionKicked(from, _amount); + uint256 available = auctioneer.kick(from); assertEq(ERC20(from).balanceOf(address(auctioneer)), _amount); assertEq(ERC20(from).balanceOf(address(auctioneer)), available); - assertEq(auctioneer.kickable(id), 0); - (, , _kicked, _available) = auctioneer.auctionInfo(id); + assertEq(auctioneer.kickable(from), 0); + (, _kicked, _initialAvailable) = auctioneer.auctions(from); assertEq(_kicked, block.timestamp); - assertEq(_available, _amount); - uint256 startingPrice = ((auctioneer.auctionStartingPrice() * + assertEq(_initialAvailable, _amount); + uint256 startingPrice = ((auctioneer.startingPrice() * (WAD / wantScaler)) * 1e18) / _amount / fromScaler; - assertEq(auctioneer.price(id), startingPrice); + assertEq(auctioneer.price(from), startingPrice); assertRelApproxEq( - auctioneer.getAmountNeeded(id, _amount), + auctioneer.getAmountNeeded(from, _amount), (startingPrice * fromScaler * _amount) / (WAD / wantScaler) / wantScaler, MAX_BPS ); - uint256 expectedPrice = auctioneer.price(id, block.timestamp + 100); + uint256 expectedPrice = auctioneer.price(from, block.timestamp + 100); assertLt(expectedPrice, startingPrice); uint256 expectedAmount = auctioneer.getAmountNeeded( - id, + from, _amount, block.timestamp + 100 ); @@ -227,20 +194,20 @@ contract BaseAuctioneerTest is Setup { skip(100); - assertEq(auctioneer.price(id), expectedPrice); - assertEq(auctioneer.getAmountNeeded(id, _amount), expectedAmount); + assertEq(auctioneer.price(from), expectedPrice); + assertEq(auctioneer.getAmountNeeded(from, _amount), expectedAmount); // Skip full auction skip(auctioneer.auctionLength()); - assertEq(auctioneer.price(id), 0); - assertEq(auctioneer.getAmountNeeded(id, _amount), 0); + assertEq(auctioneer.price(from), 0); + assertEq(auctioneer.getAmountNeeded(from, _amount), 0); // Can't kick a new one yet vm.expectRevert("too soon"); - auctioneer.kick(id); + auctioneer.kick(from); - assertEq(auctioneer.kickable(id), 0); + assertEq(auctioneer.kickable(from), 0); } function test_takeAuction_default(uint256 _amount, uint16 _percent) public { @@ -252,185 +219,25 @@ contract BaseAuctioneerTest is Setup { fromScaler = WAD / 10 ** ERC20(from).decimals(); wantScaler = WAD / 10 ** ERC20(asset).decimals(); - bytes32 id = auctioneer.enableAuction(from); - - airdrop(ERC20(from), address(auctioneer), _amount); - - auctioneer.kick(id); - - assertEq(auctioneer.kickable(id), 0); - ( - address _from, - address _to, - uint256 _kicked, - uint256 _available - ) = auctioneer.auctionInfo(id); - assertEq(_from, from); - assertEq(_to, address(asset)); - assertEq(_kicked, block.timestamp); - assertEq(_available, _amount); - assertEq(ERC20(from).balanceOf(address(auctioneer)), _amount); - - skip(auctioneer.auctionLength() / 2); - - uint256 toTake = (_amount * _percent) / MAX_BPS; - uint256 left = _amount - toTake; - uint256 needed = auctioneer.getAmountNeeded(id, toTake); - uint256 beforeAsset = ERC20(asset).balanceOf(address(this)); - - airdrop(ERC20(asset), address(this), needed); - - ERC20(asset).safeApprove(address(auctioneer), needed); - - uint256 before = ERC20(from).balanceOf(address(this)); - - vm.expectEmit(true, true, true, true, address(auctioneer)); - emit AuctionTaken(id, toTake, left); - uint256 amountTaken = auctioneer.take(id, toTake); - - assertEq(amountTaken, toTake); - - (, , , _available) = auctioneer.auctionInfo(id); - assertEq(_available, left); - assertEq(ERC20(asset).balanceOf(address(this)), beforeAsset); - assertEq(ERC20(from).balanceOf(address(this)), before + toTake); - assertEq(ERC20(from).balanceOf(address(auctioneer)), left); - assertEq(ERC20(asset).balanceOf(address(auctioneer)), needed); - } - - function test_kickAuction_custom(uint256 _amount) public { - vm.assume(_amount >= minFuzzAmount && _amount <= maxFuzzAmount); - - address from = tokenAddrs["WBTC"]; - - fromScaler = WAD / 10 ** ERC20(from).decimals(); - wantScaler = WAD / 10 ** ERC20(asset).decimals(); - - bytes32 id = auctioneer.enableAuction(from); - - assertEq(auctioneer.kickable(id), 0); - ( - address _from, - address _to, - uint256 _kicked, - uint256 _available - ) = auctioneer.auctionInfo(id); - - assertEq(_from, from); - assertEq(_to, address(asset)); - assertEq(_kicked, 0); - assertEq(_available, 0); - - airdrop(ERC20(from), address(auctioneer), _amount); - - auctioneer.setUseDefault(false); - - assertEq(auctioneer.kickable(id), 0); - - uint256 kickable = _amount / 10; - auctioneer.setLetKick(kickable); - - assertEq(auctioneer.kickable(id), kickable); - (, , _kicked, _available) = auctioneer.auctionInfo(id); - assertEq(_kicked, 0); - assertEq(_available, 0); - - vm.expectEmit(true, true, true, true, address(auctioneer)); - emit AuctionKicked(id, kickable); - uint256 available = auctioneer.kick(id); - - assertEq(ERC20(from).balanceOf(address(auctioneer)), _amount); - assertEq(kickable, available); - - assertEq(auctioneer.kickable(id), 0); - (, , _kicked, _available) = auctioneer.auctionInfo(id); - assertEq(_kicked, block.timestamp); - assertEq(_available, kickable); - uint256 startingPrice = ((auctioneer.auctionStartingPrice() * - (WAD / wantScaler)) * 1e18) / - kickable / - fromScaler; - assertEq(auctioneer.price(id), startingPrice); - assertRelApproxEq( - auctioneer.getAmountNeeded(id, kickable), - (startingPrice * fromScaler * kickable) / - (WAD / wantScaler) / - wantScaler, - MAX_BPS - ); - - uint256 expectedPrice = auctioneer.price(id, block.timestamp + 100); - assertLt(expectedPrice, startingPrice); - uint256 expectedAmount = auctioneer.getAmountNeeded( - id, - kickable, - block.timestamp + 100 - ); - assertLt( - expectedAmount, - (startingPrice * fromScaler * kickable) / - (WAD / wantScaler) / - wantScaler - ); - - skip(100); - - assertEq(auctioneer.price(id), expectedPrice); - assertEq(auctioneer.getAmountNeeded(id, kickable), expectedAmount); - - // Skip full auction - skip(auctioneer.auctionLength()); - - assertEq(auctioneer.price(id), 0); - assertEq(auctioneer.getAmountNeeded(id, kickable), 0); - - // Can't kick a new one yet - vm.expectRevert("too soon"); - auctioneer.kick(id); - - assertEq(auctioneer.kickable(id), 0); - } - - function test_takeAuction_custom(uint256 _amount, uint16 _percent) public { - vm.assume(_amount >= minFuzzAmount && _amount <= maxFuzzAmount); - _percent = uint16(bound(uint256(_percent), 1_000, MAX_BPS)); - - address from = tokenAddrs["WBTC"]; - - fromScaler = WAD / 10 ** ERC20(from).decimals(); - wantScaler = WAD / 10 ** ERC20(asset).decimals(); - - bytes32 id = auctioneer.enableAuction(from); + auctioneer.enableAuction(from); airdrop(ERC20(from), address(auctioneer), _amount); - auctioneer.setUseDefault(false); - - assertEq(auctioneer.kickable(id), 0); - - uint256 kickable = _amount / 10; - auctioneer.setLetKick(kickable); + auctioneer.kick(from); - auctioneer.kick(id); - - assertEq(auctioneer.kickable(id), 0); - ( - address _from, - address _to, - uint256 _kicked, - uint256 _available - ) = auctioneer.auctionInfo(id); - assertEq(_from, from); - assertEq(_to, address(asset)); + assertEq(auctioneer.kickable(from), 0); + (uint64 _kicked, uint64 _scaler, uint128 _initialAvailable) = auctioneer + .auctions(from); assertEq(_kicked, block.timestamp); - assertEq(_available, kickable); + assertEq(_scaler, 1e12); + assertEq(_initialAvailable, _amount); assertEq(ERC20(from).balanceOf(address(auctioneer)), _amount); skip(auctioneer.auctionLength() / 2); - uint256 toTake = (kickable * _percent) / MAX_BPS; + uint256 toTake = (_amount * _percent) / MAX_BPS; uint256 left = _amount - toTake; - uint256 needed = auctioneer.getAmountNeeded(id, toTake); + uint256 needed = auctioneer.getAmountNeeded(from, toTake); uint256 beforeAsset = ERC20(asset).balanceOf(address(this)); airdrop(ERC20(asset), address(this), needed); @@ -440,15 +247,14 @@ contract BaseAuctioneerTest is Setup { uint256 before = ERC20(from).balanceOf(address(this)); vm.expectEmit(true, true, true, true, address(auctioneer)); - emit PreTake(from, toTake, needed); - vm.expectEmit(true, true, true, true, address(auctioneer)); - emit PostTake(address(asset), toTake, needed); - uint256 amountTaken = auctioneer.take(id, toTake); + emit AuctionTaken(from, toTake, left); + uint256 amountTaken = auctioneer.take(from, toTake); assertEq(amountTaken, toTake); - (, , , _available) = auctioneer.auctionInfo(id); - assertEq(_available, kickable - toTake); + (, , _initialAvailable) = auctioneer.auctions(from); + assertEq(_initialAvailable, _amount); + assertEq(auctioneer.available(from), left); assertEq(ERC20(asset).balanceOf(address(this)), beforeAsset); assertEq(ERC20(from).balanceOf(address(this)), before + toTake); assertEq(ERC20(from).balanceOf(address(auctioneer)), left); diff --git a/src/test/DumperAuction.t.sol b/src/test/DumperAuction.t.sol deleted file mode 100644 index f1f0ddf..0000000 --- a/src/test/DumperAuction.t.sol +++ /dev/null @@ -1,409 +0,0 @@ -// SPDX-License-Identifier: AGPL-3.0 -pragma solidity >=0.8.18; - -import "forge-std/console.sol"; -import {Setup, IStrategy, SafeERC20, ERC20} from "./utils/Setup.sol"; - -import {ITaker} from "../interfaces/ITaker.sol"; -import {DumperAuction, DumperAuctionFactory} from "../Auctions/DumperAuctionFactory.sol"; - -contract DumperAuctionTest is Setup, ITaker { - using SafeERC20 for ERC20; - - event AuctionEnabled(address indexed from, address indexed to); - - event AuctionDisabled(address indexed from, address indexed to); - - event AuctionKicked(address indexed from, uint256 available); - - event Callback( - address indexed from, - address _sender, - uint256 _amountTaken, - uint256 _amountNeeded, - bytes _data - ); - - DumperAuction public auction; - DumperAuctionFactory public auctionFactory; - - uint256 public wantScaler; - uint256 public fromScaler; - - bool public callbackHit; - - function setUp() public override { - super.setUp(); - - auctionFactory = new DumperAuctionFactory(); - } - - function test_setup() public { - assertEq(auctionFactory.DEFAULT_AUCTION_LENGTH(), 1 days); - assertEq(auctionFactory.DEFAULT_STARTING_PRICE(), 1e6); - } - - function test_defaults() public { - auction = DumperAuction( - auctionFactory.createNewAuction(address(asset)) - ); - - vm.expectRevert("initialized"); - auction.initialize(address(asset), address(this), management, 1, 10); - - assertEq(auction.want(), address(asset)); - assertEq(auction.receiver(), address(this)); - assertEq(auction.governance(), address(this)); - assertEq( - auction.auctionLength(), - auctionFactory.DEFAULT_AUCTION_LENGTH() - ); - assertEq( - auction.startingPrice(), - auctionFactory.DEFAULT_STARTING_PRICE() - ); - } - - function test_enableAuction() public { - address from = tokenAddrs["USDC"]; - auction = DumperAuction( - auctionFactory.createNewAuction(address(asset)) - ); - - vm.expectRevert("!governance"); - vm.prank(management); - auction.enable(from); - - vm.expectEmit(true, true, true, true, address(auction)); - emit AuctionEnabled(from, address(asset)); - - auction.enable(from); - - assertEq(auction.getAllEnabledAuctions().length, 1); - assertEq(auction.enabledAuctions(0), from); - assertEq(auction.kickable(from), 0); - assertEq(auction.getAmountNeeded(from, 1e18), 0); - assertEq(auction.price(from), 0); - assertEq(auction.receiver(), address(this)); - - (uint128 _kicked, uint128 _scaler, uint128 _initialAvailable) = auction - .auctions(from); - - assertEq(_kicked, 0); - assertEq(_scaler, 1e12); - assertEq(_initialAvailable, 0); - assertEq(auction.available(from), 0); - - // Kicking it reverts - vm.expectRevert("nothing to kick"); - auction.kick(from); - - // Can't re-enable - vm.expectRevert("already enabled"); - auction.enable(from); - } - - function test_disableAuction() public { - address from = tokenAddrs["USDC"]; - auction = DumperAuction( - auctionFactory.createNewAuction(address(asset)) - ); - - vm.expectRevert("not enabled"); - auction.disable(from); - - auction.enable(from); - - assertEq(auction.getAllEnabledAuctions().length, 1); - - (uint128 _kicked, uint128 _scaler, uint128 _initialAvailable) = auction - .auctions(from); - - assertEq(_kicked, 0); - assertEq(_scaler, 1e12); - assertEq(_initialAvailable, 0); - assertEq(auction.available(from), 0); - - vm.expectRevert("!governance"); - vm.prank(management); - auction.disable(from); - - vm.expectEmit(true, true, true, true, address(auction)); - emit AuctionDisabled(from, address(asset)); - auction.disable(from); - - assertEq(auction.getAllEnabledAuctions().length, 0); - - (_kicked, _scaler, _initialAvailable) = auction.auctions(from); - - assertEq(_kicked, 0); - assertEq(_scaler, 0); - assertEq(_initialAvailable, 0); - assertEq(auction.available(from), 0); - } - - function test_kickAuction(uint256 _amount) public { - vm.assume(_amount >= minFuzzAmount && _amount <= maxFuzzAmount); - - address from = tokenAddrs["WBTC"]; - auction = DumperAuction( - auctionFactory.createNewAuction(address(asset)) - ); - - fromScaler = WAD / 10 ** ERC20(from).decimals(); - wantScaler = WAD / 10 ** ERC20(asset).decimals(); - - auction.enable(from); - - assertEq(auction.kickable(from), 0); - (uint128 _kicked, uint128 _scaler, uint128 _initialAvailable) = auction - .auctions(from); - - assertEq(_kicked, 0); - assertEq(_scaler, 1e10); - assertEq(_initialAvailable, 0); - assertEq(auction.available(from), 0); - - airdrop(ERC20(from), address(auction), _amount); - - assertEq(auction.kickable(from), _amount); - (_kicked, , _initialAvailable) = auction.auctions(from); - assertEq(_kicked, 0); - assertEq(_initialAvailable, 0); - assertEq(auction.available(from), 0); - - uint256 available = auction.kick(from); - - assertEq(auction.kickable(from), 0); - (_kicked, , _initialAvailable) = auction.auctions(from); - assertEq(_kicked, block.timestamp); - assertEq(_initialAvailable, _amount); - assertEq(auction.available(from), _amount); - uint256 startingPrice = ((auction.startingPrice() * - (WAD / wantScaler)) * 1e18) / - _amount / - fromScaler; - assertEq(auction.price(from), startingPrice); - assertRelApproxEq( - auction.getAmountNeeded(from, _amount), - (startingPrice * fromScaler * _amount) / - (WAD / wantScaler) / - wantScaler, - MAX_BPS - ); - - uint256 expectedPrice = auction.price(from, block.timestamp + 100); - assertLt(expectedPrice, startingPrice); - uint256 expectedAmount = auction.getAmountNeeded( - from, - _amount, - block.timestamp + 100 - ); - assertLt( - expectedAmount, - (startingPrice * fromScaler * _amount) / - (WAD / wantScaler) / - wantScaler - ); - - skip(100); - - assertEq(auction.price(from), expectedPrice); - assertEq(auction.getAmountNeeded(from, _amount), expectedAmount); - - // Can't kick a new one yet - vm.expectRevert("too soon"); - auction.kick(from); - - // Skip full auction - skip(auction.auctionLength()); - - assertEq(auction.price(from), 0); - assertEq(auction.getAmountNeeded(from, _amount), 0); - assertEq(auction.available(from), 0); - - assertEq(auction.kickable(from), _amount); - } - - function test_takeAuction_all(uint256 _amount) public { - vm.assume(_amount >= minFuzzAmount && _amount <= maxFuzzAmount); - - address from = tokenAddrs["WBTC"]; - auction = DumperAuction( - auctionFactory.createNewAuction( - address(asset), - address(mockStrategy) - ) - ); - - fromScaler = WAD / 10 ** ERC20(from).decimals(); - wantScaler = WAD / 10 ** ERC20(asset).decimals(); - - auction.enable(from); - - airdrop(ERC20(from), address(auction), _amount); - - uint256 available = auction.kick(from); - - assertEq(auction.kickable(from), 0); - (uint128 _kicked, uint128 _scaler, uint128 _initialAvailable) = auction - .auctions(from); - assertEq(_kicked, block.timestamp); - assertEq(_scaler, 1e10); - assertEq(_initialAvailable, _amount); - assertEq(auction.available(from), _amount); - - skip(auction.auctionLength() / 2); - - uint256 needed = auction.getAmountNeeded(from, _amount); - uint256 beforeAsset = ERC20(asset).balanceOf(address(this)); - - airdrop(ERC20(asset), address(this), needed); - - ERC20(asset).safeApprove(address(auction), needed); - - uint256 before = ERC20(from).balanceOf(address(this)); - - uint256 amountTaken = auction.take(from); - - assertEq(amountTaken, _amount); - - (, , _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); - assertEq(ERC20(from).balanceOf(address(auction)), 0); - assertEq(ERC20(asset).balanceOf(address(mockStrategy)), needed); - assertEq(ERC20(asset).balanceOf(address(auction)), 0); - } - - function test_takeAuction_part(uint256 _amount, uint16 _percent) public { - vm.assume(_amount >= minFuzzAmount && _amount <= maxFuzzAmount); - _percent = uint16(bound(uint256(_percent), 1_000, MAX_BPS)); - - address from = tokenAddrs["WBTC"]; - auction = DumperAuction( - auctionFactory.createNewAuction( - address(asset), - address(mockStrategy) - ) - ); - - fromScaler = WAD / 10 ** ERC20(from).decimals(); - wantScaler = WAD / 10 ** ERC20(asset).decimals(); - - auction.enable(from); - - airdrop(ERC20(from), address(auction), _amount); - - auction.kick(from); - - assertEq(auction.kickable(from), 0); - (uint256 _kicked, uint256 _scaler, uint256 _initialAvailable) = auction - .auctions(from); - assertEq(_kicked, block.timestamp); - assertEq(_scaler, 1e10); - assertEq(_initialAvailable, _amount); - assertEq(auction.available(from), _amount); - - skip(auction.auctionLength() / 2); - - uint256 toTake = (_amount * _percent) / MAX_BPS; - uint256 left = _amount - toTake; - uint256 needed = auction.getAmountNeeded(from, toTake); - uint256 beforeAsset = ERC20(asset).balanceOf(address(this)); - - airdrop(ERC20(asset), address(this), needed); - - ERC20(asset).safeApprove(address(auction), needed); - - uint256 before = ERC20(from).balanceOf(address(this)); - - uint256 amountTaken = auction.take(from, toTake); - - assertEq(amountTaken, toTake); - - (, , _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); - assertEq(ERC20(asset).balanceOf(address(mockStrategy)), needed); - assertEq(ERC20(asset).balanceOf(address(auction)), 0); - } - - function test_takeAuction_callback(uint256 _amount) public { - vm.assume(_amount >= minFuzzAmount && _amount <= maxFuzzAmount); - - address from = tokenAddrs["WBTC"]; - auction = DumperAuction( - auctionFactory.createNewAuction( - address(asset), - address(mockStrategy) - ) - ); - - fromScaler = WAD / 10 ** ERC20(from).decimals(); - wantScaler = WAD / 10 ** ERC20(asset).decimals(); - - auction.enable(from); - - airdrop(ERC20(from), address(auction), _amount); - - auction.kick(from); - - assertEq(auction.kickable(from), 0); - (uint256 _kicked, uint256 _scaler, uint256 _initialAvailable) = auction - .auctions(from); - assertEq(_kicked, block.timestamp); - assertEq(_scaler, 1e10); - assertEq(_initialAvailable, _amount); - assertEq(auction.available(from), _amount); - skip(auction.auctionLength() / 2); - - uint256 toTake = _amount / 2; - uint256 left = _amount - toTake; - uint256 needed = auction.getAmountNeeded(from, toTake); - uint256 beforeAsset = ERC20(asset).balanceOf(address(this)); - - airdrop(ERC20(asset), address(this), needed); - - ERC20(asset).safeApprove(address(auction), needed); - - uint256 before = ERC20(from).balanceOf(address(this)); - - callbackHit = false; - bytes memory _data = new bytes(69); - - vm.expectEmit(true, true, true, true, address(this)); - emit Callback(from, address(this), toTake, needed, _data); - uint256 amountTaken = auction.take(from, toTake, address(this), _data); - - assertTrue(callbackHit); - assertEq(amountTaken, toTake); - - (, , _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); - assertEq(ERC20(asset).balanceOf(address(mockStrategy)), needed); - assertEq(ERC20(asset).balanceOf(address(auction)), 0); - } - - // Taker call back function - function auctionTakeCallback( - address _from, - address _sender, - uint256 _amountTaken, - uint256 _amountNeeded, - bytes memory _data - ) external { - callbackHit = true; - emit Callback(_from, _sender, _amountTaken, _amountNeeded, _data); - } -} diff --git a/src/test/mocks/MockAuctionSwapper.sol b/src/test/mocks/MockAuctionSwapper.sol index 0bc279d..135c9c9 100644 --- a/src/test/mocks/MockAuctionSwapper.sol +++ b/src/test/mocks/MockAuctionSwapper.sol @@ -12,8 +12,6 @@ contract MockAuctionSwapper is BaseStrategy, AuctionSwapper { bool public useDefault = true; - bool public shouldRevert; - uint256 public letKick; constructor(address _asset) BaseStrategy(_asset, "Mock Uni V3") {} @@ -30,11 +28,8 @@ contract MockAuctionSwapper is BaseStrategy, AuctionSwapper { _totalAssets = asset.balanceOf(address(this)); } - function enableAuction( - address _from, - address _to - ) external returns (bytes32) { - return _enableAuction(_from, _to); + function enableAuction(address _from, address _to) external { + _enableAuction(_from, _to); } function disableAuction(address _from) external { @@ -46,35 +41,13 @@ contract MockAuctionSwapper is BaseStrategy, AuctionSwapper { return letKick; } - function _auctionKicked( - address _token - ) internal override returns (uint256) { - if (useDefault) return super._auctionKicked(_token); + function kickAuction(address _token) internal returns (uint256) { + if (useDefault) return _kickAuction(_token); ERC20(_token).safeTransfer(auction, letKick); return ERC20(_token).balanceOf(auction); } - function _preTake( - address _token, - uint256 _amountToTake, - uint256 _amountToPay - ) internal override { - require(!shouldRevert, "pre take revert"); - if (useDefault) return; - emit PreTake(_token, _amountToTake, _amountToPay); - } - - function _postTake( - address _token, - uint256 _amountTaken, - uint256 _amountPayed - ) internal override { - require(!shouldRevert, "post take revert"); - if (useDefault) return; - emit PostTake(_token, _amountTaken, _amountPayed); - } - function setUseDefault(bool _useDefault) external { useDefault = _useDefault; } @@ -82,10 +55,6 @@ contract MockAuctionSwapper is BaseStrategy, AuctionSwapper { function setLetKick(uint256 _letKick) external { letKick = _letKick; } - - function setShouldRevert(bool _shouldRevert) external { - shouldRevert = _shouldRevert; - } } import {IStrategy} from "@tokenized-strategy/interfaces/IStrategy.sol"; @@ -107,5 +76,5 @@ interface IMockAuctionSwapper is IStrategy, IAuctionSwapper { function setLetKick(uint256 _letKick) external; - function setShouldRevert(bool _shouldRevert) external; + function kickAuction(address _token) external returns (uint256); } diff --git a/src/test/mocks/MockAuctioneer.sol b/src/test/mocks/MockAuctioneer.sol index 648f964..d3b2688 100644 --- a/src/test/mocks/MockAuctioneer.sol +++ b/src/test/mocks/MockAuctioneer.sol @@ -1,24 +1,16 @@ // SPDX-License-Identifier: GPL-3.0 pragma solidity >=0.8.18; -import {BaseAuctioneer, SafeERC20} from "../../Bases/Auctioneer/BaseAuctioneer.sol"; +import {SafeERC20} from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol"; +import {BaseAuctioneer} from "../../Bases/Auctioneer/BaseAuctioneer.sol"; import {BaseStrategy, ERC20} from "@tokenized-strategy/BaseStrategy.sol"; contract MockAuctioneer is BaseAuctioneer { using SafeERC20 for ERC20; - event PreTake(address token, uint256 amountToTake, uint256 amountToPay); - event PostTake(address token, uint256 amountTaken, uint256 amountPayed); - - bool public useDefault = true; - - bool public shouldRevert; - - uint256 public letKick; - constructor( address _asset - ) BaseAuctioneer(_asset, "Mock Auctioneer", _asset, 1 days, 5 days, 1e7) {} + ) BaseAuctioneer(_asset, "Mock Auctioneer", msg.sender, 1 days, 1e7) {} function _deployFunds(uint256) internal override {} @@ -31,65 +23,9 @@ contract MockAuctioneer is BaseAuctioneer { { _totalAssets = asset.balanceOf(address(this)); } - - function _kickable( - address _token - ) internal view override returns (uint256) { - if (useDefault) return super._kickable(_token); - return letKick; - } - - function _auctionKicked( - address _token - ) internal override returns (uint256) { - if (useDefault) return super._auctionKicked(_token); - return letKick; - } - - function _preTake( - address _token, - uint256 _amountToTake, - uint256 _amountToPay - ) internal override { - require(!shouldRevert, "pre take revert"); - if (useDefault) return; - emit PreTake(_token, _amountToTake, _amountToPay); - } - - function _postTake( - address _token, - uint256 _amountTaken, - uint256 _amountPayed - ) internal override { - require(!shouldRevert, "post take revert"); - if (useDefault) return; - emit PostTake(_token, _amountTaken, _amountPayed); - } - - function setUseDefault(bool _useDefault) external { - useDefault = _useDefault; - } - - function setLetKick(uint256 _letKick) external { - letKick = _letKick; - } - - function setShouldRevert(bool _shouldRevert) external { - shouldRevert = _shouldRevert; - } } import {IStrategy} from "@tokenized-strategy/interfaces/IStrategy.sol"; import {IBaseAuctioneer} from "../../Bases/Auctioneer/IBaseAuctioneer.sol"; -interface IMockAuctioneer is IStrategy, IBaseAuctioneer { - function useDefault() external view returns (bool); - - function setUseDefault(bool _useDefault) external; - - function letKick() external view returns (uint256); - - function setLetKick(uint256 _letKick) external; - - function setShouldRevert(bool _shouldRevert) external; -} +interface IMockAuctioneer is IStrategy, IBaseAuctioneer {} From 785d5586faa0e8f4779344d3a9287083bf2d93ca Mon Sep 17 00:00:00 2001 From: Schlagonia Date: Tue, 12 Nov 2024 23:11:14 -0700 Subject: [PATCH 11/12] chore: deploy auction --- script/DeployAuction.s.sol | 2 +- src/Auctions/Auction.sol | 2 +- src/Bases/Auctioneer/IBaseAuctioneer.sol | 6 +- src/swappers/AuctionSwapper.sol | 2 +- src/test/AuctionSwapper.t.sol | 81 ++++++++---------------- src/test/BaseAuctioneer.t.sol | 42 ++++-------- src/test/mocks/MockAuctionSwapper.sol | 13 ++-- 7 files changed, 54 insertions(+), 94 deletions(-) diff --git a/script/DeployAuction.s.sol b/script/DeployAuction.s.sol index 2de4f52..fbb31d8 100644 --- a/script/DeployAuction.s.sol +++ b/script/DeployAuction.s.sol @@ -10,7 +10,7 @@ contract DeployAuction is BaseScript { vm.startBroadcast(); // Get the bytecode - bytes memory bytecode = vm.getCode("DumperAuctionFactory.sol:DumperAuctionFactory"); + bytes memory bytecode = vm.getCode("AuctionFactory.sol:AuctionFactory"); bytes32 salt; diff --git a/src/Auctions/Auction.sol b/src/Auctions/Auction.sol index 23a6e35..416bcbf 100644 --- a/src/Auctions/Auction.sol +++ b/src/Auctions/Auction.sol @@ -29,7 +29,7 @@ contract Auction is Governance2Step, ReentrancyGuard { event AuctionDisabled(address indexed from, address indexed to); /// @notice Emitted when auction has been kicked. - event AuctionKicked(address from, uint256 available); + event AuctionKicked(address indexed from, uint256 available); /// @notice Emitted when the starting price is updated. event UpdatedStartingPrice(uint256 startingPrice); diff --git a/src/Bases/Auctioneer/IBaseAuctioneer.sol b/src/Bases/Auctioneer/IBaseAuctioneer.sol index 53d3564..a706f90 100644 --- a/src/Bases/Auctioneer/IBaseAuctioneer.sol +++ b/src/Bases/Auctioneer/IBaseAuctioneer.sol @@ -45,11 +45,11 @@ interface IBaseAuctioneer is IBaseHealthCheck { uint256 _timestamp ) external view returns (uint256); - function enableAuction(address _from) external; + function enable(address _from) external; - function disableAuction(address _from) external; + function disable(address _from) external; - function disableAuction(address _from, uint256 _index) external; + function disable(address _from, uint256 _index) external; function kick(address _from) external returns (uint256 available); diff --git a/src/swappers/AuctionSwapper.sol b/src/swappers/AuctionSwapper.sol index bd372d5..62e8b74 100644 --- a/src/swappers/AuctionSwapper.sol +++ b/src/swappers/AuctionSwapper.sol @@ -43,7 +43,7 @@ contract AuctionSwapper { /// @notice The pre-deployed Auction factory for cloning. address public constant auctionFactory = - 0xE6aB098E8582178A76DC80d55ca304d1Dec11AD8; + 0xa076c247AfA44f8F006CA7f21A4EF59f7e4dc605; /// @notice Address of the specific Auction this strategy uses. address public auction; diff --git a/src/test/AuctionSwapper.t.sol b/src/test/AuctionSwapper.t.sol index a6a4ce5..fb1242a 100644 --- a/src/test/AuctionSwapper.t.sol +++ b/src/test/AuctionSwapper.t.sol @@ -10,9 +10,6 @@ import {Auction, AuctionFactory} from "../Auctions/AuctionFactory.sol"; contract AuctionSwapperTest is Setup { using SafeERC20 for ERC20; - event PreTake(address token, uint256 amountToTake, uint256 amountToPay); - event PostTake(address token, uint256 amountTaken, uint256 amountPayed); - event DeployedNewAuction(address indexed auction, address indexed want); event AuctionEnabled(address indexed from, address indexed to); @@ -21,17 +18,11 @@ contract AuctionSwapperTest is Setup { event AuctionKicked(address indexed token, uint256 available); - event AuctionTaken( - address indexed token, - uint256 amountTaken, - uint256 amountLeft - ); - IMockAuctionSwapper public swapper; Auction public auction; AuctionFactory public auctionFactory = - AuctionFactory(0x4A14145C4977E18c719BB70E6FcBF8fBFF6F62d2); + AuctionFactory(0xa076c247AfA44f8F006CA7f21A4EF59f7e4dc605); uint256 public wantScaler; uint256 public fromScaler; @@ -55,6 +46,7 @@ contract AuctionSwapperTest is Setup { auction = Auction(swapper.auction()); assertNeq(address(auction), address(0)); + assertEq(swapper.kickable(from), 0); assertEq(auction.kickable(from), 0); assertEq(auction.getAmountNeeded(from, 1e18), 0); assertEq(auction.price(from), 0); @@ -68,8 +60,8 @@ contract AuctionSwapperTest is Setup { assertEq(auction.available(from), 0); // Kicking it reverts - vm.expectRevert("nothing to kick"); - auction.kick(from); + vm.expectRevert(); + swapper.kickAuction(from); // Can't re-enable vm.expectRevert("already enabled"); @@ -85,6 +77,7 @@ contract AuctionSwapperTest is Setup { auction = Auction(swapper.auction()); assertNeq(address(auction), address(0)); + assertEq(swapper.kickable(from), 0); assertEq(auction.kickable(from), 0); assertEq(auction.getAmountNeeded(from, 1e18), 0); assertEq(auction.price(from), 0); @@ -107,13 +100,14 @@ contract AuctionSwapperTest is Setup { swapper.enableAuction(secondFrom, address(asset)); assertEq(swapper.auction(), address(auction)); + assertEq(swapper.kickable(secondFrom), 0); assertEq(auction.kickable(secondFrom), 0); assertEq(auction.getAmountNeeded(secondFrom, 1e18), 0); assertEq(auction.price(secondFrom), 0); (_kicked, _scaler, _initialAvailable) = auction.auctions(secondFrom); assertEq(_kicked, 0); - assertEq(_scaler, 1e12); + assertEq(_scaler, 1); assertEq(_initialAvailable, 0); assertEq(auction.available(secondFrom), 0); } @@ -126,6 +120,7 @@ contract AuctionSwapperTest is Setup { auction = Auction(swapper.auction()); assertNeq(address(auction), address(0)); + assertEq(swapper.kickable(from), 0); assertEq(auction.kickable(from), 0); assertEq(auction.getAmountNeeded(from, 1e18), 0); assertEq(auction.price(from), 0); @@ -161,6 +156,7 @@ contract AuctionSwapperTest is Setup { auction = Auction(swapper.auction()); + assertEq(swapper.kickable(from), 0); assertEq(auction.kickable(from), 0); (uint64 _kicked, uint64 _scaler, uint128 _initialAvailable) = auction .auctions(from); @@ -171,20 +167,20 @@ contract AuctionSwapperTest is Setup { airdrop(ERC20(from), address(swapper), _amount); - assertEq(auction.kickable(from), _amount); - (, _kicked, _initialAvailable) = auction.auctions(from); + assertEq(swapper.kickable(from), _amount); + assertEq(auction.kickable(from), 0); + (_kicked, , _initialAvailable) = auction.auctions(from); assertEq(_kicked, 0); assertEq(_initialAvailable, 0); - vm.expectEmit(true, true, true, true, address(auction)); - emit AuctionKicked(from, _amount); - uint256 available = auction.kick(from); + uint256 available = swapper.kickAuction(from); assertEq(ERC20(from).balanceOf(address(swapper)), 0); assertEq(ERC20(from).balanceOf(address(auction)), _amount); + assertEq(swapper.kickable(from), 0); assertEq(auction.kickable(from), 0); - (, _kicked, _initialAvailable) = auction.auctions(from); + (_kicked, , _initialAvailable) = auction.auctions(from); assertEq(_kicked, block.timestamp); assertEq(_initialAvailable, _amount); uint256 startingPrice = ((auction.startingPrice() * @@ -224,12 +220,6 @@ contract AuctionSwapperTest is Setup { assertEq(auction.price(from), 0); assertEq(auction.getAmountNeeded(from, _amount), 0); - - // Can't kick a new one yet - vm.expectRevert("too soon"); - auction.kick(from); - - assertEq(auction.kickable(from), 0); } function test_takeAuction_default(uint256 _amount, uint16 _percent) public { @@ -247,9 +237,9 @@ contract AuctionSwapperTest is Setup { airdrop(ERC20(from), address(swapper), _amount); - auction.kick(from); + swapper.kickAuction(from); - assertEq(auction.kickable(from), 0); + assertEq(swapper.kickable(from), 0); (uint64 _kicked, uint64 _scaler, uint128 _initialAvailable) = auction .auctions(from); assertEq(_kicked, block.timestamp); @@ -271,13 +261,11 @@ contract AuctionSwapperTest is Setup { uint256 before = ERC20(from).balanceOf(address(this)); - vm.expectEmit(true, true, true, true, address(auction)); - emit AuctionTaken(from, toTake, left); uint256 amountTaken = auction.take(from, toTake); assertEq(amountTaken, toTake); - (, _kicked, _initialAvailable) = auction.auctions(from); + (_kicked, , _initialAvailable) = auction.auctions(from); assertEq(_initialAvailable, _amount); assertEq(auction.available(from), left); assertEq(ERC20(asset).balanceOf(address(this)), beforeAsset); @@ -300,7 +288,7 @@ contract AuctionSwapperTest is Setup { auction = Auction(swapper.auction()); - assertEq(auction.kickable(from), 0); + assertEq(swapper.kickable(from), 0); (uint64 _kicked, uint64 _scaler, uint128 _initialAvailable) = auction .auctions(from); @@ -312,25 +300,23 @@ contract AuctionSwapperTest is Setup { swapper.setUseDefault(false); - assertEq(auction.kickable(from), 0); + assertEq(swapper.kickable(from), 0); uint256 kickable = _amount / 10; swapper.setLetKick(kickable); - assertEq(auction.kickable(from), kickable); - (, _kicked, _initialAvailable) = auction.auctions(from); + assertEq(swapper.kickable(from), kickable); + (_kicked, , _initialAvailable) = auction.auctions(from); assertEq(_kicked, 0); assertEq(_initialAvailable, 0); - vm.expectEmit(true, true, true, true, address(auction)); - emit AuctionKicked(from, kickable); - uint256 available = auction.kick(from); + uint256 available = swapper.kickAuction(from); assertEq(ERC20(from).balanceOf(address(swapper)), _amount - kickable); assertEq(ERC20(from).balanceOf(address(auction)), kickable); - assertEq(auction.kickable(from), 0); - (, _kicked, _initialAvailable) = auction.auctions(from); + assertEq(swapper.kickable(from), 0); + (_kicked, , _initialAvailable) = auction.auctions(from); assertEq(_kicked, block.timestamp); assertEq(_initialAvailable, kickable); uint256 startingPrice = ((auction.startingPrice() * @@ -370,12 +356,6 @@ contract AuctionSwapperTest is Setup { assertEq(auction.price(from), 0); assertEq(auction.getAmountNeeded(from, kickable), 0); - - // Can't kick a new one yet - vm.expectRevert("too soon"); - auction.kick(from); - - assertEq(auction.kickable(from), 0); } function test_takeAuction_custom(uint256 _amount, uint16 _percent) public { @@ -395,14 +375,13 @@ contract AuctionSwapperTest is Setup { swapper.setUseDefault(false); - assertEq(auction.kickable(from), 0); + assertEq(swapper.kickable(from), 0); uint256 kickable = _amount / 10; swapper.setLetKick(kickable); - auction.kick(from); + swapper.kickAuction(from); - assertEq(auction.kickable(from), 0); (uint64 _kicked, uint64 _scaler, uint128 _initialAvailable) = auction .auctions(from); @@ -425,15 +404,11 @@ contract AuctionSwapperTest is Setup { uint256 before = ERC20(from).balanceOf(address(this)); - vm.expectEmit(true, true, true, true, address(swapper)); - emit PreTake(from, toTake, needed); - vm.expectEmit(true, true, true, true, address(swapper)); - emit PostTake(address(asset), toTake, needed); uint256 amountTaken = auction.take(from, toTake); assertEq(amountTaken, toTake); - (, _kicked, _initialAvailable) = auction.auctions(from); + (_kicked, , _initialAvailable) = auction.auctions(from); assertEq(_initialAvailable, _amount); assertEq(auction.available(from), left); assertEq(ERC20(asset).balanceOf(address(this)), beforeAsset); diff --git a/src/test/BaseAuctioneer.t.sol b/src/test/BaseAuctioneer.t.sol index 321d61a..8e0fafe 100644 --- a/src/test/BaseAuctioneer.t.sol +++ b/src/test/BaseAuctioneer.t.sol @@ -20,12 +20,6 @@ contract BaseAuctioneerTest is Setup { event AuctionKicked(address indexed from, uint256 available); - event AuctionTaken( - address indexed from, - uint256 amountTaken, - uint256 amountLeft - ); - IMockAuctioneer public auctioneer; uint256 public wantScaler; @@ -44,7 +38,7 @@ contract BaseAuctioneerTest is Setup { function test_enableAuction() public { address from = tokenAddrs["USDC"]; - auctioneer.enableAuction(from); + auctioneer.enable(from); assertEq(auctioneer.kickable(from), 0); assertEq(auctioneer.getAmountNeeded(from, 1e18), 0); @@ -64,13 +58,13 @@ contract BaseAuctioneerTest is Setup { // Can't re-enable vm.expectRevert("already enabled"); - auctioneer.enableAuction(from); + auctioneer.enable(from); } function test_enableSecondAuction() public { address from = tokenAddrs["USDC"]; - auctioneer.enableAuction(from); + auctioneer.enable(from); assertEq(auctioneer.kickable(from), 0); assertEq(auctioneer.getAmountNeeded(from, 1e18), 0); @@ -87,7 +81,7 @@ contract BaseAuctioneerTest is Setup { vm.expectEmit(true, true, true, true, address(auctioneer)); emit AuctionEnabled(secondFrom, address(asset)); - auctioneer.enableAuction(secondFrom); + auctioneer.enable(secondFrom); assertEq(auctioneer.kickable(secondFrom), 0); assertEq(auctioneer.getAmountNeeded(secondFrom, 1e18), 0); @@ -95,7 +89,7 @@ contract BaseAuctioneerTest is Setup { (_kicked, _scaler, _initialAvailable) = auctioneer.auctions(secondFrom); assertEq(_kicked, 0); - assertEq(_scaler, 1e12); + assertEq(_scaler, 1); assertEq(_initialAvailable, 0); assertEq(auctioneer.available(secondFrom), 0); } @@ -103,7 +97,7 @@ contract BaseAuctioneerTest is Setup { function test_disableAuction() public { address from = tokenAddrs["USDC"]; - auctioneer.enableAuction(from); + auctioneer.enable(from); assertEq(auctioneer.kickable(from), 0); assertEq(auctioneer.getAmountNeeded(from, 1e18), 0); @@ -118,7 +112,7 @@ contract BaseAuctioneerTest is Setup { vm.expectEmit(true, true, true, true, address(auctioneer)); emit AuctionDisabled(from, address(asset)); - auctioneer.disableAuction(from); + auctioneer.disable(from); (_kicked, _scaler, _initialAvailable) = auctioneer.auctions(from); @@ -136,23 +130,23 @@ contract BaseAuctioneerTest is Setup { fromScaler = WAD / 10 ** ERC20(from).decimals(); wantScaler = WAD / 10 ** ERC20(asset).decimals(); - auctioneer.enableAuction(from); + auctioneer.enable(from); assertEq(auctioneer.kickable(from), 0); (uint64 _kicked, uint64 _scaler, uint128 _initialAvailable) = auctioneer .auctions(from); assertEq(_kicked, 0); - assertEq(_scaler, 1e12); + assertEq(_scaler, 1e10); assertEq(_initialAvailable, 0); assertEq(auctioneer.available(from), 0); airdrop(ERC20(from), address(auctioneer), _amount); assertEq(auctioneer.kickable(from), _amount); - (, _kicked, _initialAvailable) = auctioneer.auctions(from); + (_kicked, , _initialAvailable) = auctioneer.auctions(from); assertEq(_kicked, 0); - assertEq(_initialAvailable, _amount); + assertEq(_initialAvailable, 0); vm.expectEmit(true, true, true, true, address(auctioneer)); emit AuctionKicked(from, _amount); @@ -162,7 +156,7 @@ contract BaseAuctioneerTest is Setup { assertEq(ERC20(from).balanceOf(address(auctioneer)), available); assertEq(auctioneer.kickable(from), 0); - (, _kicked, _initialAvailable) = auctioneer.auctions(from); + (_kicked, , _initialAvailable) = auctioneer.auctions(from); assertEq(_kicked, block.timestamp); assertEq(_initialAvailable, _amount); uint256 startingPrice = ((auctioneer.startingPrice() * @@ -202,12 +196,6 @@ contract BaseAuctioneerTest is Setup { assertEq(auctioneer.price(from), 0); assertEq(auctioneer.getAmountNeeded(from, _amount), 0); - - // Can't kick a new one yet - vm.expectRevert("too soon"); - auctioneer.kick(from); - - assertEq(auctioneer.kickable(from), 0); } function test_takeAuction_default(uint256 _amount, uint16 _percent) public { @@ -219,7 +207,7 @@ contract BaseAuctioneerTest is Setup { fromScaler = WAD / 10 ** ERC20(from).decimals(); wantScaler = WAD / 10 ** ERC20(asset).decimals(); - auctioneer.enableAuction(from); + auctioneer.enable(from); airdrop(ERC20(from), address(auctioneer), _amount); @@ -229,7 +217,7 @@ contract BaseAuctioneerTest is Setup { (uint64 _kicked, uint64 _scaler, uint128 _initialAvailable) = auctioneer .auctions(from); assertEq(_kicked, block.timestamp); - assertEq(_scaler, 1e12); + assertEq(_scaler, 1e10); assertEq(_initialAvailable, _amount); assertEq(ERC20(from).balanceOf(address(auctioneer)), _amount); @@ -246,8 +234,6 @@ contract BaseAuctioneerTest is Setup { uint256 before = ERC20(from).balanceOf(address(this)); - vm.expectEmit(true, true, true, true, address(auctioneer)); - emit AuctionTaken(from, toTake, left); uint256 amountTaken = auctioneer.take(from, toTake); assertEq(amountTaken, toTake); diff --git a/src/test/mocks/MockAuctionSwapper.sol b/src/test/mocks/MockAuctionSwapper.sol index 135c9c9..e6fe1f4 100644 --- a/src/test/mocks/MockAuctionSwapper.sol +++ b/src/test/mocks/MockAuctionSwapper.sol @@ -1,7 +1,7 @@ // SPDX-License-Identifier: GPL-3.0 pragma solidity >=0.8.18; -import {AuctionSwapper, SafeERC20} from "../../swappers/AuctionSwapper.sol"; +import {AuctionSwapper, Auction, SafeERC20} from "../../swappers/AuctionSwapper.sol"; import {BaseStrategy, ERC20} from "@tokenized-strategy/BaseStrategy.sol"; contract MockAuctionSwapper is BaseStrategy, AuctionSwapper { @@ -41,11 +41,11 @@ contract MockAuctionSwapper is BaseStrategy, AuctionSwapper { return letKick; } - function kickAuction(address _token) internal returns (uint256) { + function kickAuction(address _token) external returns (uint256) { if (useDefault) return _kickAuction(_token); ERC20(_token).safeTransfer(auction, letKick); - return ERC20(_token).balanceOf(auction); + return Auction(auction).kick(_token); } function setUseDefault(bool _useDefault) external { @@ -61,10 +61,7 @@ import {IStrategy} from "@tokenized-strategy/interfaces/IStrategy.sol"; import {IAuctionSwapper} from "../../swappers/interfaces/IAuctionSwapper.sol"; interface IMockAuctionSwapper is IStrategy, IAuctionSwapper { - function enableAuction( - address _from, - address _to - ) external returns (bytes32); + function enableAuction(address _from, address _to) external; function disableAuction(address _from) external; @@ -77,4 +74,6 @@ interface IMockAuctionSwapper is IStrategy, IAuctionSwapper { function setLetKick(uint256 _letKick) external; function kickAuction(address _token) external returns (uint256); + + function kickable(address _token) external view returns (uint256); } From 3a379133716acae00b7225a7167efbd7ca92ab7e Mon Sep 17 00:00:00 2001 From: Schlagonia Date: Wed, 13 Nov 2024 09:04:00 -0700 Subject: [PATCH 12/12] test: swapper --- src/test/AuctionSwapper.t.sol | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/test/AuctionSwapper.t.sol b/src/test/AuctionSwapper.t.sol index fb1242a..5efe247 100644 --- a/src/test/AuctionSwapper.t.sol +++ b/src/test/AuctionSwapper.t.sol @@ -315,7 +315,6 @@ contract AuctionSwapperTest is Setup { assertEq(ERC20(from).balanceOf(address(swapper)), _amount - kickable); assertEq(ERC20(from).balanceOf(address(auction)), kickable); - assertEq(swapper.kickable(from), 0); (_kicked, , _initialAvailable) = auction.auctions(from); assertEq(_kicked, block.timestamp); assertEq(_initialAvailable, kickable); @@ -409,7 +408,7 @@ contract AuctionSwapperTest is Setup { assertEq(amountTaken, toTake); (_kicked, , _initialAvailable) = auction.auctions(from); - assertEq(_initialAvailable, _amount); + assertEq(_initialAvailable, kickable); assertEq(auction.available(from), left); assertEq(ERC20(asset).balanceOf(address(this)), beforeAsset); assertEq(ERC20(from).balanceOf(address(this)), before + toTake);