Skip to content
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

Add auction start functionality to vault #4

Open
wants to merge 43 commits into
base: feature/mint
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 40 commits
Commits
Show all changes
43 commits
Select commit Hold shift + click to select a range
debfcce
👷 Add auction contracts
Flocqst Jul 31, 2024
bfedeb6
✨ prettify
Flocqst Jul 31, 2024
525cfef
📚 Add NatSpec
Flocqst Aug 5, 2024
4c2b6a7
✅ Add tests for USDC-KWENTA auctions
Flocqst Aug 5, 2024
f860c1e
👷 Rename ended to settled
Flocqst Sep 11, 2024
546bb38
✅ Adjust to AuctionAlreadySettled
Flocqst Sep 11, 2024
5a5670b
👷 nit : Lock -> lock
Flocqst Sep 11, 2024
a1ab973
👷 change pDAO terminology to owner
Flocqst Sep 11, 2024
9b59063
👷 Rename lock-related terminology to frozen
Flocqst Sep 11, 2024
4a45478
✅ adjust to frozen terminology
Flocqst Sep 11, 2024
cd66738
👷 add start auction functions
cmontecoding Oct 17, 2024
db9628b
📚 auctionReady natspec
cmontecoding Oct 17, 2024
1e0be95
👷📚 rename offset to decimalOffset
cmontecoding Oct 17, 2024
0ea697e
✅ basic tess for createAuction and auctionReady
cmontecoding Oct 17, 2024
55873e5
Merge branch 'auction-usdc' into weekly-auction-call
cmontecoding Oct 24, 2024
3f355ff
👷 integrate Auction into KSXVault
cmontecoding Oct 24, 2024
e0f6925
👷 add setAuctionCooldown and make vault ownable
cmontecoding Oct 24, 2024
7c84fbf
📚 natspec
cmontecoding Oct 24, 2024
94bc947
👷 add initializer to KSXVault
cmontecoding Oct 25, 2024
c11885c
👷 auctionReady
cmontecoding Oct 25, 2024
d9e9fe0
👷 bidBuffer change
Flocqst Oct 25, 2024
104330e
👷 set pDAO in constructor
Flocqst Oct 25, 2024
4acab41
👷 overhaul isAuctionReady
cmontecoding Oct 25, 2024
dd36646
✨ fmt
cmontecoding Oct 25, 2024
7c2891e
👷 block.timestamp - lastAuctionStartTime) >= epochDuration
cmontecoding Oct 25, 2024
55b6342
✅ auctionReady tests
cmontecoding Oct 25, 2024
9774bf9
✅ 📚 isAuctionReady
cmontecoding Oct 25, 2024
b25bcc5
👷 return the auction in createAuction
cmontecoding Oct 25, 2024
c6cca60
Merge branch 'auction-usdc' into weekly-auction-call
cmontecoding Oct 25, 2024
16a0769
👷 remove buffer from vault and integrate auction return
cmontecoding Oct 25, 2024
7dde650
👷 remove transferFrom and add usdc.approve in createAuction
cmontecoding Oct 25, 2024
f30567c
👷 test_createAuction
cmontecoding Oct 25, 2024
08303a3
✨ fmt
cmontecoding Oct 25, 2024
ffd5d35
✅ test_setEpochDuration
cmontecoding Oct 26, 2024
871548c
👷 _updateLastAuctionStartTime
cmontecoding Oct 28, 2024
a281e34
✅ test_isAuctionReady
cmontecoding Oct 28, 2024
faeacdd
✅ fuzz start time
cmontecoding Oct 28, 2024
3e7ca1a
📚✅ use constants
cmontecoding Oct 28, 2024
8358721
✨ fmt
cmontecoding Oct 28, 2024
65d34a9
📚 author tag
cmontecoding Oct 29, 2024
8c94033
✅ test_isAuctionReady_next_week_buffer
cmontecoding Oct 29, 2024
2da28f3
👷 modifier checkAuction
cmontecoding Oct 29, 2024
ed0f2ec
✨ fmt
cmontecoding Oct 29, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 14 additions & 2 deletions script/Deploy.s.sol
Original file line number Diff line number Diff line change
Expand Up @@ -22,14 +22,26 @@ import {Script} from "lib/forge-std/src/Script.sol";
contract Setup is Script {

function deploySystem(
address owner,
address token,
address usdc,
address stakingRewards,
uint8 decimalOffset
address auctionFactory,
uint8 decimalOffset,
uint256 initialStart
)
public
returns (KSXVault ksxVault)
{
ksxVault = new KSXVault(token, stakingRewards, decimalOffset);
ksxVault = new KSXVault(
owner,
token,
usdc,
stakingRewards,
auctionFactory,
decimalOffset,
initialStart
);

// deploy ERC1967 proxy and set implementation to ksxVault
Proxy proxy = new Proxy(address(ksxVault), "");
Expand Down
292 changes: 292 additions & 0 deletions src/Auction.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,292 @@
// SPDX-License-Identifier: MIT
pragma solidity 0.8.25;

import "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol";
import "@openzeppelin/contracts/access/Ownable.sol";
import {IERC20} from "@openzeppelin/contracts/token/ERC20/ERC20.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);
}

}
Loading
Loading