Skip to content

Commit

Permalink
Merge branch 'auction-usdc' into weekly-auction-call
Browse files Browse the repository at this point in the history
  • Loading branch information
cmontecoding committed Oct 24, 2024
2 parents 0ea697e + 4a45478 commit 55873e5
Show file tree
Hide file tree
Showing 6 changed files with 827 additions and 5 deletions.
278 changes: 278 additions & 0 deletions src/Auction.sol
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 ([email protected])
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 {
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;
}

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);
}

/// @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);
}
}
65 changes: 65 additions & 0 deletions src/AuctionFactory.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
// 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 ([email protected])
contract AuctionFactory {
/// @notice Address of the auction implementation contract
address public auctionImplementation;

/// @notice Array of all auctions created
address[] public auctions;

/// @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 Constructs the AuctionFactory with the address of the auction implementation contract
/// @param _auctionImplementation The address of the auction implementation contract
constructor(address _auctionImplementation) {
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
/// @param _bidBuffer The initial bid buffer amount
/// @dev The newly created auction contract is initialized and added to the auctions array
function createAuction(
address _owner,
address _usdc,
address _kwenta,
uint256 _startingBid,
uint256 _bidBuffer
) external {
address clone = Clones.clone(auctionImplementation);
Auction(clone).initialize(
_owner, _usdc, _kwenta, _startingBid, _bidBuffer
);
Auction newAuction =
new Auction(_owner, _usdc, _kwenta, _startingBid, _bidBuffer);
auctions.push(address(newAuction));

emit AuctionCreated(
address(newAuction), msg.sender, auctions.length, auctions
);
}

/// @notice Returns the array of all auction contract addresses
function getAllAuctions() external view returns (address[] memory) {
return auctions;
}
}
Loading

0 comments on commit 55873e5

Please sign in to comment.