diff --git a/.gitmodules b/.gitmodules index 6ef80fce..fa36f781 100644 --- a/.gitmodules +++ b/.gitmodules @@ -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 diff --git a/lib/prb-math b/lib/prb-math new file mode 160000 index 00000000..9dc06519 --- /dev/null +++ b/lib/prb-math @@ -0,0 +1 @@ +Subproject commit 9dc06519f3b9f1659fec7d396da634fe690f660c diff --git a/src/AuctionHouse.sol b/src/AuctionHouse.sol index 8f3503d6..c3cebbb5 100644 --- a/src/AuctionHouse.sol +++ b/src/AuctionHouse.sol @@ -3,9 +3,6 @@ pragma solidity 0.8.19; 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"; @@ -15,7 +12,7 @@ import {DerivativeModule} from "src/modules/Derivative.sol"; 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 @@ -45,7 +42,13 @@ abstract contract Router is FeeManager { // 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 ========== // @@ -82,31 +85,22 @@ abstract contract Router is FeeManager { ) 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 ========== // @@ -138,23 +132,23 @@ abstract contract AuctionHouse is Derivatizer, Auctioneer, Router { 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(routing, recipient_, payout, auctionOutput); // Emit event emit Purchase(id_, msg.sender, referrer_, amount_, payout); @@ -166,6 +160,7 @@ abstract contract AuctionHouse is Derivatizer, Auctioneer, Router { /// @notice Handles transfer of funds from user and market owner/callback function _handleTransfers( + uint256 id_, Routing memory routing_, uint256 amount_, uint256 payout_, @@ -177,15 +172,16 @@ abstract contract AuctionHouse is Derivatizer, Auctioneer, Router { // 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. @@ -195,28 +191,29 @@ abstract contract AuctionHouse is Derivatizer, Auctioneer, Router { 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_, @@ -224,32 +221,34 @@ abstract contract AuctionHouse is Derivatizer, Auctioneer, Router { ) 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); } } } diff --git a/src/bases/Auctioneer.sol b/src/bases/Auctioneer.sol index 9d810a85..c1c20e26 100644 --- a/src/bases/Auctioneer.sol +++ b/src/bases/Auctioneer.sol @@ -4,44 +4,33 @@ 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 {IHooks} from "src/interfaces/IHooks.sol"; +import {IAllowlist} from "src/interfaces/IAllowlist.sol"; 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 InvalidModuleType(Veecode reference_); + 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 + Veecode auctionReference; // auction module, represented by its Veecode 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. + Veecode derivativeReference; // (optional) derivative module, represented by its Veecode. If not set, no derivative will be created. 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? } struct RoutingParams { @@ -54,7 +43,6 @@ abstract contract Auctioneer is WithModules { bytes payoutData; Keycode derivativeType; // (optional) derivative type, represented by the Keycode for the derivative submodule. If not set, no derivative will be created. bytes derivativeParams; // (optional) data to be used to create payout derivatives on a purchase - Keycode condenserType; // (optional) condenser type, represented by the Keycode for the condenser submodule. If not set, no condenser will be used. } // ========= STATE ========== // @@ -65,16 +53,13 @@ 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; + /// @notice Mapping auction and derivative references to the condenser that is used to pass data between them + mapping(Veecode auctionRef => mapping(Veecode derivativeRef => Veecode condenserRef)) public + condensers; + // ========== AUCTION MANAGEMENT ========== // function auction( @@ -83,11 +68,8 @@ 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 = AuctionModule(_getLatestModuleIfActive(routing_.auctionType)); + Veecode auctionRef = auctionModule.VEECODE(); // Increment lot count and get ID id = lotCounter++; @@ -101,38 +83,47 @@ 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.auctionReference = auctionRef; + 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 derivativeRef = derivative.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.derivativeReference = derivativeRef; + 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 + // TODO register auction on allowlist } - // 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); AuctionModule module = _getModuleForId(id_); @@ -144,7 +135,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_); // Get routing from lot routing return lotRouting[id_]; @@ -188,7 +179,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; @@ -205,9 +196,33 @@ 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_].auctionReference)); + } + + // ========== GOVERNANCE FUNCTIONS ========== // + + // TODO set access control + function setCondenser( + Veecode auctionRef_, + Veecode derivativeRef_, + Veecode condenserRef_ + ) external { + // Validate that the modules are installed and of the correct type + Module auctionModule = Module(_getModuleIfInstalled(auctionRef_)); + Module derivativeModule = Module(_getModuleIfInstalled(derivativeRef_)); + Module condenserModule = Module(_getModuleIfInstalled(condenserRef_)); + + if (auctionModule.TYPE() != Module.Type.Auction) revert InvalidModuleType(auctionRef_); + if (derivativeModule.TYPE() != Module.Type.Derivative) { + revert InvalidModuleType(derivativeRef_); + } + if (condenserModule.TYPE() != Module.Type.Condenser) { + revert InvalidModuleType(condenserRef_); + } + + condensers[auctionRef_][derivativeRef_] = condenserRef_; } } diff --git a/src/bases/Derivatizer.sol b/src/bases/Derivatizer.sol index 3a7e3192..9fd53a22 100644 --- a/src/bases/Derivatizer.sol +++ b/src/bases/Derivatizer.sol @@ -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); diff --git a/src/interfaces/IAllowlist.sol b/src/interfaces/IAllowlist.sol new file mode 100644 index 00000000..30195209 --- /dev/null +++ b/src/interfaces/IAllowlist.sol @@ -0,0 +1,32 @@ +// SPDX-License-Identifier: AGPL-3.0 +pragma solidity >=0.8.0; + +interface IAllowlist { + /// @notice Check if address is allowed to interact with sending contract + /// @param user_ Address to check + /// @param proof_ Data to be used in determining allow status (optional, depends on specific implementation) + /// @return True if allowed, false otherwise + function isAllowed(address user_, bytes calldata proof_) external view returns (bool); + + /// @notice Check if address is allowed to interact with market ID on sending contract + /// @param id_ Market ID to check + /// @param user_ Address to check + /// @param proof_ Data to be used in determining allow status (optional, depends on specific implementation + /// @return True if allowed, false otherwise + function isAllowed( + uint256 id_, + address user_, + bytes calldata proof_ + ) external view returns (bool); + + /// @notice Register allowlist for sending address + /// @dev Can be used to intialize or update an allowlist + /// @param params_ Parameters to configure allowlist (depends on specific implementation) + function register(bytes calldata params_) external; + + /// @notice Register allowlist for market ID on sending address + /// @dev Can be used to intialize or update an allowlist + /// @param id_ Market ID to register allowlist for + /// @param params_ Parameters to configure allowlist (depends on specific implementation) + function register(uint256 id_, bytes calldata params_) external; +} diff --git a/src/interfaces/IHooks.sol b/src/interfaces/IHooks.sol new file mode 100644 index 00000000..8a57c58b --- /dev/null +++ b/src/interfaces/IHooks.sol @@ -0,0 +1,15 @@ +// SPDX-License-Identifier: AGPL-3.0 +pragma solidity >=0.8.0; + +/// @title IHooks +/// @notice Interface for hook contracts to be called during auction payment and payout +interface IHooks { + /// @notice Called before payment and payout + function pre(uint256 lotId_, uint256 amount_) external; + + /// @notice Called after payment and before payout + function mid(uint256 lotId_, uint256 amount_, uint256 payout_) external; + + /// @notice Called after payment and after payout + function post(uint256 lotId_, uint256 payout_) external; +} diff --git a/src/modules/Auction.sol b/src/modules/Auction.sol index 7b81ad86..2e6f111d 100644 --- a/src/modules/Auction.sol +++ b/src/modules/Auction.sol @@ -60,27 +60,20 @@ abstract contract Auction { // ========== ATOMIC AUCTIONS ========== // - /// @param approval_ - (Optional) Permit approval signature for the quoteToken function purchase( - address recipient_, - address referrer_, uint256 id_, uint256 amount_, - bytes calldata auctionData_, - bytes calldata approval_ - ) external virtual returns (uint256 payout); + bytes calldata auctionData_ + ) external virtual returns (uint256 payout, bytes memory auctionOutput); // ========== BATCH AUCTIONS ========== // // On-chain auction variant function bid( - address recipient_, - address referrer_, uint256 id_, uint256 amount_, uint256 minAmountOut_, - bytes calldata auctionData_, - bytes calldata approval_ + bytes calldata auctionData_ ) external virtual; function settle(uint256 id_) external virtual returns (uint256[] memory amountsOut); @@ -114,6 +107,10 @@ abstract contract Auction { } abstract contract AuctionModule is Auction, Module { + // ========== CONSTRUCTOR ========== // + + constructor(address auctionHouse_) Module(auctionHouse_) {} + // ========== AUCTION MANAGEMENT ========== // function auction(uint256 id_, AuctionParams memory params_) external override onlyParent { diff --git a/src/modules/Derivative.sol b/src/modules/Derivative.sol index f80924f1..e003b3de 100644 --- a/src/modules/Derivative.sol +++ b/src/modules/Derivative.sol @@ -36,6 +36,7 @@ abstract contract Derivative { ) external virtual returns (uint256, address); /// @notice Mint new derivative tokens. Deploys the derivative token if it does not already exist. + /// @param to_ The address to mint the derivative tokens to /// @param params_ ABI-encoded parameters for the derivative to be created /// @param amount_ The amount of derivative tokens to create /// @param wrapped_ Whether (true) or not (false) the derivative should be wrapped in an ERC20 token for composability @@ -43,12 +44,14 @@ abstract contract Derivative { /// @return wrappedAddress_ The address of the ERC20 wrapped derivative token, if wrapped_ is true, otherwise, it's the zero address. /// @return amountCreated_ The amount of derivative tokens created function mint( + address to_, bytes memory params_, uint256 amount_, bool wrapped_ ) external virtual returns (uint256, address, uint256); /// @notice Mint new derivative tokens for a specific token Id + /// @param to_ The address to mint the derivative tokens to /// @param tokenId_ The ID of the derivative token /// @param amount_ The amount of derivative tokens to create /// @param wrapped_ Whether (true) or not (false) the derivative should be wrapped in an ERC20 token for composability @@ -56,6 +59,7 @@ abstract contract Derivative { /// @return wrappedAddress_ The address of the ERC20 wrapped derivative token, if wrapped_ is true, otherwise, it's the zero address. /// @return amountCreated_ The amount of derivative tokens created function mint( + address to_, uint256 tokenId_, uint256 amount_, bool wrapped_ @@ -92,6 +96,9 @@ abstract contract Derivative { // Unwrap an ERC20 derivative token into the underlying ERC6909 derivative function unwrap(uint256 tokenId_, uint256 amount_) external virtual; + // Validate derivative params for the specific implementation + function validate(bytes memory params_) external view virtual returns (bool); + // ========== DERIVATIVE INFORMATION ========== // // TODO view function to format implementation specific token data correctly and return to user @@ -110,6 +117,4 @@ abstract contract Derivative { function computeId(bytes memory params_) external pure virtual returns (uint256); } -abstract contract DerivativeModule is Derivative, ERC6909, Module { - function validate(bytes memory params_) external view virtual returns (bool); -} +abstract contract DerivativeModule is Derivative, ERC6909, Module {} diff --git a/src/modules/auctions/GDA.sol b/src/modules/auctions/GDA.sol index a3558b9b..d93a4e51 100644 --- a/src/modules/auctions/GDA.sol +++ b/src/modules/auctions/GDA.sol @@ -15,7 +15,7 @@ pragma solidity 0.8.19; // struct AuctionData { // uint256 equilibriumPrice; // price at which the auction is balanced // uint256 minimumPrice; // minimum price the auction can reach -// uint256 payoutScale; +// uint256 baseScale; // uint256 quoteScale; // uint48 lastAuctionStart; // Decay decayType; // type of decay to use for the market @@ -25,12 +25,14 @@ pragma solidity 0.8.19; // /* ========== STATE ========== */ -// SD59x18 public constant ONE = convert(int256(1)); +// SD59x18 public constant ONE = SD59x18.wrap(1e18); // mapping(uint256 lotId => AuctionData) public auctionData; // } // contract GradualDutchAuctioneer is AtomicAuctionModule, GDA { // /* ========== ERRORS ========== */ +// error InsufficientCapacity(); +// error InvalidParams(); // /* ========== CONSTRUCTOR ========== */ @@ -38,6 +40,11 @@ pragma solidity 0.8.19; // address auctionHouse_ // ) Module(auctionHouse_) {} +// /* ========== MODULE FUNCTIONS ========== */ +// function ID() public pure override returns (Keycode, uint8) { +// return (toKeycode("GDA"), 1); +// } + // /* ========== MARKET FUNCTIONS ========== */ // function _auction( @@ -47,7 +54,7 @@ pragma solidity 0.8.19; // ) internal override { // // Decode params // ( -// uint256 equilibriumPrice_, // quote tokens per payout token, so would be in quote token decimals? +// uint256 equilibriumPrice_, // quote tokens per base token, so would be in quote token decimals? // Decay decayType_, // SD59x18 decayConstant_ // ) = abi.decode(params_, (uint256, Decay, SD59x18)); @@ -55,18 +62,18 @@ pragma solidity 0.8.19; // // Validate params // // TODO -// // Calculate scale from payout token decimals -// uint256 payoutScale = 10 ** uint256(lot_.payoutToken.decimals()); +// // Calculate scale from base token decimals +// uint256 baseScale = 10 ** uint256(lot_.baseToken.decimals()); // uint256 quoteScale = 10 ** uint256(lot_.quoteToken.decimals()); // // Calculate emissions rate -// uint256 payoutCapacity = lot_.capacityInQuote ? lot_.capacity.mulDiv(payoutScale, equilibriumPrice_) : lot_.capacity; -// SD59x18 emissionsRate = sd(int256(payoutCapacity.mulDiv(uUNIT, (lot_.conclusion - lot_.start) * payoutScale))); +// uint256 baseCapacity = lot_.capacityInQuote ? lot_.capacity.mulDiv(baseScale, equilibriumPrice_) : lot_.capacity; +// SD59x18 emissionsRate = sd(int256(baseCapacity.mulDiv(uUNIT, (lot_.conclusion - lot_.start) * baseScale))); // // Set auction data // AuctionData storage auction = auctionData[id_]; // auction.equilibriumPrice = equilibriumPrice_; -// auction.payoutScale = payoutScale; +// auction.baseScale = baseScale; // auction.quoteScale = quoteScale; // auction.lastAuctionStart = uint48(block.timestamp); // auction.decayType = decayType_; @@ -90,7 +97,7 @@ pragma solidity 0.8.19; // /* ========== PRICE FUNCTIONS ========== */ -// function priceFor(uint256 id_, uint256 payout_) external view returns (uint256) { +// function priceFor(uint256 id_, uint256 payout_) public view override returns (uint256) { // Decay decayType = auctionData[id_].decayType; // uint256 amount; @@ -103,7 +110,9 @@ pragma solidity 0.8.19; // // Check that amount in or payout do not exceed remaining capacity // Lot memory lot = lotData[id_]; // if (lot.capacityInQuote ? amount > lot.capacity : payout_ > lot.capacity) -// revert Auctioneer_InsufficientCapacity(); +// revert InsufficientCapacity(); + +// return amount; // } // // For Continuos GDAs with exponential decay, the price of a given token t seconds after being emitted is: p(t) = p0 * e^(-k*t) @@ -113,8 +122,8 @@ pragma solidity 0.8.19; // Lot memory lot = lotData[id_]; // AuctionData memory auction = auctionData[id_]; -// // Convert payout to SD59x18. We scale first to 18 decimals from the payout token decimals -// SD59x18 payout = sd(int256(payout_.mulDiv(uUNIT, auction.payoutScale))); +// // Convert payout to SD59x18. We scale first to 18 decimals from the base token decimals +// SD59x18 payout = sd(int256(payout_.mulDiv(uUNIT, auction.baseScale))); // // Calculate time since last auction start // SD59x18 timeSinceLastAuctionStart = convert( @@ -134,7 +143,7 @@ pragma solidity 0.8.19; // // Calculate return value // // This value should always be positive, therefore, we can safely cast to uint256 -// // We scale the return value back to payout token decimals +// // We scale the return value back to base token decimals // return num1.mul(num2).div(denominator).intoUint256().mulDiv(auction.quoteScale, uUNIT); // } @@ -145,8 +154,8 @@ pragma solidity 0.8.19; // Lot memory lot = lotData[id_]; // AuctionData memory auction = auctionData[id_]; -// // Convert payout to SD59x18. We scale first to 18 decimals from the payout token decimals -// SD59x18 payout = sd(int256(payout_.mulDiv(uUNIT, auction.payoutScale))); +// // Convert payout to SD59x18. We scale first to 18 decimals from the base token decimals +// SD59x18 payout = sd(int256(payout_.mulDiv(uUNIT, auction.baseScale))); // // Calculate time since last auction start // SD59x18 timeSinceLastAuctionStart = convert( @@ -174,13 +183,13 @@ pragma solidity 0.8.19; // /* ========== PAYOUT CALCULATIONS ========== */ -// function _payoutFor(uint256 id_, uint256 amount_) internal view override returns (uint256) { +// function _payoutFor(uint256 id_, uint256 amount_) internal view returns (uint256) { // (uint256 payout, ) = _payoutAndEmissionsFor(id_, amount_); // // Check that amount in or payout do not exceed remaining capacity // Lot memory lot = lotData[id_]; // if (lot.capacityInQuote ? amount_ > lot.capacity : payout > lot.capacity) -// revert Auctioneer_InsufficientCapacity(); +// revert InsufficientCapacity(); // return payout; // } @@ -196,18 +205,17 @@ pragma solidity 0.8.19; // } else if (decayType == Decay.LINEAR) { // return _payoutForLinearDecay(id_, amount_); // } else { -// revert Auctioneer_InvalidParams(); +// revert InvalidParams(); // } // } // // TODO check this math again -// // P = (r / k) * ln(Q * k / p0 * e^(k*T) + 1) where P is the number of payout tokens, Q is the number of quote tokens, r is the emissions rate, k is the decay constant, +// // P = (r / k) * ln(Q * k / p0 * e^(k*T) + 1) where P is the number of base tokens, Q is the number of quote tokens, r is the emissions rate, k is the decay constant, // // p0 is the price target of the market, and T is the time since the last auction start // function _payoutForExpDecay( // uint256 id_, // uint256 amount_ // ) internal view returns (uint256, uint48) { -// CoreData memory core = coreData[id_]; // AuctionData memory auction = auctionData[id_]; // // Convert to 18 decimals for fixed math by pre-computing the Q / p0 factor @@ -235,15 +243,15 @@ pragma solidity 0.8.19; // // Calculate the payout // SD59x18 payout = logFactor.mul(auction.emissionsRate).div(auction.decayConstant); -// // Scale back to payout token decimals +// // Scale back to base token decimals // // TODO verify we can safely cast to uint256 -// return payout.intoUint256().mulDiv(auction.payoutScale, uUNIT); +// return payout.intoUint256().mulDiv(auction.baseScale, uUNIT); // // Calculate seconds of emissions from payout or amount (depending on capacity type) -// // TODO emissionsRate is being set only in payout tokens over, need to think about this +// // TODO emissionsRate is being set only in base tokens over, need to think about this // // Might just use equilibrium price to convert the quote amount here to payout amount and then divide by emissions rate // uint48 secondsOfEmissions; -// if (core.capacityInQuote) { +// if (lotData[id_].capacityInQuote) { // // Convert amount to SD59x18 // SD59x18 amount = sd(int256(amount_.mulDiv(uUNIT, auction.quoteScale))); // secondsOfEmissions = uint48(amount.div(auction.emissionsRate).intoUint256()); @@ -251,16 +259,15 @@ pragma solidity 0.8.19; // secondsOfEmissions = uint48(payout.div(auction.emissionsRate).intoUint256()); // } -// // Scale payout to payout token decimals and return +// // Scale payout to base token decimals and return // // Payout should always be positive since it is atleast 1, therefore, we can safely cast to uint256 -// return (payout.intoUint256().mulDiv(auction.payoutScale, uUNIT), secondsOfEmissions); +// return (payout.intoUint256().mulDiv(auction.baseScale, uUNIT), secondsOfEmissions); // } // // TODO check this math again -// // P = (r / k) * (sqrt(2 * k * Q / p0) + k * T - 1) where P is the number of payout tokens, Q is the number of quote tokens, r is the emissions rate, k is the decay constant, +// // P = (r / k) * (sqrt(2 * k * Q / p0) + k * T - 1) where P is the number of base tokens, Q is the number of quote tokens, r is the emissions rate, k is the decay constant, // // p0 is the price target of the market, and T is the time since the last auction start // function _payoutForLinearDecay(uint256 id_, uint256 amount_) internal view returns (uint256) { -// Lot memory lot = lotData[id_]; // AuctionData memory auction = auctionData[id_]; // // Convert to 18 decimals for fixed math by pre-computing the Q / p0 factor @@ -285,7 +292,7 @@ pragma solidity 0.8.19; // // Calculate seconds of emissions from payout or amount (depending on capacity type) // // TODO same as in the above function // uint48 secondsOfEmissions; -// if (core.capacityInQuote) { +// if (lotData[id_].capacityInQuote) { // // Convert amount to SD59x18 // SD59x18 amount = sd(int256(amount_.mulDiv(uUNIT, auction.scale))); // secondsOfEmissions = uint48(amount.div(auction.emissionsRate).intoUint256()); @@ -293,8 +300,8 @@ pragma solidity 0.8.19; // secondsOfEmissions = uint48(payout.div(auction.emissionsRate).intoUint256()); // } -// // Scale payout to payout token decimals and return -// return (payout.intoUint256().mulDiv(auction.payoutScale, uUNIT), secondsOfEmissions); +// // Scale payout to base token decimals and return +// return (payout.intoUint256().mulDiv(auction.baseScale, uUNIT), secondsOfEmissions); // } // /* ========== ADMIN FUNCTIONS ========== */ // /* ========== VIEW FUNCTIONS ========== */ diff --git a/src/modules/auctions/OFDA.sol b/src/modules/auctions/OFDA.sol index d38f8f13..60a3eaff 100644 --- a/src/modules/auctions/OFDA.sol +++ b/src/modules/auctions/OFDA.sol @@ -13,21 +13,21 @@ pragma solidity 0.8.19; // uint256 minPrice; // } -// /// @notice Calculate current market price of payout token in quote tokens -// /// @param id_ ID of market -// /// @return Price for market in configured decimals (see MarketParams) -// /// @dev price is derived from the equation: -// // -// // p = max(min_p, o_p * (1 - d)) -// // -// // where -// // p = price -// // min_p = minimum price -// // o_p = oracle price -// // d = fixed discount -// // -// // if price is below minimum price, minimum price is returned -// function marketPrice(uint256 id_) external view returns (uint256); +// /// @notice Calculate current market price of payout token in quote tokens +// /// @param id_ ID of market +// /// @return Price for market in configured decimals (see MarketParams) +// /// @dev price is derived from the equation: +// // +// // p = max(min_p, o_p * (1 - d)) +// // +// // where +// // p = price +// // min_p = minimum price +// // o_p = oracle price +// // d = fixed discount +// // +// // if price is below minimum price, minimum price is returned +// function marketPrice(uint256 id_) external view returns (uint256); // } // import {MaxPayoutAuctioneer, IAggregator, Authority} from "src/auctioneers/bases/MaxPayoutAuctioneer.sol"; diff --git a/src/modules/auctions/bases/DiscreteAuction.sol b/src/modules/auctions/bases/DiscreteAuction.sol index 4eca4e96..47118532 100644 --- a/src/modules/auctions/bases/DiscreteAuction.sol +++ b/src/modules/auctions/bases/DiscreteAuction.sol @@ -3,8 +3,6 @@ pragma solidity 0.8.19; // import "src/modules/auctions/bases/AtomicAuction.sol"; -// import {Auction} from "src/modules/Auction.sol"; - // abstract contract DiscreteAuction { // /* ========== ERRORS ========== */ // error Auction_MaxPayoutExceeded(); @@ -47,6 +45,9 @@ pragma solidity 0.8.19; // abstract contract DiscreteAuctionModule is AtomicAuctionModule, DiscreteAuction { +// /* ========== ERRORS ========== */ +// error InvalidParams(); + // /* ========== CONSTRUCTOR ========== */ // constructor( @@ -59,7 +60,7 @@ pragma solidity 0.8.19; // function _auction( // uint256 id_, -// Auction.Lot memory lot_, +// Lot memory lot_, // bytes calldata params_ // ) internal override { // // Decode provided params @@ -67,8 +68,8 @@ pragma solidity 0.8.19; // // Validate that deposit interval is in-bounds // uint48 duration = lot_.conclusion - lot_.start; -// if (depositInterval < MIN_DEPOSIT_INTERVAL || depositInterval > duration) -// revert Auctioneer_InvalidParams(); +// if (depositInterval < minDepositInterval || depositInterval > duration) +// revert InvalidParams(); // // Set style data // StyleData memory style = styleData[id_]; @@ -76,34 +77,34 @@ pragma solidity 0.8.19; // style.scale = 10 ** lot_.quoteToken.decimals(); // // Call internal __createMarket function to store implementation-specific data -// __createMarket(id, lot_, style, params); +// __auction(id_, lot_, style, params); // // Set max payout (depends on auctionPrice being available so must be done after __createMarket) -// style.maxPayout = _baseCapacity(lot_).mulDiv(depositInterval, duration); +// style.maxPayout = _baseCapacity(id_, lot_).mulDiv(depositInterval, duration); // } // /// @dev implementation-specific auction creation logic can be inserted by overriding this function // function __auction( // uint256 id_, -// Auction.Lot memory lot_, +// Lot memory lot_, // StyleData memory style_, // bytes memory params_ // ) internal virtual; // /* ========== TELLER FUNCTIONS ========== */ -// function _purchase(uint256 id_, uint256 amount_) internal returns (uint256) { +// function _purchase(uint256 id_, uint256 amount_, bytes memory auctionData_) internal returns (uint256, bytes memory) { // // Get payout from implementation-specific purchase logic -// uint256 payout = __purchaseBond(id_, amount_); +// (uint256 payout, bytes memory auctionOutput) = __purchase(id_, amount_, auctionData_); // // Check that payout is less than or equal to max payout // if (payout > styleData[id_].maxPayout) revert Auction_MaxPayoutExceeded(); -// return payout; +// return (payout, auctionOutput); // } // /// @dev implementation-specific purchase logic can be inserted by overriding this function -// function __purchase(uint256 id_, uint256 amount_) internal virtual returns (uint256); +// function __purchase(uint256 id_, uint256 amount_, bytes memory auctionData_) internal virtual returns (uint256, bytes memory); // /* ========== ADMIN FUNCTIONS ========== */ @@ -120,7 +121,7 @@ pragma solidity 0.8.19; // /* ========== VIEW FUNCTIONS ========== */ -// function _baseCapacity(Auction.Lot memory lot_) internal view returns (uint256) { +// function _baseCapacity(uint256 id_, Lot memory lot_) internal view returns (uint256) { // // Calculate capacity in terms of base tokens // // If capacity is in quote tokens, convert to base tokens with auction price // // Otherwise, return capacity as-is @@ -158,7 +159,7 @@ pragma solidity 0.8.19; // function maxAmountAccepted(uint256 id_) external view override onlyParent returns (uint256) { // // Calculate maximum amount of quote tokens that correspond to max bond size // // Maximum of the maxPayout and the remaining capacity converted to quote tokens -// Auction.Lot memory lot = lotData[id_]; +// Lot memory lot = lotData[id_]; // StyleData memory style = styleData[id_]; // uint256 price = auctionPrice(id_); // uint256 quoteCapacity = lot.capacityInQuote @@ -177,7 +178,7 @@ pragma solidity 0.8.19; // /// @dev This function is gated by onlyParent because it does not include any fee logic, which is applied in the parent contract // function maxPayout(uint256 id_) public view override onlyParent returns (uint256) { // // Convert capacity to base token units for comparison with max payout -// uint256 capacity = _baseCapacity(lotData[id_]); +// uint256 capacity = _baseCapacity(id_, lotData[id_]); // // Cap max payout at the remaining capacity // return styleData[id_].maxPayout > capacity ? capacity : styleData[id_].maxPayout; diff --git a/src/modules/derivatives/CliffVesting.sol b/src/modules/derivatives/CliffVesting.sol index cedebd21..d7d59b3e 100644 --- a/src/modules/derivatives/CliffVesting.sol +++ b/src/modules/derivatives/CliffVesting.sol @@ -2,6 +2,7 @@ pragma solidity 0.8.19; // import {ClonesWithImmutableArgs} from "src/lib/clones/ClonesWithImmutableArgs.sol"; +// import {ERC20} from "solmate/tokens/ERC20.sol"; // import "src/modules/Derivative.sol"; // // TODO this only uses the ERC20 clones, need to convert to ERC6909 with optional ERC20 via wrapping the ERC6909 @@ -19,12 +20,12 @@ pragma solidity 0.8.19; // uint48 expiry; // } -// // ========== SUBMODULE SETUP ========== // +// // ========== MODULE SETUP ========== // -// constructor(Module parent_) Submodule(parent_) {} +// constructor(Module parent_) Module(parent_) {} -// function KEYCODE() public pure override returns (SubKeycode) { -// return toSubKeycode("VAULT.FIXED_EXPIRY"); +// function ID() public pure override returns (Keycode, uint8) { +// return (toKeycode("CFV"), uint8(1)); // } // // ========== DERIVATIVE MANAGEMENT ========== // @@ -48,7 +49,7 @@ pragma solidity 0.8.19; // // If wrapping, deploy ERC20 clone using ID as salt // // Note: token implementations implement view functions, but they are just a passthrough to get data from the tokenMetadata mapping on the vault contract // // Therefore, we don't need to store the data redundantly on the token contract -// SubKeycode dType = SUBKEYCODE(); +// Keycode dType = KEYCODE(); // if (wrapped_) { // // TODO think about collisions from different contract code and salts // t.wrapped = wrappedImplementations[dType].clone3(abi.encodePacked( @@ -70,71 +71,71 @@ pragma solidity 0.8.19; // // // Get address of fixed expiry token using salt // // address feToken = ClonesWithImmutableArgs.addressOfClone3(salt); -// // // Check if the token already exists. If not, deploy it. -// // if (feToken.code.length == 0) { -// // (string memory name, string memory symbol) = _getNameAndSymbol(underlying_, expiry); -// // bytes memory tokenData = abi.encodePacked( -// // bytes32(bytes(name)), -// // bytes32(bytes(symbol)), -// // uint8(base.decimals()), -// // base, -// // uint256(expiry), -// // address(this) -// // ); -// // feToken = address(dStore.implementation).clone3(tokenData, salt); -// // emit FixedExpiryERC20Created(feToken, base, expiry); -// // } -// // return bytes32(uint256(uint160(feToken))); -// } +// // Check if the token already exists. If not, deploy it. +// if (feToken.code.length == 0) { +// (string memory name, string memory symbol) = _getNameAndSymbol(underlying_, expiry); +// bytes memory tokenData = abi.encodePacked( +// bytes32(bytes(name)), +// bytes32(bytes(symbol)), +// uint8(base.decimals()), +// base, +// uint256(expiry), +// address(this) +// ); +// feToken = address(dStore.implementation).clone3(tokenData, salt); +// emit FixedExpiryERC20Created(feToken, base, expiry); +// } +// return bytes32(uint256(uint160(feToken))); +// } // function create(bytes memory data, uint256 amount) external override onlyParent returns (bytes memory) {} -// function redeem(bytes memory data, uint256 amount) external override onlyParent {} +// function redeem(bytes memory data, uint256 amount) external override onlyParent {} -// // function batchRedeem(bytes[] memory data, uint256[] memory amounts) external override {} +// // function batchRedeem(bytes[] memory data, uint256[] memory amounts) external override {} -// function exercise(bytes memory data, uint256 amount) external override {} +// function exercise(bytes memory data, uint256 amount) external override {} -// function reclaim(bytes memory data) external override {} +// function reclaim(bytes memory data) external override {} -// function convert(bytes memory data, uint256 amount) external override {} +// function convert(bytes memory data, uint256 amount) external override {} -// // ========== DERIVATIVE INFORMATION ========== // +// // ========== DERIVATIVE INFORMATION ========== // -// function exerciseCost(bytes memory data, uint256 amount) external view override returns (uint256) {} +// function exerciseCost(bytes memory data, uint256 amount) external view override returns (uint256) {} -// function convertsTo(bytes memory data, uint256 amount) external view override returns (uint256) {} +// function convertsTo(bytes memory data, uint256 amount) external view override returns (uint256) {} -// function derivativeForMarket(uint256 id_) external view override returns (bytes memory) {} +// function derivativeForMarket(uint256 id_) external view override returns (bytes memory) {} -// // ========== INTERNAL FUNCTIONS ========== // +// // ========== INTERNAL FUNCTIONS ========== // -// // unique to this submodule by using the hash of the params and then hashing again with the subkeycode -// function _computeId(ERC20 base_, uint48 expiry_) internal pure returns (uint256) { -// return uint256(keccak256( -// abi.encodePacked( -// SUBKEYCODE(), -// keccak256( -// abi.encode( -// base_, -// expiry_ -// ) +// // unique to this submodule by using the hash of the params and then hashing again with the subkeycode +// function _computeId(ERC20 base_, uint48 expiry_) internal pure returns (uint256) { +// return uint256(keccak256( +// abi.encodePacked( +// SUBKEYCODE(), +// keccak256( +// abi.encode( +// base_, +// expiry_ // ) // ) -// )); -// } +// ) +// )); +// } -// function _decodeAndNormalize(bytes memory params_) internal pure returns (ERC20 base, uint48 expiry) { -// (base, expiry) = abi.decode(params_, (ERC20, uint48)); +// function _decodeAndNormalize(bytes memory params_) internal pure returns (ERC20 base, uint48 expiry) { +// (base, expiry) = abi.decode(params_, (ERC20, uint48)); -// // Expiry is rounded to the nearest day at 0000 UTC (in seconds) since fixed expiry tokens -// // are only unique to a day, not a specific timestamp. -// expiry = uint48(expiry / 1 days) * 1 days; -// } +// // Expiry is rounded to the nearest day at 0000 UTC (in seconds) since fixed expiry tokens +// // are only unique to a day, not a specific timestamp. +// expiry = uint48(expiry / 1 days) * 1 days; +// } -// function computeId(bytes memory params_) external pure override returns (uint256) { -// (ERC20 base, uint48 expiry) = _decodeAndNormalize(params_); -// return _computeId(base, expiry); -// } +// function computeId(bytes memory params_) external pure override returns (uint256) { +// (ERC20 base, uint48 expiry) = _decodeAndNormalize(params_); +// return _computeId(base, expiry); +// } // }