diff --git a/script/DeployAuction.s.sol b/script/DeployAuction.s.sol new file mode 100644 index 0000000..fbb31d8 --- /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 DeployAuction is BaseScript { + + function run() external { + vm.startBroadcast(); + + // Get the bytecode + bytes memory bytecode = vm.getCode("AuctionFactory.sol:AuctionFactory"); + + 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/Auction.sol b/src/Auctions/Auction.sol index df883d9..416bcbf 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 indexed 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( - _auctionId, + _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/Bases/Auctioneer/BaseAuctioneer.sol b/src/Bases/Auctioneer/BaseAuctioneer.sol index 78a3305..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( - _auctionId, - 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..a706f90 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 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(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/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/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))) + } + } +} diff --git a/src/swappers/AuctionSwapper.sol b/src/swappers/AuctionSwapper.sol index 5403ffb..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; @@ -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 826426e..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( - bytes32 _auctionId, + 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.TokenInfo memory _token, , address _receiver, , ) = auction - .auctions(id); - assertEq(_token.tokenAddress, from); - assertEq(_receiver, address(this)); + 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(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); - - // Can't kick a new one yet - vm.expectRevert("too soon"); - auction.kick(id); + assertEq(auction.price(from), 0); + assertEq(auction.getAmountNeeded(from, _amount), 0); + assertEq(auction.available(from), 0); - 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); @@ -419,14 +371,15 @@ 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); - uint256 amountTaken = auction.take(id, toTake, address(this), _data); + emit Callback(from, address(this), toTake, needed, _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); @@ -436,13 +389,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); } } diff --git a/src/test/AuctionSwapper.t.sol b/src/test/AuctionSwapper.t.sol index cc6be41..5efe247 100644 --- a/src/test/AuctionSwapper.t.sol +++ b/src/test/AuctionSwapper.t.sol @@ -10,38 +10,19 @@ 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( - 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 token, uint256 available); IMockAuctionSwapper public swapper; Auction public auction; AuctionFactory public auctionFactory = - AuctionFactory(0x4A14145C4977E18c719BB70E6FcBF8fBFF6F62d2); + AuctionFactory(0xa076c247AfA44f8F006CA7f21A4EF59f7e4dc605); uint256 public wantScaler; uint256 public fromScaler; @@ -61,35 +42,26 @@ 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(swapper.kickable(from), 0); + 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); + vm.expectRevert(); + swapper.kickAuction(from); // Can't re-enable vm.expectRevert("already enabled"); @@ -100,89 +72,76 @@ 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(swapper.kickable(from), 0); + 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(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(_from, secondFrom); - assertEq(_to, address(asset)); assertEq(_kicked, 0); - assertEq(_available, 0); + assertEq(_scaler, 1); + 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(swapper.kickable(from), 0); + 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 +152,54 @@ 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(swapper.kickable(from), 0); + 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(swapper.kickable(from), _amount); + assertEq(auction.kickable(from), 0); + (_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); + uint256 available = swapper.kickAuction(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(swapper.kickable(from), 0); + 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 +212,14 @@ 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); - - // Can't kick a new one yet - vm.expectRevert("too soon"); - auction.kick(id); - - assertEq(auction.kickable(id), 0); + assertEq(auction.price(from), 0); + assertEq(auction.getAmountNeeded(from, _amount), 0); } function test_takeAuction_default(uint256 _amount, uint16 _percent) public { @@ -282,25 +231,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)); + swapper.kickAuction(from); + + assertEq(swapper.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 +252,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); @@ -317,14 +261,13 @@ 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); + 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 +284,57 @@ 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(swapper.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(swapper.kickable(from), 0); uint256 kickable = _amount / 10; swapper.setLetKick(kickable); - assertEq(auction.kickable(id), kickable); - (, , _kicked, _available) = auction.auctionInfo(id); + assertEq(swapper.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); + uint256 available = swapper.kickAuction(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); + (_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 +347,14 @@ 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); - - // Can't kick a new one yet - vm.expectRevert("too soon"); - auction.kick(id); - - assertEq(auction.kickable(id), 0); + assertEq(auction.price(from), 0); + assertEq(auction.getAmountNeeded(from, kickable), 0); } function test_takeAuction_custom(uint256 _amount, uint16 _percent) public { @@ -437,7 +366,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 +374,19 @@ contract AuctionSwapperTest is Setup { swapper.setUseDefault(false); - assertEq(auction.kickable(id), 0); + assertEq(swapper.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)); + swapper.kickAuction(from); + + (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 +394,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); @@ -479,79 +403,17 @@ 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(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, kickable); + 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..8e0fafe 100644 --- a/src/test/BaseAuctioneer.t.sol +++ b/src/test/BaseAuctioneer.t.sol @@ -14,27 +14,11 @@ 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 AuctionDisabled( - bytes32 auctionId, - address indexed from, - address indexed to, - address indexed strategy - ); - - event AuctionKicked(bytes32 auctionId, uint256 available); - - event AuctionTaken( - bytes32 auctionId, - uint256 amountTaken, - uint256 amountLeft - ); + event AuctionEnabled(address indexed from, address indexed to); + + event AuctionDisabled(address indexed from, address indexed to); + + event AuctionKicked(address indexed from, uint256 available); IMockAuctioneer public auctioneer; @@ -54,107 +38,88 @@ contract BaseAuctioneerTest is Setup { function test_enableAuction() public { address from = tokenAddrs["USDC"]; - bytes32 id = auctioneer.enableAuction(from); + auctioneer.enable(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); // Kicking it reverts vm.expectRevert("nothing to kick"); - auctioneer.kick(id); + auctioneer.kick(from); // Can't re-enable vm.expectRevert("already enabled"); - auctioneer.enableAuction(from); + auctioneer.enable(from); } function test_enableSecondAuction() public { address from = tokenAddrs["USDC"]; - bytes32 id = auctioneer.enableAuction(from); + auctioneer.enable(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.enable(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, 1); + assertEq(_initialAvailable, 0); + assertEq(auctioneer.available(secondFrom), 0); } function test_disableAuction() public { address from = tokenAddrs["USDC"]; - bytes32 id = auctioneer.enableAuction(from); + auctioneer.enable(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)); - auctioneer.disableAuction(from); + emit AuctionDisabled(from, address(asset)); + auctioneer.disable(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 +130,52 @@ contract BaseAuctioneerTest is Setup { fromScaler = WAD / 10 ** ERC20(from).decimals(); wantScaler = WAD / 10 ** ERC20(asset).decimals(); - bytes32 id = auctioneer.enableAuction(from); + auctioneer.enable(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, 1e10); + 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, 0); 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 +188,14 @@ 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); - - // Can't kick a new one yet - vm.expectRevert("too soon"); - auctioneer.kick(id); - - assertEq(auctioneer.kickable(id), 0); + assertEq(auctioneer.price(from), 0); + assertEq(auctioneer.getAmountNeeded(from, _amount), 0); } function test_takeAuction_default(uint256 _amount, uint16 _percent) public { @@ -252,185 +207,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.enable(from); airdrop(ERC20(from), address(auctioneer), _amount); - auctioneer.setUseDefault(false); + auctioneer.kick(from); - assertEq(auctioneer.kickable(id), 0); - - uint256 kickable = _amount / 10; - auctioneer.setLetKick(kickable); - - 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, 1e10); + 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); @@ -439,16 +234,13 @@ 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); + 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/mocks/MockAuctionSwapper.sol b/src/test/mocks/MockAuctionSwapper.sol index 0bc279d..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 { @@ -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,33 +41,11 @@ 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) external 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); + return Auction(auction).kick(_token); } function setUseDefault(bool _useDefault) external { @@ -82,20 +55,13 @@ 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"; 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; @@ -107,5 +73,7 @@ interface IMockAuctionSwapper is IStrategy, IAuctionSwapper { function setLetKick(uint256 _letKick) external; - function setShouldRevert(bool _shouldRevert) external; + function kickAuction(address _token) external returns (uint256); + + function kickable(address _token) external view 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 {}