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

Miscellaneous Interface and Function Clean-up #18

Merged
merged 12 commits into from
Jan 10, 2024
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
1 change: 1 addition & 0 deletions lib/prb-math
Submodule prb-math added at 9dc065
88 changes: 44 additions & 44 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 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,31 +85,22 @@
) external virtual returns (uint256[] memory amountsOut);
}

// contract AuctionHouse is Derivatizer, Auctioneer, Router {
// TODO abstract for now so compiler doesn't complain
abstract 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 ========== //

Expand Down Expand Up @@ -138,23 +132,23 @@
Routing memory routing = lotRouting[id_];

// 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;
(payout, auctionOutput) =
module.purchase(id_, amount_ - toReferrer - toProtocol, 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();
if (payout < minAmountOut_) revert AmountLessThanMinimum();

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

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

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

// Emit event
emit Purchase(id_, msg.sender, referrer_, amount_, payout);
Expand All @@ -166,6 +160,7 @@

/// @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 +172,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,28 +191,30 @@
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_,

Check warning on line 217 in src/AuctionHouse.sol

View workflow job for this annotation

GitHub Actions / Foundry project

Variable "lotId_" is unused
Routing memory routing_,
address recipient_,
uint256 payout_,
Expand All @@ -224,19 +222,21 @@
) 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 (fromKeycode(routing_.derivativeType) == bytes5("")) {
// 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 module = DerivativeModule(
_getModuleIfInstalled(routing_.derivativeType, routing_.derivativeVersion)
);

bytes memory derivativeParams = routing_.derivativeParams;

// TODO 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)) {
if (fromKeycode(routing_.condenserType) != bytes5("")) {
// Get condenser module
CondenserModule condenser =
CondenserModule(_getLatestModuleIfActive(routing_.condenserType));
Expand All @@ -246,10 +246,10 @@
}

// 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);
}
}
}
94 changes: 47 additions & 47 deletions src/bases/Auctioneer.sol
Original file line number Diff line number Diff line change
Expand Up @@ -4,41 +4,32 @@ pragma solidity 0.8.19;
import {ERC20} from "lib/solmate/src/tokens/ERC20.sol";

import "src/modules/Auction.sol";

import {fromKeycode} from "src/modules/Modules.sol";

import {DerivativeModule} from "src/modules/Derivative.sol";

interface IHooks {}

interface IAllowlist {}
import "src/interfaces/IHooks.sol";
import "src/interfaces/IAllowlist.sol";
0xJem marked this conversation as resolved.
Show resolved Hide resolved

abstract contract Auctioneer is WithModules {
// ========= ERRORS ========= //

error HOUSE_AuctionTypeSunset(Keycode auctionType);

error HOUSE_NotAuctionOwner(address caller);

error HOUSE_InvalidLotId(uint256 id);

error Auctioneer_InvalidParams();
error InvalidParams();
error InvalidLotId(uint256 id_);
error NotAuctionOwner(address caller_);

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

event AuctionCreated(uint256 indexed id, address indexed baseToken, address indexed quoteToken);
event AuctionCreated(uint256 id, address baseToken, address quoteToken);

// ========= DATA STRUCTURES ========== //

/// @notice Auction routing information for a lot
struct Routing {
Keycode auctionType; // auction type, represented by the Keycode for the auction submodule
uint8 auctionVersion; // auction version, represented by the version number of the auction submodule
0xJem marked this conversation as resolved.
Show resolved Hide resolved
address owner; // market owner. sends payout tokens, receives quote tokens
ERC20 baseToken; // token provided by seller
ERC20 quoteToken; // token to accept as payment
IHooks hooks; // (optional) address to call for any hooks to be executed on a purchase. Must implement IHooks.
IAllowlist allowlist; // (optional) contract that implements an allowlist for the market, based on IAllowlist
Keycode derivativeType; // (optional) derivative type, represented by the Keycode for the derivative submodule. If not set, no derivative will be created.
uint8 derivativeVersion; // (optional) derivative version, represented by the version number of the derivative submodule, must be set if derivative type is set
bytes derivativeParams; // (optional) abi-encoded data to be used to create payout derivatives on a purchase
bool wrapDerivative; // (optional) whether to wrap the derivative in a ERC20 token instead of the native ERC6909 format.
Keycode condenserType; // (optional) condenser type, represented by the Keycode for the condenser submodule. If not set, no condenser will be used. TODO should a condenser be stored on the auctionhouse for a particular auction/derivative combination and looked up?
Expand All @@ -65,13 +56,6 @@ abstract contract Auctioneer is WithModules {
/// @notice Counter for auction lots
uint256 public lotCounter;

/// @notice Designates whether an auction type is sunset on this contract
/// @dev We can remove Keycodes from the module to completely remove them,
/// However, that would brick any existing auctions of that type.
/// Therefore, we can sunset them instead, which will prevent new auctions.
/// After they have all ended, then we can remove them.
mapping(Keycode auctionType => bool) public typeSunset;

/// @notice Mapping of lot IDs to their auction type (represented by the Keycode for the auction submodule)
mapping(uint256 lotId => Routing) public lotRouting;

Expand All @@ -83,11 +67,13 @@ abstract contract Auctioneer is WithModules {
) external returns (uint256 id) {
// Load auction type module, this checks that it is installed.
// We load it here vs. later to avoid two checks.
Keycode auctionType = routing_.auctionType;
AuctionModule auctionModule = AuctionModule(_getLatestModuleIfActive(auctionType));

// Check that the auction type is allowing new auctions to be created
if (typeSunset[auctionType]) revert HOUSE_AuctionTypeSunset(auctionType);
AuctionModule auctionModule;
uint8 auctionVersion;
{
auctionModule = AuctionModule(_getLatestModuleIfActive(routing_.auctionType));
Veecode veecode = auctionModule.VEECODE();
(, auctionVersion) = unwrapVeecode(veecode);
}

// Increment lot count and get ID
id = lotCounter++;
Expand All @@ -101,38 +87,50 @@ abstract contract Auctioneer is WithModules {
uint8 baseTokenDecimals = routing_.baseToken.decimals();
uint8 quoteTokenDecimals = routing_.quoteToken.decimals();

if (baseTokenDecimals < 6 || baseTokenDecimals > 18) revert Auctioneer_InvalidParams();
if (quoteTokenDecimals < 6 || quoteTokenDecimals > 18) revert Auctioneer_InvalidParams();
if (baseTokenDecimals < 6 || baseTokenDecimals > 18) {
revert InvalidParams();
}
if (quoteTokenDecimals < 6 || quoteTokenDecimals > 18) {
revert InvalidParams();
}

// Store routing information
Routing storage routing = lotRouting[id];
routing.auctionType = routing_.auctionType;
routing.auctionVersion = auctionVersion;
routing.owner = msg.sender;
routing.baseToken = routing_.baseToken;
routing.quoteToken = routing_.quoteToken;
routing.hooks = routing_.hooks;

// If payout is a derivative, validate derivative data on the derivative module
if (fromKeycode(routing_.derivativeType) != bytes6(0)) {
if (fromKeycode(routing_.derivativeType) != bytes5("")) {
// Load derivative module, this checks that it is installed.
DerivativeModule derivativeModule =
DerivativeModule derivative =
DerivativeModule(_getLatestModuleIfActive(routing_.derivativeType));
Veecode veecode = derivative.VEECODE();
(, uint8 version) = unwrapVeecode(veecode);

// Call module validate function to validate implementation-specific data
derivativeModule.validate(routing_.derivativeParams);
if (!derivative.validate(routing_.derivativeParams)) revert InvalidParams();

// Store derivative information
routing.derivativeType = routing_.derivativeType;
routing.derivativeVersion = version;
routing.derivativeParams = routing_.derivativeParams;
}

// If allowlist is being used, validate the allowlist data and register the auction on the allowlist
if (address(routing_.allowlist) != address(0)) {
// TODO
}

// Store routing information
Routing storage routing = lotRouting[id];
routing.auctionType = auctionType;
routing.owner = msg.sender;
routing.baseToken = routing_.baseToken;
routing.quoteToken = routing_.quoteToken;
routing.hooks = routing_.hooks;

emit AuctionCreated(id, address(routing.baseToken), address(routing.quoteToken));
}

function cancel(uint256 id_) external {
// Check that caller is the auction owner
if (msg.sender != lotRouting[id_].owner) revert HOUSE_NotAuctionOwner(msg.sender);
if (msg.sender != lotRouting[id_].owner) revert NotAuctionOwner(msg.sender);
0xJem marked this conversation as resolved.
Show resolved Hide resolved

AuctionModule module = _getModuleForId(id_);

Expand All @@ -144,7 +142,7 @@ abstract contract Auctioneer is WithModules {

function getRouting(uint256 id_) external view returns (Routing memory) {
// Check that lot ID is valid
if (id_ >= lotCounter) revert HOUSE_InvalidLotId(id_);
if (id_ >= lotCounter) revert InvalidLotId(id_);
0xJem marked this conversation as resolved.
Show resolved Hide resolved

// Get routing from lot routing
return lotRouting[id_];
Expand Down Expand Up @@ -188,7 +186,7 @@ abstract contract Auctioneer is WithModules {

function ownerOf(uint256 id_) external view returns (address) {
// Check that lot ID is valid
if (id_ >= lotCounter) revert HOUSE_InvalidLotId(id_);
if (id_ >= lotCounter) revert InvalidLotId(id_);

// Get owner from lot routing
return lotRouting[id_].owner;
Expand All @@ -205,9 +203,11 @@ abstract contract Auctioneer is WithModules {

function _getModuleForId(uint256 id_) internal view returns (AuctionModule) {
// Confirm lot ID is valid
if (id_ >= lotCounter) revert HOUSE_InvalidLotId(id_);
if (id_ >= lotCounter) revert InvalidLotId(id_);

// Load module, will revert if not installed
return AuctionModule(_getLatestModuleIfActive(lotRouting[id_].auctionType));
return AuctionModule(
_getModuleIfInstalled(lotRouting[id_].auctionType, lotRouting[id_].auctionVersion)
);
}
}
8 changes: 2 additions & 6 deletions src/bases/Derivatizer.sol
Original file line number Diff line number Diff line change
Expand Up @@ -12,12 +12,8 @@ abstract contract Derivatizer is WithModules {
bytes memory data,
bool wrapped
) external virtual returns (uint256, address) {
// Load the derivative module, will revert if not installed
Derivative derivative = Derivative(address(_getLatestModuleIfActive(dType)));

// Check that the type hasn't been sunset
ModStatus storage moduleStatus = getModuleStatus[dType];
if (moduleStatus.sunset) revert("Derivatizer: type sunset");
// Load the derivative module, will revert if not installed or sunset
DerivativeModule derivative = DerivativeModule(_getLatestModuleIfActive(dType));

// Call the deploy function on the derivative module
(uint256 tokenId, address wrappedToken) = derivative.deploy(data, wrapped);
Expand Down
Loading
Loading