-
Notifications
You must be signed in to change notification settings - Fork 0
USDC/KWENTA Auctions #3
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
debfcce
bfedeb6
525cfef
4c2b6a7
f860c1e
546bb38
5a5670b
a1ab973
9b59063
4a45478
d9e9fe0
104330e
b25bcc5
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,278 @@ | ||
| // SPDX-License-Identifier: MIT | ||
| pragma solidity 0.8.25; | ||
|
|
||
| import {IERC20} from "@openzeppelin/contracts/token/ERC20/ERC20.sol"; | ||
| import "@openzeppelin/contracts/access/Ownable.sol"; | ||
| import "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol"; | ||
|
|
||
| /// @title USDC-KWENTA Auction Contract | ||
| /// @author Flocqst (florian@kwenta.io) | ||
| contract Auction is Ownable, Initializable { | ||
| /*////////////////////////////////////////////////////////////// | ||
| EVENTS | ||
| //////////////////////////////////////////////////////////////*/ | ||
|
|
||
| /// @notice Emitted when the auction starts | ||
| event Start(); | ||
|
|
||
| /// @notice Emitted when a bid is placed | ||
| /// @param sender The address of the bidder | ||
| /// @param amount The amount of the bid | ||
| event Bid(address indexed sender, uint256 amount); | ||
|
|
||
| /// @notice Emitted when a bidder withdraws their non-winning bids | ||
| /// @param bidder The address of the bidder | ||
| /// @param amount The amount of funds withdrawn | ||
| event Withdraw(address indexed bidder, uint256 amount); | ||
|
|
||
| /// @notice Emitted when the auction ends | ||
| /// @param winner The address of the winner | ||
| /// @param amount The amount of the winning bid | ||
| event End(address winner, uint256 amount); | ||
|
|
||
| /// @notice Emitted when the bid increment is updated | ||
| /// @param newBidIncrement The new bid increment value | ||
| event BidBufferUpdated(uint256 newBidIncrement); | ||
|
|
||
| /// @notice Emitted when bidding is frozen | ||
| event BiddingFrozen(); | ||
|
|
||
| /// @notice Emitted when bidding is resumed | ||
| event BiddingResumed(); | ||
|
|
||
| /// @notice Emitted when funds are withdrawn by the owner | ||
| /// @param owner The address of the owner | ||
| /// @param usdcAmount The amount of USDC withdrawn | ||
| /// @param kwentaAmount The amount of KWENTA withdrawn | ||
| event FundsWithdrawn( | ||
| address indexed owner, uint256 usdcAmount, uint256 kwentaAmount | ||
| ); | ||
|
|
||
| /*////////////////////////////////////////////////////////////// | ||
| ERRORS | ||
| //////////////////////////////////////////////////////////////*/ | ||
|
|
||
| /// @notice Thrown when trying to start the auction when it is already started | ||
| error AuctionAlreadyStarted(); | ||
|
|
||
| /// @notice Thrown when trying to bid or settle on an auction that has not started yet | ||
| error AuctionNotStarted(); | ||
|
|
||
| /// @notice Thrown when trying to bid on an auction that has already ended | ||
| error AuctionAlreadyEnded(); | ||
|
|
||
| /// @notice Throw when the bid amount is too low to be accepted | ||
| /// @param highestBidPlusBuffer The required minimum bid amount | ||
| error BidTooLow(uint256 highestBidPlusBuffer); | ||
|
|
||
| /// @notice Thrown when trying to settle an auction that has not ended yet | ||
| error AuctionNotEnded(); | ||
|
|
||
| /// @notice Thrown when trying to settle an auction that has already been settled | ||
| error AuctionAlreadySettled(); | ||
|
|
||
| /// @notice Thrown when trying to froze bidding when it is already frozen | ||
| error BiddingFrozenErr(); | ||
|
|
||
| /*////////////////////////////////////////////////////////////// | ||
| STATE VARIABLES | ||
| //////////////////////////////////////////////////////////////*/ | ||
|
|
||
| /// @notice Contract for USDC ERC20 token | ||
| IERC20 public usdc; | ||
|
|
||
| /// @notice Contract for KWENTA ERC20 token | ||
| IERC20 public kwenta; | ||
|
|
||
| /// @notice The amount of USDC to be auctioned | ||
| uint256 public auctionAmount; | ||
|
|
||
| /// @notice The starting bid amount | ||
| uint256 public startingBid; | ||
|
|
||
| /// @notice The minimum amount that a bid must be above the current highest bid | ||
| uint256 public bidBuffer; | ||
|
|
||
| /// @notice The timestamp at which the auction ends | ||
| uint256 public endAt; | ||
|
|
||
| /// @notice Indicates if the auction has started. | ||
| bool public started; | ||
|
|
||
| /// @notice Indicates if the auction has been settled. | ||
| bool public settled; | ||
|
|
||
| /// @notice Indicates if bidding is frozen | ||
| bool public frozen; | ||
|
|
||
| /// @notice The address of the highest bidder | ||
| address public highestBidder; | ||
|
|
||
| /// @notice The amount of the highest bid | ||
| uint256 public highestBid; | ||
|
|
||
| /// @notice Mapping of bidders to their bids | ||
| mapping(address => uint256) public bids; | ||
|
|
||
| /*/////////////////////////////////////////////////////////////// | ||
| CONSTRUCTOR / INITIALIZER | ||
| ///////////////////////////////////////////////////////////////*/ | ||
|
|
||
| /// @dev Actual contract construction will take place in the initialize function via proxy | ||
| /// @param initialOwner The address of the owner of this contract | ||
| /// @param _usdc The address for the USDC ERC20 token | ||
| /// @param _kwenta The address for the KWENTA ERC20 token | ||
| /// @param _startingBid The starting bid amount | ||
| /// @param _bidBuffer The initial bid buffer amount | ||
| constructor( | ||
| address initialOwner, | ||
| address _usdc, | ||
| address _kwenta, | ||
| uint256 _startingBid, | ||
| uint256 _bidBuffer | ||
| ) Ownable(initialOwner) { | ||
| usdc = IERC20(_usdc); | ||
| kwenta = IERC20(_kwenta); | ||
|
|
||
| highestBid = _startingBid; | ||
| bidBuffer = _bidBuffer; | ||
| } | ||
|
|
||
| /// @notice Initializes the auction contract | ||
| /// @param initialOwner The address of the owner of this contract | ||
| /// @param _usdc The address for the USDC ERC20 token | ||
| /// @param _kwenta The address for the KWENTA ERC20 token | ||
| /// @param _startingBid The starting bid amount | ||
| /// @param _bidBuffer The initial bid buffer amount | ||
| function initialize( | ||
| address initialOwner, | ||
| address _usdc, | ||
| address _kwenta, | ||
| uint256 _startingBid, | ||
| uint256 _bidBuffer | ||
| ) public initializer { | ||
| _transferOwnership(initialOwner); | ||
|
|
||
| usdc = IERC20(_usdc); | ||
| kwenta = IERC20(_kwenta); | ||
|
|
||
| highestBid = _startingBid; | ||
| bidBuffer = _bidBuffer; | ||
| } | ||
|
|
||
| /*/////////////////////////////////////////////////////////////// | ||
| AUCTION OPERATIONS | ||
| ///////////////////////////////////////////////////////////////*/ | ||
|
|
||
| /// @notice Starts the auction | ||
| /// @param _auctionAmount The amount of USDC to be auctioned | ||
| /// @dev Can only be called by the owner once | ||
| function start(uint256 _auctionAmount) external onlyOwner { | ||
cmontecoding marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| if (started) revert AuctionAlreadyStarted(); | ||
|
|
||
| usdc.transferFrom(msg.sender, address(this), _auctionAmount); | ||
| auctionAmount = _auctionAmount; | ||
|
|
||
| started = true; | ||
| endAt = block.timestamp + 1 days; | ||
|
|
||
| emit Start(); | ||
| } | ||
|
|
||
| /// @notice Places a bid in the auction. | ||
| /// @param amount The amount of KWENTA to bid. | ||
| /// @dev The auction must be started, not ended, and the bid must be higher than the current highest bid plus buffer | ||
| function bid(uint256 amount) external isFrozen { | ||
| if (!started) revert AuctionNotStarted(); | ||
| if (block.timestamp >= endAt) revert AuctionAlreadyEnded(); | ||
| if (amount < highestBid + bidBuffer) { | ||
| revert BidTooLow(highestBid + bidBuffer); | ||
| } | ||
|
|
||
| kwenta.transferFrom(msg.sender, address(this), amount); | ||
|
|
||
| if (highestBidder != address(0)) { | ||
| bids[highestBidder] += highestBid; | ||
| } | ||
|
|
||
| highestBidder = msg.sender; | ||
| highestBid = amount; | ||
|
|
||
| // Extend the auction if it is ending in less than an hour | ||
| if (endAt - block.timestamp < 1 hours) { | ||
| endAt = block.timestamp + 1 hours; | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I thought we were making auction time countdown length customizable? Or are you implicitly assuming we will change via upgradeability (new version) in the future (which is fine). Even so, this should probably be a constant for DRY purposes. Not good to hardcode the same value in multiple places.
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yes, if we want to change auction time in the future (currently one day) then yes it will be done through a new version of the |
||
| } | ||
|
|
||
| emit Bid(msg.sender, amount); | ||
| } | ||
|
|
||
| /// @notice Withdraws the callers non-winning bids | ||
| function withdraw() external { | ||
| uint256 bal = bids[msg.sender]; | ||
| bids[msg.sender] = 0; | ||
|
|
||
| kwenta.transfer(msg.sender, bal); | ||
|
|
||
| emit Withdraw(msg.sender, bal); | ||
| } | ||
|
Comment on lines
+210
to
+217
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. What if you are a winning bid, auction ends, and then you withdraw before the auction is settled?
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. You can't withdraw winning bid, even when the auction ends. |
||
|
|
||
| /// @notice Settles the auction | ||
| function settleAuction() external { | ||
| if (!started) revert AuctionNotStarted(); | ||
| if (block.timestamp < endAt) revert AuctionNotEnded(); | ||
| if (settled) revert AuctionAlreadySettled(); | ||
|
|
||
| settled = true; | ||
|
|
||
| if (highestBidder != address(0)) { | ||
| usdc.transfer(highestBidder, auctionAmount); | ||
| kwenta.transfer(owner(), highestBid); | ||
| } else { | ||
| usdc.transfer(owner(), auctionAmount); | ||
| } | ||
|
|
||
| emit End(highestBidder, highestBid); | ||
| } | ||
|
|
||
| /// @notice Updates the minimum bid increment | ||
| /// @param _bidBuffer The new bid buffer value | ||
| function setBidIncrement(uint256 _bidBuffer) external onlyOwner { | ||
| bidBuffer = _bidBuffer; | ||
| emit BidBufferUpdated(_bidBuffer); | ||
| } | ||
|
|
||
| /// @notice Modifier to ensure that bidding is not frozen | ||
| modifier isFrozen() { | ||
| if (frozen) revert BiddingFrozenErr(); | ||
| _; | ||
| } | ||
|
|
||
| /// @notice Freeze bidding, preventing any new bids | ||
| function freezeBidding() external onlyOwner { | ||
| frozen = true; | ||
| emit BiddingFrozen(); | ||
| } | ||
|
|
||
| /// @notice Resume bidding, allowing new bids to be placed | ||
| function resumeBidding() external onlyOwner { | ||
| frozen = false; | ||
| emit BiddingResumed(); | ||
| } | ||
|
|
||
| /// @notice Withdraws all funds from the contract | ||
| /// @dev Only callable by the owner. This is a safety feature only to be used in emergencies | ||
| function withdrawFunds() external onlyOwner { | ||
| uint256 usdcBalance = usdc.balanceOf(address(this)); | ||
| uint256 kwentaBalance = kwenta.balanceOf(address(this)); | ||
|
|
||
| if (usdcBalance > 0) { | ||
| usdc.transfer(owner(), usdcBalance); | ||
| } | ||
|
|
||
| if (kwentaBalance > 0) { | ||
| kwenta.transfer(owner(), kwentaBalance); | ||
| } | ||
|
|
||
| emit FundsWithdrawn(owner(), usdcBalance, kwentaBalance); | ||
| } | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,97 @@ | ||
| // SPDX-License-Identifier: UNLICENSED | ||
| pragma solidity 0.8.25; | ||
|
|
||
| import {Auction} from "./Auction.sol"; | ||
| import "@openzeppelin/contracts/proxy/Clones.sol"; | ||
|
|
||
| /// @title Auction Factory Contract for USDC-KWENTA Auctions | ||
| /// @author Flocqst (florian@kwenta.io) | ||
| contract AuctionFactory { | ||
| /// @notice Kwenta owned/operated multisig address that | ||
| /// can authorize upgrades | ||
| /// @dev making immutable because the pDAO address | ||
| /// will *never* change | ||
| address internal immutable pDAO; | ||
|
|
||
| /// @notice Address of the auction implementation contract | ||
| address public auctionImplementation; | ||
|
|
||
| /// @notice Array of all auctions created | ||
| address[] public auctions; | ||
|
|
||
| /// @notice Bid buffer amount used for all auctions | ||
| uint256 public bidBuffer; | ||
|
|
||
| /// @notice thrown when attempting to update | ||
| /// the bidBuffer when caller is not the Kwenta pDAO | ||
| error OnlyPDAO(); | ||
|
|
||
| /// @notice Emitted when a new auction is created | ||
| /// @param auctionContract The address of the newly created auction contract | ||
| /// @param owner The address of the account that created the auction | ||
| /// @param numAuctions The total number of auctions created | ||
| /// @param allAuctions Array of all auction contract addresses | ||
| event AuctionCreated( | ||
| address auctionContract, | ||
| address owner, | ||
| uint256 numAuctions, | ||
| address[] allAuctions | ||
| ); | ||
|
|
||
| /// @notice Emitted when the bid buffer is updated | ||
| /// @param _newBidBuffer The new bid buffer value | ||
| event BidBufferUpdated(uint256 _newBidBuffer); | ||
|
|
||
| /// @notice Modifier to restrict access to pDAO only | ||
| modifier onlyPDAO() { | ||
| if (msg.sender != pDAO) revert OnlyPDAO(); | ||
| _; | ||
| } | ||
|
|
||
| /// @notice Constructs the AuctionFactory with the address of the auction implementation contract | ||
| /// @param _pDAO Kwenta owned/operated multisig address | ||
| /// @param _auctionImplementation The address of the auction implementation contract | ||
| constructor(address _pDAO, address _auctionImplementation) { | ||
| pDAO = _pDAO; | ||
| auctionImplementation = _auctionImplementation; | ||
| } | ||
|
|
||
| /// @notice Creates a new auction by cloning the auction implementation contract | ||
| /// @param _owner The address of the DAO that owns the auction | ||
| /// @param _usdc The address for the USDC ERC20 token | ||
| /// @param _kwenta The address for the KWENTA ERC20 token | ||
| /// @param _startingBid The starting bid amount | ||
| /// @return newAuction The newly created auction contract | ||
| /// @dev The newly created auction contract is initialized and added to the auctions array and returned | ||
| function createAuction( | ||
| address _owner, | ||
| address _usdc, | ||
| address _kwenta, | ||
| uint256 _startingBid | ||
| ) external returns (Auction newAuction) { | ||
| address clone = Clones.clone(auctionImplementation); | ||
| Auction(clone).initialize( | ||
| _owner, _usdc, _kwenta, _startingBid, bidBuffer | ||
| ); | ||
| newAuction = | ||
| new Auction(_owner, _usdc, _kwenta, _startingBid, bidBuffer); | ||
| auctions.push(address(newAuction)); | ||
|
|
||
| emit AuctionCreated( | ||
| address(newAuction), msg.sender, auctions.length, auctions | ||
| ); | ||
| } | ||
|
|
||
| /// @notice Updates the bid buffer amount | ||
| /// @param _newBidBuffer The new bid buffer value to set | ||
| /// @dev Only callable by pDAO | ||
| function updateBidBuffer(uint256 _newBidBuffer) external onlyPDAO { | ||
| bidBuffer = _newBidBuffer; | ||
| emit BidBufferUpdated(_newBidBuffer); | ||
| } | ||
|
|
||
| /// @notice Returns the array of all auction contract addresses | ||
| function getAllAuctions() external view returns (address[] memory) { | ||
| return auctions; | ||
| } | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
contracts-upgradeable gitmodule is missing
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The contract exists in the repo that is included.
I change the import to:
"import "@openzeppelin/contracts/proxy/utils/Initializable.sol";"
and it worked fine
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@Flocqst resolved?