diff --git a/src/Auctions/DumperAuction.sol b/src/Auctions/DumperAuction.sol index 09894f9..a61f5f3 100644 --- a/src/Auctions/DumperAuction.sol +++ b/src/Auctions/DumperAuction.sol @@ -6,6 +6,11 @@ import {ERC20} from "@openzeppelin/contracts/token/ERC20/ERC20.sol"; import {SafeERC20} from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol"; import {ReentrancyGuard} from "@openzeppelin/contracts/security/ReentrancyGuard.sol"; import {ITaker} from "../interfaces/ITaker.sol"; +import {GPv2Order} from "../libraries/GPv2Order.sol"; + +interface ICowSettlement { + function domainSeparator() external view returns (bytes32); +} /** * @title Auction @@ -13,6 +18,7 @@ import {ITaker} from "../interfaces/ITaker.sol"; * @notice General use dutch auction contract for token sales. */ contract DumperAuction is Governance2Step, ReentrancyGuard { + using GPv2Order for GPv2Order.Data; using SafeERC20 for ERC20; /// @notice Emitted when a new auction is enabled @@ -32,26 +38,10 @@ contract DumperAuction is Governance2Step, ReentrancyGuard { /// @notice Store all the auction specific information. struct AuctionInfo { - uint96 kicked; - address receiver; - uint64 scaler; - uint192 initialAvailable; - uint256 currentAvailable; - } - - struct GPv2OrderData { - ERC20 sellToken; - ERC20 buyToken; - address receiver; - uint256 sellAmount; - uint256 buyAmount; - uint32 validTo; - bytes32 appData; - uint256 feeAmount; - bytes32 kind; - bool partiallyFillable; - bytes32 sellTokenBalance; - bytes32 buyTokenBalance; + uint128 kicked; + uint128 scaler; + uint128 initialAvailable; + uint128 currentAvailable; } uint256 internal constant WAD = 1e18; @@ -62,9 +52,14 @@ contract DumperAuction is Governance2Step, ReentrancyGuard { address internal constant VAULT_RELAYER = 0xC92E8bdf79f0507f65a392b0ab4667716BFE0110; + bytes32 internal immutable COW_DOMAIN_SEPARATOR; + /// @notice Struct to hold the info for `want`. TokenInfo internal wantInfo; + /// @notice The address that will receive the funds in the auction. + address public receiver; + /// @notice The amount to start the auction at. uint256 public startingPrice; @@ -77,17 +72,21 @@ contract DumperAuction is Governance2Step, ReentrancyGuard { /// @notice Array of all the enabled auction for this contract. address[] public enabledAuctions; - constructor() Governance2Step(msg.sender) {} + constructor() Governance2Step(msg.sender) { + COW_DOMAIN_SEPARATOR = ICowSettlement(COW_SETTLEMENT).domainSeparator(); + } /** * @notice Initializes the Auction contract with initial parameters. * @param _want Address this auction is selling to. + * @param _receiver Address that will receive the funds from the auction. * @param _governance Address of the contract governance. * @param _auctionLength Duration of each auction in seconds. * @param _startingPrice Starting price for each auction. */ function initialize( address _want, + address _receiver, address _governance, uint256 _auctionLength, uint256 _startingPrice @@ -96,7 +95,7 @@ contract DumperAuction is Governance2Step, ReentrancyGuard { require(_want != address(0), "ZERO ADDRESS"); require(_auctionLength != 0, "length"); require(_startingPrice != 0, "starting price"); - + require(_receiver != address(0), "receiver"); // Cannot have more than 18 decimals. uint256 decimals = ERC20(_want).decimals(); require(decimals <= 18, "unsupported decimals"); @@ -107,6 +106,7 @@ contract DumperAuction is Governance2Step, ReentrancyGuard { scaler: uint96(WAD / 10 ** decimals) }); + receiver = _receiver; governance = _governance; auctionLength = _auctionLength; startingPrice = _startingPrice; @@ -270,39 +270,21 @@ contract DumperAuction is Governance2Step, ReentrancyGuard { SETTERS //////////////////////////////////////////////////////////////*/ - /** - * @notice Enables a new auction. - * @dev Uses governance as the receiver. - * @param _from The address of the token to be auctioned. - */ - function enable(address _from) external virtual { - return enable(_from, msg.sender); - } - /** * @notice Enables a new auction. * @param _from The address of the token to be auctioned. - * @param _receiver The address that will receive the funds in the auction. */ - function enable( - address _from, - address _receiver - ) public virtual onlyGovernance { + function enable(address _from) external virtual onlyGovernance { address _want = want(); require(_from != address(0) && _from != _want, "ZERO ADDRESS"); - require( - _receiver != address(0) && _receiver != address(this), - "receiver" - ); + require(auctions[_from].scaler == 0, "already enabled"); + // Cannot have more than 18 decimals. uint256 decimals = ERC20(_from).decimals(); require(decimals <= 18, "unsupported decimals"); - require(auctions[_from].receiver == address(0), "already enabled"); - // Store all needed info. - auctions[_from].scaler = uint64(WAD / 10 ** decimals); - auctions[_from].receiver = _receiver; + auctions[_from].scaler = uint128(WAD / 10 ** decimals); ERC20(_from).safeApprove(VAULT_RELAYER, type(uint256).max); @@ -332,7 +314,7 @@ contract DumperAuction is Governance2Step, ReentrancyGuard { uint256 _index ) public virtual onlyGovernance { // Make sure the auction was enabled. - require(auctions[_from].receiver != address(0), "not enabled"); + require(auctions[_from].scaler != 0, "not enabled"); // Remove the struct. delete auctions[_from]; @@ -373,9 +355,23 @@ contract DumperAuction is Governance2Step, ReentrancyGuard { function setStartingPrice( uint256 _startingPrice ) external virtual onlyGovernance { + require(_startingPrice != 0, "starting price"); startingPrice = _startingPrice; } + /** + * @notice Sets the auction length. + * @param _auctionLength The new auction length. + */ + function setAuctionLength( + uint256 _auctionLength + ) external virtual onlyGovernance { + require(_auctionLength != 0, "length"); + require(_auctionLength < 1 weeks, "too long"); + + auctionLength = _auctionLength; + } + /*////////////////////////////////////////////////////////////// PARTICIPATE IN AUCTION //////////////////////////////////////////////////////////////*/ @@ -388,7 +384,7 @@ contract DumperAuction is Governance2Step, ReentrancyGuard { function kick( address _from ) external virtual nonReentrant returns (uint256 _available) { - require(auctions[_from].receiver != address(0), "not enabled"); + require(auctions[_from].scaler != 0, "not enabled"); require( block.timestamp > auctions[_from].kicked + auctionLength, "too soon" @@ -400,9 +396,9 @@ contract DumperAuction is Governance2Step, ReentrancyGuard { require(_available != 0, "nothing to kick"); // Update the auctions status. - auctions[_from].kicked = uint96(block.timestamp); - auctions[_from].initialAvailable = uint192(_available); - auctions[_from].currentAvailable = _available; + auctions[_from].kicked = uint128(block.timestamp); + auctions[_from].initialAvailable = uint128(_available); + auctions[_from].currentAvailable = uint128(_available); emit AuctionKicked(_from, _available); } @@ -419,7 +415,7 @@ contract DumperAuction is Governance2Step, ReentrancyGuard { /** * @notice Take the token being sold in a live auction with a specified maximum amount. - * @dev Uses the sender's address as the receiver. + * @dev Will send the funds to the msg sender. * @param _from The address of the token to be auctioned. * @param _maxAmount The maximum amount of fromToken to take in the auction. * @return . The amount of fromToken taken in the auction. @@ -517,31 +513,34 @@ contract DumperAuction is Governance2Step, ReentrancyGuard { address _want = want(); // Pull `want`. - ERC20(_want).safeTransferFrom(msg.sender, auction.receiver, needed); + ERC20(_want).safeTransferFrom(msg.sender, receiver, needed); } + /// @dev Validates a COW order signature. function isValidSignature( bytes32 _hash, bytes calldata signature ) external view returns (bytes4) { require(!_reentrancyGuardEntered(), "ReentrancyGuard: reentrant call"); - // Decode the signature into GPv2Order_Data - GPv2OrderData memory order = abi.decode(signature, (GPv2OrderData)); + // Decode the signature to get the order. + GPv2Order.Data memory order = abi.decode(signature, (GPv2Order.Data)); AuctionInfo memory auction = auctions[address(order.sellToken)]; - // Verify the auction is valid + + // Get the current amount needed for the auction. uint256 paymentAmount = _getAmountNeeded( auction, order.sellAmount, block.timestamp ); - // Verify order details + // Verify the order details. + require(_hash == order.hash(COW_DOMAIN_SEPARATOR), "bad order"); require(paymentAmount > 0, "zero amount"); require(order.buyAmount >= paymentAmount, "bad price"); require(address(order.buyToken) == want(), "bad token"); - require(order.receiver == auction.receiver, "bad receiver"); + require(order.receiver == receiver, "bad receiver"); require(order.sellAmount == auction.currentAvailable, "bad amount"); require( order.sellToken.balanceOf(address(this)) >= @@ -550,6 +549,6 @@ contract DumperAuction is Governance2Step, ReentrancyGuard { ); // If all checks pass, return the magic value - return DumperAuction.isValidSignature.selector; + return this.isValidSignature.selector; } } diff --git a/src/Auctions/DumperAuctionFactory.sol b/src/Auctions/DumperAuctionFactory.sol new file mode 100644 index 0000000..5cc71c5 --- /dev/null +++ b/src/Auctions/DumperAuctionFactory.sol @@ -0,0 +1,173 @@ +// SPDX-License-Identifier: AGPL-3.0 +pragma solidity >=0.8.18; + +import {Auction} from "./Auction.sol"; +import {Clonable} from "../utils/Clonable.sol"; + +/// @title AuctionFactory +/// @notice Deploy a new Auction. +contract AuctionFactory is Clonable { + event DeployedNewAuction(address indexed auction, address indexed want); + + /// @notice The time that each auction lasts. + uint256 public constant DEFAULT_AUCTION_LENGTH = 1 days; + + /// @notice The amount to start the auction with. + uint256 public constant DEFAULT_STARTING_PRICE = 1_000_000; + + /// @notice Full array of all auctions deployed through this factory. + address[] public auctions; + + constructor() { + // Deploy the original + original = address(new Auction()); + } + + /** + * @notice Creates a new auction contract. + * @param _want Address of the token users will bid with. + * @return _newAuction Address of the newly created auction contract. + */ + function createNewAuction(address _want) external returns (address) { + return + _createNewAuction( + _want, + msg.sender, + msg.sender, + DEFAULT_AUCTION_LENGTH, + DEFAULT_STARTING_PRICE + ); + } + + /** + * @notice Creates a new auction contract. + * @param _want Address of the token users will bid with. + * @param _receiver Address that will receive the funds in the auction. + * @return _newAuction Address of the newly created auction contract. + */ + function createNewAuction( + address _want, + address _receiver + ) external returns (address) { + return + _createNewAuction( + _want, + _receiver, + msg.sender, + DEFAULT_AUCTION_LENGTH, + DEFAULT_STARTING_PRICE + ); + } + + /** + * @notice Creates a new auction contract. + * @param _want Address of the token users will bid with. + * @param _receiver Address that will receive the funds in the auction. + * @param _governance Address allowed to enable and disable auctions. + * @return _newAuction Address of the newly created auction contract. + */ + function createNewAuction( + address _want, + address _receiver, + address _governance + ) external returns (address) { + return + _createNewAuction( + _want, + _receiver, + _governance, + DEFAULT_AUCTION_LENGTH, + DEFAULT_STARTING_PRICE + ); + } + + /** + * @notice Creates a new auction contract. + * @param _want Address of the token users will bid with. + * @param _receiver Address that will receive the funds in the auction. + * @param _governance Address allowed to enable and disable auctions. + * @param _auctionLength Length of the auction in seconds. + * @return _newAuction Address of the newly created auction contract. + */ + function createNewAuction( + address _want, + address _receiver, + address _governance, + uint256 _auctionLength + ) external returns (address) { + return + _createNewAuction( + _want, + _receiver, + _governance, + _auctionLength, + DEFAULT_STARTING_PRICE + ); + } + + /** + * @notice Creates a new auction contract. + * @param _want Address of the token users will bid with. + * @param _receiver Address that will receive the funds in the auction. + * @param _governance Address allowed to enable and disable auctions. + * @param _auctionLength Length of the auction in seconds. + * @param _startingPrice Starting price for the auction (no decimals). + * NOTE: The starting price should be without decimals (1k == 1_000). + * @return _newAuction Address of the newly created auction contract. + */ + function createNewAuction( + address _want, + address _receiver, + address _governance, + uint256 _auctionLength, + uint256 _startingPrice + ) external returns (address) { + return + _createNewAuction( + _want, + _receiver, + _governance, + _auctionLength, + _startingPrice + ); + } + + /** + * @dev Deploys and initializes a new Auction + */ + function _createNewAuction( + address _want, + address _receiver, + address _governance, + uint256 _auctionLength, + uint256 _startingPrice + ) internal returns (address _newAuction) { + _newAuction = _clone(); + + Auction(_newAuction).initialize( + _want, + _receiver, + _governance, + _auctionLength, + _startingPrice + ); + + auctions.push(_newAuction); + + emit DeployedNewAuction(_newAuction, _want); + } + + /** + * @notice Get the full list of auctions deployed through this factory. + */ + function getAllAuctions() external view returns (address[] memory) { + return auctions; + } + + /** + * @notice Get the total number of auctions deployed through this factory. + */ + function numberOfAuctions() external view returns (uint256) { + return auctions.length; + } +} diff --git a/src/libraries/GPv2Order.sol b/src/libraries/GPv2Order.sol new file mode 100644 index 0000000..f3c2ef8 --- /dev/null +++ b/src/libraries/GPv2Order.sol @@ -0,0 +1,243 @@ +// SPDX-License-Identifier: LGPL-3.0-or-later +pragma solidity ^0.8.0; + +import {ERC20} from "@openzeppelin/contracts/token/ERC20/ERC20.sol"; + +/// @title Gnosis Protocol v2 Order Library +/// @author Gnosis Developers +library GPv2Order { + /// @dev The complete data for a Gnosis Protocol order. This struct contains + /// all order parameters that are signed for submitting to GP. + struct Data { + ERC20 sellToken; + ERC20 buyToken; + address receiver; + uint256 sellAmount; + uint256 buyAmount; + uint32 validTo; + bytes32 appData; + uint256 feeAmount; + bytes32 kind; + bool partiallyFillable; + bytes32 sellTokenBalance; + bytes32 buyTokenBalance; + } + + /// @dev The order EIP-712 type hash for the [`GPv2Order.Data`] struct. + /// + /// This value is pre-computed from the following expression: + /// ``` + /// keccak256( + /// "Order(" + + /// "address sellToken," + + /// "address buyToken," + + /// "address receiver," + + /// "uint256 sellAmount," + + /// "uint256 buyAmount," + + /// "uint32 validTo," + + /// "bytes32 appData," + + /// "uint256 feeAmount," + + /// "string kind," + + /// "bool partiallyFillable" + + /// "string sellTokenBalance" + + /// "string buyTokenBalance" + + /// ")" + /// ) + /// ``` + bytes32 internal constant TYPE_HASH = + hex"d5a25ba2e97094ad7d83dc28a6572da797d6b3e7fc6663bd93efb789fc17e489"; + + /// @dev The marker value for a sell order for computing the order struct + /// hash. This allows the EIP-712 compatible wallets to display a + /// descriptive string for the order kind (instead of 0 or 1). + /// + /// This value is pre-computed from the following expression: + /// ``` + /// keccak256("sell") + /// ``` + bytes32 internal constant KIND_SELL = + hex"f3b277728b3fee749481eb3e0b3b48980dbbab78658fc419025cb16eee346775"; + + /// @dev The OrderKind marker value for a buy order for computing the order + /// struct hash. + /// + /// This value is pre-computed from the following expression: + /// ``` + /// keccak256("buy") + /// ``` + bytes32 internal constant KIND_BUY = + hex"6ed88e868af0a1983e3886d5f3e95a2fafbd6c3450bc229e27342283dc429ccc"; + + /// @dev The TokenBalance marker value for using direct ERC20 balances for + /// computing the order struct hash. + /// + /// This value is pre-computed from the following expression: + /// ``` + /// keccak256("erc20") + /// ``` + bytes32 internal constant BALANCE_ERC20 = + hex"5a28e9363bb942b639270062aa6bb295f434bcdfc42c97267bf003f272060dc9"; + + /// @dev The TokenBalance marker value for using Balancer Vault external + /// balances (in order to re-use Vault ERC20 approvals) for computing the + /// order struct hash. + /// + /// This value is pre-computed from the following expression: + /// ``` + /// keccak256("external") + /// ``` + bytes32 internal constant BALANCE_EXTERNAL = + hex"abee3b73373acd583a130924aad6dc38cfdc44ba0555ba94ce2ff63980ea0632"; + + /// @dev The TokenBalance marker value for using Balancer Vault internal + /// balances for computing the order struct hash. + /// + /// This value is pre-computed from the following expression: + /// ``` + /// keccak256("internal") + /// ``` + bytes32 internal constant BALANCE_INTERNAL = + hex"4ac99ace14ee0a5ef932dc609df0943ab7ac16b7583634612f8dc35a4289a6ce"; + + /// @dev Marker address used to indicate that the receiver of the trade + /// proceeds should the owner of the order. + /// + /// This is chosen to be `address(0)` for gas efficiency as it is expected + /// to be the most common case. + address internal constant RECEIVER_SAME_AS_OWNER = address(0); + + /// @dev The byte length of an order unique identifier. + uint256 internal constant UID_LENGTH = 56; + + /// @dev Returns the actual receiver for an order. This function checks + /// whether or not the [`receiver`] field uses the marker value to indicate + /// it is the same as the order owner. + /// + /// @return receiver The actual receiver of trade proceeds. + function actualReceiver( + Data memory order, + address owner + ) internal pure returns (address receiver) { + if (order.receiver == RECEIVER_SAME_AS_OWNER) { + receiver = owner; + } else { + receiver = order.receiver; + } + } + + /// @dev Return the EIP-712 signing hash for the specified order. + /// + /// @param order The order to compute the EIP-712 signing hash for. + /// @param domainSeparator The EIP-712 domain separator to use. + /// @return orderDigest The 32 byte EIP-712 struct hash. + function hash( + Data memory order, + bytes32 domainSeparator + ) internal pure returns (bytes32 orderDigest) { + bytes32 structHash; + + // NOTE: Compute the EIP-712 order struct hash in place. As suggested + // in the EIP proposal, noting that the order struct has 10 fields, and + // including the type hash `(12 + 1) * 32 = 416` bytes to hash. + // + // solhint-disable-next-line no-inline-assembly + assembly { + let dataStart := sub(order, 32) + let temp := mload(dataStart) + mstore(dataStart, TYPE_HASH) + structHash := keccak256(dataStart, 416) + mstore(dataStart, temp) + } + + // NOTE: Now that we have the struct hash, compute the EIP-712 signing + // hash using scratch memory past the free memory pointer. The signing + // hash is computed from `"\x19\x01" || domainSeparator || structHash`. + // + // + // solhint-disable-next-line no-inline-assembly + assembly { + let freeMemoryPointer := mload(0x40) + mstore(freeMemoryPointer, "\x19\x01") + mstore(add(freeMemoryPointer, 2), domainSeparator) + mstore(add(freeMemoryPointer, 34), structHash) + orderDigest := keccak256(freeMemoryPointer, 66) + } + } + + /// @dev Packs order UID parameters into the specified memory location. The + /// result is equivalent to `abi.encodePacked(...)` with the difference that + /// it allows re-using the memory for packing the order UID. + /// + /// This function reverts if the order UID buffer is not the correct size. + /// + /// @param orderUid The buffer pack the order UID parameters into. + /// @param orderDigest The EIP-712 struct digest derived from the order + /// parameters. + /// @param owner The address of the user who owns this order. + /// @param validTo The epoch time at which the order will stop being valid. + function packOrderUidParams( + bytes memory orderUid, + bytes32 orderDigest, + address owner, + uint32 validTo + ) internal pure { + require(orderUid.length == UID_LENGTH, "GPv2: uid buffer overflow"); + + // NOTE: Write the order UID to the allocated memory buffer. The order + // parameters are written to memory in **reverse order** as memory + // operations write 32-bytes at a time and we want to use a packed + // encoding. This means, for example, that after writing the value of + // `owner` to bytes `20:52`, writing the `orderDigest` to bytes `0:32` + // will **overwrite** bytes `20:32`. This is desirable as addresses are + // only 20 bytes and `20:32` should be `0`s: + // + // | 1111111111222222222233333333334444444444555555 + // byte | 01234567890123456789012345678901234567890123456789012345 + // -------+--------------------------------------------------------- + // field | [.........orderDigest..........][......owner.......][vT] + // -------+--------------------------------------------------------- + // mstore | [000000000000000000000000000.vT] + // | [00000000000.......owner.......] + // | [.........orderDigest..........] + // + // Additionally, since Solidity `bytes memory` are length prefixed, + // 32 needs to be added to all the offsets. + // + // solhint-disable-next-line no-inline-assembly + assembly { + mstore(add(orderUid, 56), validTo) + mstore(add(orderUid, 52), owner) + mstore(add(orderUid, 32), orderDigest) + } + } + + /// @dev Extracts specific order information from the standardized unique + /// order id of the protocol. + /// + /// @param orderUid The unique identifier used to represent an order in + /// the protocol. This uid is the packed concatenation of the order digest, + /// the validTo order parameter and the address of the user who created the + /// order. It is used by the user to interface with the contract directly, + /// and not by calls that are triggered by the solvers. + /// @return orderDigest The EIP-712 signing digest derived from the order + /// parameters. + /// @return owner The address of the user who owns this order. + /// @return validTo The epoch time at which the order will stop being valid. + function extractOrderUidParams( + bytes calldata orderUid + ) + internal + pure + returns (bytes32 orderDigest, address owner, uint32 validTo) + { + require(orderUid.length == UID_LENGTH, "GPv2: invalid uid"); + + // Use assembly to efficiently decode packed calldata. + // solhint-disable-next-line no-inline-assembly + assembly { + orderDigest := calldataload(orderUid.offset) + owner := shr(96, calldataload(add(orderUid.offset, 32))) + validTo := shr(224, calldataload(add(orderUid.offset, 52))) + } + } +}