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

Auction Management #17

Merged
merged 44 commits into from
Jan 11, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
44 commits
Select commit Hold shift + click to select a range
f9b3f0f
feat: add module versioning
Oighty Jan 2, 2024
0652de9
forge install: prb-math
Oighty Jan 2, 2024
0a840d0
wip: cleaning up compile issues
Oighty Jan 3, 2024
bd64050
refactor: simplify module install
Oighty Jan 3, 2024
fded83f
chore: compiles
Oighty Jan 3, 2024
b1810ca
Add stubs for inherited functions, so that AuctionHouse can be non-ab…
0xJem Jan 5, 2024
d6deaa3
chore: linting
0xJem Jan 5, 2024
718d1da
Function docs
0xJem Jan 8, 2024
c2e20bc
WIP tests for auction creation
0xJem Jan 8, 2024
6bc9ac1
Merge branch 'jem/module-management' into jem/auction-management
0xJem Jan 8, 2024
5f96a59
Commenced tests for auction creation
0xJem Jan 8, 2024
01a0dd1
Progress on Auctioneer tests
0xJem Jan 8, 2024
d12af45
Tests for derivatives
0xJem Jan 8, 2024
b79e362
Tests for Condenser module
0xJem Jan 8, 2024
e0f4229
Merge branch 'master' into clean-up
Oighty Jan 8, 2024
75640cf
chore: run linter
Oighty Jan 8, 2024
4afa848
Integrate Slither recommendations
0xJem Jan 9, 2024
574d36b
Remove duplications
0xJem Jan 9, 2024
e37c0ce
Merge branch 'master' into jem/auction-management
0xJem Jan 9, 2024
b38a3d9
Add condenser module lookup. Cleanup of tests.
0xJem Jan 9, 2024
f316477
Tests for setCondenserLookup
0xJem Jan 9, 2024
c43159d
Add tests for cancellation
0xJem Jan 9, 2024
7299d18
Check AuctionModule.isLive()
0xJem Jan 9, 2024
73c4e33
Add tests for allowlist and hooks
0xJem Jan 9, 2024
ca6298d
Documentation
0xJem Jan 9, 2024
fc100be
Tests for AuctionModule.auction()
0xJem Jan 9, 2024
32ceb60
Test for onlyParent
0xJem Jan 9, 2024
2f4c7c1
Add tests on AuctionModule for cancel
0xJem Jan 9, 2024
262f76e
feat: store condensers and update module references in routing
Oighty Jan 9, 2024
0740a7e
Fix test for auction start time
0xJem Jan 10, 2024
ec025d0
Note on start time
0xJem Jan 10, 2024
408e352
Explicit imports
0xJem Jan 10, 2024
b3766d3
Documentation
0xJem Jan 10, 2024
501988e
Merge branch 'clean-up' into jem/auction-management
0xJem Jan 10, 2024
4a32f16
Fix compiler errors from merge
0xJem Jan 10, 2024
3692eb1
Test fixes
0xJem Jan 10, 2024
a5b39d7
Validation for hooks and allowlist
0xJem Jan 10, 2024
18d6d55
Address compiler warnings
0xJem Jan 11, 2024
c348442
Address linter warnings
0xJem Jan 11, 2024
082ff91
Update solhint
0xJem Jan 11, 2024
2743209
chore: comment out derivatizer and remove stubbed functions
Oighty Jan 11, 2024
dd04b0e
refactor: change fee functions to reduce gas costs
Oighty Jan 11, 2024
e4f92e2
chore: add TODO as reminder
Oighty Jan 11, 2024
ba88174
chore: run linter
Oighty Jan 11, 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
3 changes: 3 additions & 0 deletions .gitmodules
Original file line number Diff line number Diff line change
Expand Up @@ -7,3 +7,6 @@
[submodule "lib/solmate"]
path = lib/solmate
url = https://github.com/transmissions11/solmate
[submodule "lib/prb-math"]
path = lib/prb-math
url = https://github.com/PaulRBerg/prb-math
5 changes: 4 additions & 1 deletion design/FEATURES.md
Original file line number Diff line number Diff line change
Expand Up @@ -128,7 +128,10 @@ Auctions (and auction types) will have different configuration options. This wil
- Payout token
- Quote token
- Purchase hook addresses
- Optional whitelist of allowed bidders
- Optional allowlist of allowed bidders
- The allowlist is a contract that supports two approaches:
- Determining if a user address is allowed, in general
- Determining if a user address is allowed for a specific auction lot
- Capacity (in quote or payout token)
- Optional derivative type and parameters
- Optional condenser type (used to manipulate auction output for the derivative module)
Expand Down
1 change: 1 addition & 0 deletions lib/prb-math
Submodule prb-math added at 9dc065
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,6 @@
"author": "",
"license": "ISC",
"devDependencies": {
"solhint": "^4.0.0"
"solhint": "^4.1.1"
}
}
8 changes: 4 additions & 4 deletions pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

178 changes: 114 additions & 64 deletions src/AuctionHouse.sol
Original file line number Diff line number Diff line change
@@ -1,11 +1,8 @@
/// SPDX-License-Identifier: AGPL-3.0
pragma solidity 0.8.19;

Check warning on line 2 in src/AuctionHouse.sol

View workflow job for this annotation

GitHub Actions / Foundry project

Found more than One contract per file. 3 contracts found!

import {ERC20} from "lib/solmate/src/tokens/ERC20.sol";
import {SafeTransferLib} from "lib/solmate/src/utils/SafeTransferLib.sol";
import {EIP712} from "lib/solady/src/utils/EIP712.sol";
import {SignatureCheckerLib} from "lib/solady/src/utils/SignatureCheckerLib.sol";
import {Owned} from "lib/solmate/src/auth/Owned.sol";

import {Derivatizer} from "src/bases/Derivatizer.sol";
import {Auctioneer} from "src/bases/Auctioneer.sol";
Expand All @@ -15,7 +12,7 @@

import {Auction, AuctionModule} from "src/modules/Auction.sol";

import {fromKeycode, WithModules} from "src/modules/Modules.sol";
import {Veecode, fromVeecode, WithModules} from "src/modules/Modules.sol";

abstract contract FeeManager {
// TODO write fee logic in separate contract to keep it organized
Expand Down Expand Up @@ -45,7 +42,13 @@

// Address the protocol receives fees at
// TODO make this updatable
address internal _protocol;
address internal immutable PROTOCOL;

// ========== CONSTRUCTOR ========== //

constructor(address protocol_) {
PROTOCOL = protocol_;
}

// ========== ATOMIC AUCTIONS ========== //

Expand Down Expand Up @@ -82,34 +85,66 @@
) external virtual returns (uint256[] memory amountsOut);
}

// contract AuctionHouse is Derivatizer, Auctioneer, Router {
abstract contract AuctionHouse is Derivatizer, Auctioneer, Router {
/// @title AuctionHouse
/// @notice As its name implies, the AuctionHouse is where auctions take place and the core of the protocol.
contract AuctionHouse is Derivatizer, Auctioneer, Router {
using SafeTransferLib for ERC20;

/// Implement the router functionality here since it combines all of the base functionality

// ========== ERRORS ========== //

error AuctionHouse_AmountLessThanMinimum();
error AmountLessThanMinimum();
error InvalidHook();
error UnsupportedToken(ERC20 token_);

// ========== EVENTS ========== //

event Purchase(
uint256 indexed id,
address indexed buyer,
address indexed referrer,
uint256 amount,
uint256 payout
);
event Purchase(uint256 id, address buyer, address referrer, uint256 amount, uint256 payout);

// ========== CONSTRUCTOR ========== //

constructor() WithModules(msg.sender) {
//
}
constructor(address protocol_) Router(protocol_) WithModules(msg.sender) {}

// ========== DIRECT EXECUTION ========== //

// ========== AUCTION FUNCTIONS ========== //

function allocateFees(
address referrer_,
ERC20 quoteToken_,
uint256 amount_
) internal returns (uint256 totalFees) {
// TODO should protocol and/or referrer be able to charge different fees based on the type of auction being used?

// Calculate fees for purchase
// 1. Calculate referrer fee
// 2. Calculate protocol fee as the total expected fee amount minus the referrer fee
// to avoid issues with rounding from separate fee calculations
uint256 toReferrer;
uint256 toProtocol;
if (referrer_ == address(0)) {
// There is no referrer
toProtocol = (amount_ * protocolFee) / FEE_DECIMALS;
} else {
uint256 referrerFee = referrerFees[referrer_]; // reduce to single SLOAD
if (referrerFee == 0) {
// There is a referrer, but they have not set a fee
// If protocol fee is zero, return zero
// Otherwise, calcualte protocol fee
if (protocolFee == 0) return 0;
toProtocol = (amount_ * protocolFee) / FEE_DECIMALS;
} else {
// There is a referrer and they have set a fee
toReferrer = (amount_ * referrerFee) / FEE_DECIMALS;
toProtocol = ((amount_ * (protocolFee + referrerFee)) / FEE_DECIMALS) - toReferrer;
}
}

// Update fee balances if non-zero
if (toReferrer > 0) rewards[referrer_][quoteToken_] += toReferrer;
if (toProtocol > 0) rewards[PROTOCOL][quoteToken_] += toProtocol;

return toReferrer + toProtocol;
}

function purchase(
address recipient_,
address referrer_,
Expand All @@ -119,53 +154,64 @@
bytes calldata auctionData_,
bytes calldata approval_
) external override returns (uint256 payout) {
AuctionModule module = _getModuleForId(id_);

// TODO should this not check if the auction is atomic?
// Response: No, my thought was that the module will just revert on `purchase` if it's not atomic. Vice versa

// Calculate fees for purchase
// 1. Calculate referrer fee
// 2. Calculate protocol fee as the total expected fee amount minus the referrer fee
// to avoid issues with rounding from separate fee calculations
// TODO think about how to reduce storage loads
uint256 toReferrer =
referrer_ == address(0) ? 0 : (amount_ * referrerFees[referrer_]) / FEE_DECIMALS;
uint256 toProtocol =
((amount_ * (protocolFee + referrerFees[referrer_])) / FEE_DECIMALS) - toReferrer;

// Load routing data for the lot
Routing memory routing = lotRouting[id_];

uint256 totalFees = allocateFees(referrer_, routing.quoteToken, amount_);

// Send purchase to auction house and get payout plus any extra output
(payout) = module.purchase(
recipient_, referrer_, amount_ - toReferrer - toProtocol, id_, auctionData_, approval_
);
bytes memory auctionOutput;
{
AuctionModule module = _getModuleForId(id_);
(payout, auctionOutput) = module.purchase(id_, amount_ - totalFees, auctionData_);
}

// Check that payout is at least minimum amount out
// @dev Moved the slippage check from the auction to the AuctionHouse to allow different routing and purchase logic
if (payout < minAmountOut_) revert AuctionHouse_AmountLessThanMinimum();

// Update fee balances if non-zero
if (toReferrer > 0) rewards[referrer_][routing.quoteToken] += toReferrer;
if (toProtocol > 0) rewards[_protocol][routing.quoteToken] += toProtocol;
if (payout < minAmountOut_) revert AmountLessThanMinimum();

// Handle transfers from purchaser and seller
_handleTransfers(routing, amount_, payout, toReferrer + toProtocol, approval_);
_handleTransfers(id_, routing, amount_, payout, totalFees, approval_);

// Handle payout to user, including creation of derivative tokens
// _handlePayout(id_, routing, recipient_, payout, auctionOutput);
_handlePayout(routing, recipient_, payout, auctionOutput);

// Emit event
emit Purchase(id_, msg.sender, referrer_, amount_, payout);
}

// ============ DELEGATED EXECUTION ========== //
function bid(
address recipient_,
address referrer_,
uint256 id_,
uint256 amount_,
uint256 minAmountOut_,
bytes calldata auctionData_,
bytes calldata approval_
) external override {
// TODO
}

function settle(uint256 id_) external override returns (uint256[] memory amountsOut) {
// TODO
}

// Off-chain auction variant
function settle(
uint256 id_,
Auction.Bid[] memory bids_
) external override returns (uint256[] memory amountsOut) {
// TODO
}

// ============ INTERNAL EXECUTION FUNCTIONS ========== //

/// @notice Handles transfer of funds from user and market owner/callback
function _handleTransfers(
uint256 id_,
Routing memory routing_,
uint256 amount_,
uint256 payout_,
Expand All @@ -177,15 +223,16 @@

// Check if approval signature has been provided, if so use it increase allowance
// TODO a bunch of extra data has to be provided for Permit.
// if (approval_ != bytes(0))
if (approval_.length != 0) {}

// Have to transfer to teller first since fee is in quote token
// Check balance before and after to ensure full amount received, revert if not
// Handles edge cases like fee-on-transfer tokens (which are not supported)
uint256 quoteBalance = routing_.quoteToken.balanceOf(address(this));
routing_.quoteToken.safeTransferFrom(msg.sender, address(this), amount_);
// if (routing_.quoteToken.balanceOf(address(this)) < quoteBalance + amount_)
// revert Router_UnsupportedToken();
if (routing_.quoteToken.balanceOf(address(this)) < quoteBalance + amount_) {
revert UnsupportedToken(routing_.quoteToken);
}

// If callback address supplied, transfer tokens from teller to callback, then execute callback function,
// and ensure proper amount of tokens transferred in.
Expand All @@ -195,61 +242,64 @@
routing_.quoteToken.safeTransfer(address(routing_.hooks), amountLessFee);

// Call the callback function to receive payout tokens for payout
// uint256 payoutBalance = routing_.payoutToken.balanceOf(address(this));
// IBondCallback(routing_.callbackAddr).callback(id_, amountLessFee, payout_);
uint256 baseBalance = routing_.baseToken.balanceOf(address(this));
routing_.hooks.mid(id_, amountLessFee, payout_);

// Check to ensure that the callback sent the requested amount of payout tokens back to the teller
// if (routing_.payoutToken.balanceOf(address(this)) < (payoutBalance + payout_))
// revert Teller_InvalidCallback();
if (routing_.baseToken.balanceOf(address(this)) < (baseBalance + payout_)) {
revert InvalidHook();
}
} else {
// If no callback is provided, transfer tokens from market owner to this contract
// for payout.
// Check balance before and after to ensure full amount received, revert if not
// Handles edge cases like fee-on-transfer tokens (which are not supported)
// uint256 payoutBalance = routing_.payoutToken.balanceOf(address(this));
// routing_.payoutToken.safeTransferFrom(routing_.owner, address(this), payout_);
// if (routing_.payoutToken.balanceOf(address(this)) < (payoutBalance + payout_))
// revert Router_UnsupportedToken();
uint256 baseBalance = routing_.baseToken.balanceOf(address(this));
routing_.baseToken.safeTransferFrom(routing_.owner, address(this), payout_);
if (routing_.baseToken.balanceOf(address(this)) < (baseBalance + payout_)) {
revert UnsupportedToken(routing_.baseToken);
}

routing_.quoteToken.safeTransfer(routing_.owner, amountLessFee);
}
}

function _handlePayout(
uint256 lotId_,
Routing memory routing_,
address recipient_,
uint256 payout_,
bytes memory auctionOutput_
) internal {
// If no derivative, then the payout is sent directly to the recipient
// Otherwise, send parameters and payout to the derivative to mint to recipient
if (fromKeycode(routing_.derivativeType) == bytes6(0)) {
if (fromVeecode(routing_.derivativeReference) == bytes7("")) {
// No derivative, send payout to recipient
// routing_.payoutToken.safeTransfer(recipient_, payout_);
routing_.baseToken.safeTransfer(recipient_, payout_);
} else {
// Get the module for the derivative type
// We assume that the module type has been checked when the lot was created
DerivativeModule module =
DerivativeModule(_getLatestModuleIfActive(routing_.derivativeType));
DerivativeModule(_getModuleIfInstalled(routing_.derivativeReference));

bytes memory derivativeParams = routing_.derivativeParams;

// Lookup condensor module from combination of auction and derivative types
// If condenser specified, condense auction output and derivative params before sending to derivative module
if (fromKeycode(routing_.condenserType) != bytes6(0)) {
Veecode condenserRef =
condensers[routing_.auctionReference][routing_.derivativeReference];
if (fromVeecode(condenserRef) != bytes7("")) {
// Get condenser module
CondenserModule condenser =
CondenserModule(_getLatestModuleIfActive(routing_.condenserType));
CondenserModule condenser = CondenserModule(_getModuleIfInstalled(condenserRef));

// Condense auction output and derivative params
derivativeParams = condenser.condense(auctionOutput_, derivativeParams);
}

// Approve the module to transfer payout tokens
// routing_.payoutToken.safeApprove(address(module), payout_);
routing_.baseToken.safeApprove(address(module), payout_);

// Call the module to mint derivative tokens to the recipient
// module.mint(recipient_, payout_, derivativeParams, routing_.wrapDerivative);
module.mint(recipient_, derivativeParams, payout_, routing_.wrapDerivative);
}
}
}
Loading
Loading