From 06befd725a619654111e4d0a99570dcdd73dab21 Mon Sep 17 00:00:00 2001 From: jaketimothy Date: Mon, 7 Oct 2024 22:36:52 -0700 Subject: [PATCH 01/10] chore: ignore --- .gitignore | 1 + 1 file changed, 1 insertion(+) create mode 100644 .gitignore diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..e43b0f9 --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +.DS_Store From 34dfc7f6b3810c2dc6855a1fd95fc29016882ba1 Mon Sep 17 00:00:00 2001 From: jaketimothy Date: Tue, 8 Oct 2024 20:39:27 -0700 Subject: [PATCH 02/10] feat: ComponentToken public view methods for inheritance --- nest/src/ComponentToken.sol | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/nest/src/ComponentToken.sol b/nest/src/ComponentToken.sol index 81ebe1b..832e67f 100644 --- a/nest/src/ComponentToken.sol +++ b/nest/src/ComponentToken.sol @@ -371,22 +371,22 @@ abstract contract ComponentToken is } /// @notice CurrencyToken used to buy and sell the ComponentToken - function getCurrencyToken() external view returns (IERC20) { + function getCurrencyToken() public view returns (IERC20) { return _getComponentTokenStorage().currencyToken; } /// @notice Total yield distributed to the ComponentToken for all users - function totalYield() external view returns (uint256 amount) { + function totalYield() public view returns (uint256 amount) { return _getComponentTokenStorage().totalYieldAccrued; } /// @notice Claimed yield across the ComponentToken for all users - function claimedYield() external view returns (uint256 amount) { + function claimedYield() public view returns (uint256 amount) { return _getComponentTokenStorage().totalYieldWithdrawn; } /// @notice Unclaimed yield across the ComponentToken for all users - function unclaimedYield() external view returns (uint256 amount) { + function unclaimedYield() public view returns (uint256 amount) { ComponentTokenStorage storage $ = _getComponentTokenStorage(); return $.totalYieldAccrued - $.totalYieldWithdrawn; } @@ -396,7 +396,7 @@ abstract contract ComponentToken is * @param user Address of the user for which to get the total yield * @return amount Total yield distributed to the user */ - function totalYield(address user) external view returns (uint256 amount) { + function totalYield(address user) public view returns (uint256 amount) { return _getComponentTokenStorage().yieldAccrued[user]; } @@ -405,7 +405,7 @@ abstract contract ComponentToken is * @param user Address of the user for which to get the claimed yield * @return amount Amount of yield that the user has claimed */ - function claimedYield(address user) external view returns (uint256 amount) { + function claimedYield(address user) public view returns (uint256 amount) { return _getComponentTokenStorage().yieldWithdrawn[user]; } From b35aba5efffd09688958f8853955d37368b05f2c Mon Sep 17 00:00:00 2001 From: jaketimothy Date: Tue, 8 Oct 2024 20:39:53 -0700 Subject: [PATCH 03/10] feat: add external Dinari IOrderProcessor --- nest/src/token/external/IOrderProcessor.sol | 227 ++++++++++++++++++++ 1 file changed, 227 insertions(+) create mode 100644 nest/src/token/external/IOrderProcessor.sol diff --git a/nest/src/token/external/IOrderProcessor.sol b/nest/src/token/external/IOrderProcessor.sol new file mode 100644 index 0000000..ebf2a65 --- /dev/null +++ b/nest/src/token/external/IOrderProcessor.sol @@ -0,0 +1,227 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.23; + +/// @notice Interface for contracts processing orders for dShares +/// @author Dinari (https://github.com/dinaricrypto/sbt-contracts/blob/main/src/orders/IOrderProcessor.sol) +/// This interface provides a standard Order type and order lifecycle events +/// Orders are requested on-chain, processed off-chain, then fulfillment is submitted for on-chain settlement +interface IOrderProcessor { + /// ------------------ Types ------------------ /// + + // Market or limit order + enum OrderType { + MARKET, + LIMIT + } + + // Time in force + enum TIF { + // Good until end of day + DAY, + // Good until cancelled + GTC, + // Immediate or cancel + IOC, + // Fill or kill + FOK + } + + // Order status enum + enum OrderStatus { + // Order has never existed + NONE, + // Order is active + ACTIVE, + // Order is completely filled + FULFILLED, + // Order is cancelled + CANCELLED + } + + struct Order { + // Timestamp or other salt added to order hash for replay protection + uint64 requestTimestamp; + // Recipient of order fills + address recipient; + // Bridged asset token + address assetToken; + // Payment token + address paymentToken; + // Buy or sell + bool sell; + // Market or limit + OrderType orderType; + // Amount of asset token to be used for fills + uint256 assetTokenQuantity; + // Amount of payment token to be used for fills + uint256 paymentTokenQuantity; + // Price for limit orders in ether decimals + uint256 price; + // Time in force + TIF tif; + } + + struct OrderRequest { + // Unique ID and hash of order data used to validate order details stored offchain + uint256 orderId; + // Signature expiration timestamp + uint64 deadline; + } + + struct Signature { + // Signature expiration timestamp + uint64 deadline; + // Signature bytes (r, s, v) + bytes signature; + } + + struct FeeQuote { + // Unique ID and hash of order data used to validate order details stored offchain + uint256 orderId; + // Requester of order + address requester; + // Fee amount in payment token + uint256 fee; + // Timestamp of fee quote + uint64 timestamp; + // Signature expiration timestamp + uint64 deadline; + } + + struct PricePoint { + // Price specified with 18 decimals + uint256 price; + uint64 blocktime; + } + + /// @dev Emitted order details and order ID for each order + event OrderCreated(uint256 indexed id, address indexed requester, Order order, uint256 feesEscrowed); + /// @dev Emitted for each fill + event OrderFill( + uint256 indexed id, + address indexed paymentToken, + address indexed assetToken, + address requester, + uint256 assetAmount, + uint256 paymentAmount, + uint256 feesTaken, + bool sell + ); + /// @dev Emitted when order is completely filled, terminal + event OrderFulfilled(uint256 indexed id, address indexed requester); + /// @dev Emitted when order cancellation is requested + event CancelRequested(uint256 indexed id, address indexed requester); + /// @dev Emitted when order is cancelled, terminal + event OrderCancelled(uint256 indexed id, address indexed requester, string reason); + + /// ------------------ Getters ------------------ /// + + /// @notice Hash order data for validation and create unique order ID + /// @param order Order data + /// @dev EIP-712 typed data hash of order + function hashOrder(Order calldata order) external pure returns (uint256); + + /// @notice Status of a given order + /// @param id Order ID + function getOrderStatus(uint256 id) external view returns (OrderStatus); + + /// @notice Get remaining order quantity to fill + /// @param id Order ID + function getUnfilledAmount(uint256 id) external view returns (uint256); + + /// @notice Get received amount for an order + /// @param id Order ID + function getReceivedAmount(uint256 id) external view returns (uint256); + + /// @notice Get fees in payment token escrowed for a buy order + /// @param id Order ID + function getFeesEscrowed(uint256 id) external view returns (uint256); + + /// @notice Get cumulative payment token fees taken for an order + /// @param id Order ID + /// @dev Only valid for ACTIVE orders + function getFeesTaken(uint256 id) external view returns (uint256); + + /// @notice Reduces the precision allowed for the asset token quantity of an order + /// @param token The address of the token + function orderDecimalReduction(address token) external view returns (uint8); + + /// @notice Get worst case fees for an order + /// @param sell Sell order + /// @param paymentToken Payment token for order + /// @return flatFee Flat fee for order + /// @return percentageFeeRate Percentage fee rate for order + function getStandardFees(bool sell, address paymentToken) external view returns (uint256, uint24); + + /// @notice Get total standard fees for an order + /// @param sell Sell order + /// @param paymentToken Payment token for order + /// @param paymentTokenQuantity Payment token quantity for order + function totalStandardFee(bool sell, address paymentToken, uint256 paymentTokenQuantity) + external + view + returns (uint256); + + /// @notice Check if an account is locked from transferring tokens + /// @param token Token to check + /// @param account Account to check + /// @dev Only used for payment tokens + function isTransferLocked(address token, address account) external view returns (bool); + + /// @notice Get the latest fill price for a token pair + /// @param assetToken Asset token + /// @param paymentToken Payment token + /// @dev price specified with 18 decimals + function latestFillPrice(address assetToken, address paymentToken) external view returns (PricePoint memory); + + /// ------------------ Actions ------------------ /// + + /// @notice Lock tokens and initialize signed order + /// @param order Order request to initialize + /// @param orderSignature Signature and deadline for order + /// @param feeQuote Fee quote for order + /// @param feeQuoteSignature Signature for fee quote + /// @return id Order id + /// @dev Only callable by operator + function createOrderWithSignature( + Order calldata order, + Signature calldata orderSignature, + FeeQuote calldata feeQuote, + bytes calldata feeQuoteSignature + ) external returns (uint256); + + /// @notice Request an order + /// @param order Order request to submit + /// @param feeQuote Fee quote for order + /// @param feeQuoteSignature Signature for fee quote + /// @return id Order id + /// @dev Emits OrderCreated event to be sent to fulfillment service (operator) + function createOrder(Order calldata order, FeeQuote calldata feeQuote, bytes calldata feeQuoteSignature) + external + returns (uint256); + + /// @notice Request an order with standard fees + /// @param order Order request to submit + /// @return id Order id + /// @dev Emits OrderCreated event to be sent to fulfillment service (operator) + function createOrderStandardFees(Order calldata order) external returns (uint256); + + /// @notice Fill an order + /// @param order Order request to fill + /// @param fillAmount Amount of order token to fill + /// @param receivedAmount Amount of received token + /// @dev Only callable by operator + function fillOrder(Order calldata order, uint256 fillAmount, uint256 receivedAmount, uint256 fees) external; + + /// @notice Request to cancel an order + /// @param id Order id + /// @dev Only callable by initial order requester + /// @dev Emits CancelRequested event to be sent to fulfillment service (operator) + function requestCancel(uint256 id) external; + + /// @notice Cancel an order + /// @param order Order request to cancel + /// @param reason Reason for cancellation + /// @dev Only callable by operator + function cancelOrder(Order calldata order, string calldata reason) external; +} From 37e75dd4693ebd90526aae1378b222256788c8a5 Mon Sep 17 00:00:00 2001 From: jaketimothy Date: Tue, 8 Oct 2024 20:47:20 -0700 Subject: [PATCH 04/10] feat: restore external view methods, use internal storage --- nest/src/ComponentToken.sol | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/nest/src/ComponentToken.sol b/nest/src/ComponentToken.sol index 832e67f..81ebe1b 100644 --- a/nest/src/ComponentToken.sol +++ b/nest/src/ComponentToken.sol @@ -371,22 +371,22 @@ abstract contract ComponentToken is } /// @notice CurrencyToken used to buy and sell the ComponentToken - function getCurrencyToken() public view returns (IERC20) { + function getCurrencyToken() external view returns (IERC20) { return _getComponentTokenStorage().currencyToken; } /// @notice Total yield distributed to the ComponentToken for all users - function totalYield() public view returns (uint256 amount) { + function totalYield() external view returns (uint256 amount) { return _getComponentTokenStorage().totalYieldAccrued; } /// @notice Claimed yield across the ComponentToken for all users - function claimedYield() public view returns (uint256 amount) { + function claimedYield() external view returns (uint256 amount) { return _getComponentTokenStorage().totalYieldWithdrawn; } /// @notice Unclaimed yield across the ComponentToken for all users - function unclaimedYield() public view returns (uint256 amount) { + function unclaimedYield() external view returns (uint256 amount) { ComponentTokenStorage storage $ = _getComponentTokenStorage(); return $.totalYieldAccrued - $.totalYieldWithdrawn; } @@ -396,7 +396,7 @@ abstract contract ComponentToken is * @param user Address of the user for which to get the total yield * @return amount Total yield distributed to the user */ - function totalYield(address user) public view returns (uint256 amount) { + function totalYield(address user) external view returns (uint256 amount) { return _getComponentTokenStorage().yieldAccrued[user]; } @@ -405,7 +405,7 @@ abstract contract ComponentToken is * @param user Address of the user for which to get the claimed yield * @return amount Amount of yield that the user has claimed */ - function claimedYield(address user) public view returns (uint256 amount) { + function claimedYield(address user) external view returns (uint256 amount) { return _getComponentTokenStorage().yieldWithdrawn[user]; } From 6f1400a54611538a2647c747f17ee9c7ee8951f4 Mon Sep 17 00:00:00 2001 From: jaketimothy Date: Tue, 8 Oct 2024 21:38:03 -0700 Subject: [PATCH 05/10] feat: mockup dshare order flow --- nest/src/token/DinariToken.sol | 143 +++++++++++++++++++++------------ 1 file changed, 92 insertions(+), 51 deletions(-) diff --git a/nest/src/token/DinariToken.sol b/nest/src/token/DinariToken.sol index a3268dc..ed2f7f4 100644 --- a/nest/src/token/DinariToken.sol +++ b/nest/src/token/DinariToken.sol @@ -7,6 +7,7 @@ import { ERC20Upgradeable } from "@openzeppelin/contracts-upgradeable/token/ERC2 import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; import { ComponentToken } from "../ComponentToken.sol"; +import { IOrderProcessor } from "./external/IOrderProcessor.sol"; /// @notice Example of an interface for the Nest Staking contract interface IAggregateToken { @@ -28,33 +29,26 @@ interface IAggregateToken { } -/// @notice Example of an interface for the external contract that manages the external asset -interface IExternalContract { - - /// @notice Notify the external contract that a buy has been requested - function requestBuy(uint256 currencyTokenAmount, uint256 requestId) external; - /// @notice Notify the external contract that a sell has been requested - function requestSell(uint256 componentTokenAmount, uint256 requestId) external; - -} - /** * @title DinariToken * @author Jake Timothy, Eugene Y. Q. Shen * @notice Implementation of the abstract ComponentToken that interfaces with external assets. */ contract DinariToken is ComponentToken { + // TODO: name - NestDinariVault? // Storage /// @custom:storage-location erc7201:plume.storage.DinariToken struct DinariTokenStorage { + /// @dev dShare token underlying component token + address dshareToken; /// @dev Address of the Nest Staking contract - IAggregateToken nestStakingContract; - /// @dev Address of the external contract that manages the external asset - IExternalContract externalContract; - /// @dev Mapping from request IDs to external request UUIDs - mapping(uint256 requestId => bytes16 externalUuid) requestMap; + address nestStakingContract; + /// @dev Address of the dShares order contract + IOrderProcessor externalOrderContract; + /// @dev Mapping from request IDs to external request IDs + mapping(uint256 requestId => uint256 externalId) requestMap; } // keccak256(abi.encode(uint256(keccak256("plume.storage.DinariToken")) - 1)) & ~bytes32(uint256(0xff)) @@ -76,6 +70,8 @@ contract DinariToken is ComponentToken { */ error Unauthorized(address invalidCaller, address caller); + error OrderNotFilled(); + // Initializer /** @@ -92,23 +88,26 @@ contract DinariToken is ComponentToken { * @param name Name of the DinariToken * @param symbol Symbol of the DinariToken * @param currencyToken CurrencyToken used to mint and burn the DinariToken + * @param dshareToken dShare token underlying component token * @param decimals_ Number of decimals of the DinariToken * @param nestStakingContract Address of the Nest Staking contract - * @param externalContract Address of the external contract that manages the external asset + * @param externalOrderContract Address of the dShares order contract */ function initialize( address owner, string memory name, string memory symbol, - IERC20 currencyToken, + address currencyToken, + address dshareToken, uint8 decimals_, - IAggregateToken nestStakingContract, - IExternalContract externalContract + address nestStakingContract, + address externalOrderContract ) public initializer { - super.initialize(owner, name, symbol, currencyToken, decimals_); + super.initialize(owner, name, symbol, IERC20(currencyToken), decimals_); DinariTokenStorage storage $ = _getDinariTokenStorage(); + $.dshareToken = dshareToken; $.nestStakingContract = nestStakingContract; - $.externalContract = externalContract; + $.externalOrderContract = IOrderProcessor(externalOrderContract); } // Override Functions @@ -120,11 +119,32 @@ contract DinariToken is ComponentToken { */ function requestBuy(uint256 currencyTokenAmount) public override(ComponentToken) returns (uint256 requestId) { DinariTokenStorage storage $ = _getDinariTokenStorage(); - if (msg.sender != address($.nestStakingContract)) { - revert Unauthorized(msg.sender, address($.nestStakingContract)); + address nestStakingContract = $.nestStakingContract; + if (msg.sender != nestStakingContract) { + revert Unauthorized(msg.sender, nestStakingContract); } requestId = super.requestBuy(currencyTokenAmount); - $.externalContract.requestBuy(currencyTokenAmount, requestId); + + IOrderProcessor orderContract = $.externalOrderContract; + address paymentToken = _getComponentTokenStorage().currencyToken; + uint256 addedFees = orderContract.totalStandardFee(false, paymentToken, currencyTokenAmount); + // Does not spend all currencyTokenAmount, collect unspent in executeBuy if needed + uint256 paymentTokenQuantity = currencyTokenAmount - addedFees; + // TODO: round down to nearest supported decimal + + IOrderProcessor.Order memory order = IOrderProcessor.Order({ + requestTimestamp: block.timestamp, + recipient: address(this), + assetToken: $.dshareToken, + paymentToken: paymentToken, + sell: false, + orderType: IOrderProcessor.OrderType.MARKET, + assetTokenQuantity: 0, + paymentTokenQuantity: paymentTokenQuantity, + price: 0, + tif: IOrderProcessor.TIF.DAY + }); + $.requestMap[requestId] = $.externalOrderContract.createOrderStandardFees(order); } /** @@ -134,11 +154,25 @@ contract DinariToken is ComponentToken { */ function requestSell(uint256 componentTokenAmount) public override(ComponentToken) returns (uint256 requestId) { DinariTokenStorage storage $ = _getDinariTokenStorage(); - if (msg.sender != address($.nestStakingContract)) { - revert Unauthorized(msg.sender, address($.nestStakingContract)); + address nestStakingContract = $.nestStakingContract; + if (msg.sender != nestStakingContract) { + revert Unauthorized(msg.sender, nestStakingContract); } requestId = super.requestSell(componentTokenAmount); - $.externalContract.requestSell(componentTokenAmount, requestId); + + IOrderProcessor.Order memory order = IOrderProcessor.Order({ + requestTimestamp: block.timestamp, + recipient: address(this), + assetToken: $.dshareToken, + paymentToken: _getComponentTokenStorage().currencyToken, + sell: true, + orderType: IOrderProcessor.OrderType.MARKET, + assetTokenQuantity: componentTokenAmount, + paymentTokenQuantity: 0, + price: 0, + tif: IOrderProcessor.TIF.DAY + }); + $.requestMap[requestId] = $.externalOrderContract.createOrderStandardFees(order); } /** @@ -147,23 +181,27 @@ contract DinariToken is ComponentToken { * @param requestId Unique identifier for the request * @param currencyTokenAmount Amount of CurrencyToken to send * @param componentTokenAmount Amount of ComponentToken to receive + * @dev Dshare order fulfillment does not spend all tokens; called by external keeper */ function executeBuy( - address requestor, + address , uint256 requestId, uint256 currencyTokenAmount, - uint256 componentTokenAmount + uint256 ) public override(ComponentToken) { + // Restrict caller? DinariTokenStorage storage $ = _getDinariTokenStorage(); - if (msg.sender != address($.nestStakingContract)) { - revert Unauthorized(requestor, address($.nestStakingContract)); - } - if (msg.sender != address($.externalContract)) { - revert Unauthorized(msg.sender, address($.externalContract)); - } - super.executeBuy(address($.nestStakingContract), requestId, currencyTokenAmount, componentTokenAmount); - $.nestStakingContract.notifyBuy( - _getComponentTokenStorage().currencyToken, this, currencyTokenAmount, componentTokenAmount + + IOrderProcessor orderContract = $.externalOrderContract; + uint256 externalId = $.requestMap[requestId]; + if (orderContract.getOrderStatus(externalId) != IOrderProcessor.OrderStatus.FULFILLED) revert OrderNotFilled(); + + // TODO: handle partial refunds + uint256 proceeds = orderContract.getReceivedAmount(externalId); + address nestStakingContract = $.nestStakingContract; + super.executeBuy(nestStakingContract, requestId, currencyTokenAmount, proceeds); + IAggregateToken(nestStakingContract).notifyBuy( + _getComponentTokenStorage().currencyToken, this, currencyTokenAmount, proceeds ); } @@ -175,21 +213,23 @@ contract DinariToken is ComponentToken { * @param componentTokenAmount Amount of ComponentToken to send */ function executeSell( - address requestor, + address , uint256 requestId, - uint256 currencyTokenAmount, + uint256 , uint256 componentTokenAmount ) public override(ComponentToken) { + // Restrict caller? DinariTokenStorage storage $ = _getDinariTokenStorage(); - if (requestor != address($.nestStakingContract)) { - revert Unauthorized(requestor, address($.nestStakingContract)); - } - if (msg.sender != address($.externalContract)) { - revert Unauthorized(msg.sender, address($.externalContract)); - } - super.executeSell(address($.nestStakingContract), requestId, currencyTokenAmount, componentTokenAmount); - $.nestStakingContract.notifySell( - _getComponentTokenStorage().currencyToken, this, currencyTokenAmount, componentTokenAmount + + IOrderProcessor orderContract = $.externalOrderContract; + uint256 externalId = $.requestMap[requestId]; + if (orderContract.getOrderStatus(externalId) != IOrderProcessor.OrderStatus.FULFILLED) revert OrderNotFilled(); + + uint256 proceeds = orderContract.getReceivedAmount(externalId); + address nestStakingContract = $.nestStakingContract; + super.executeSell(nestStakingContract, requestId, proceeds, componentTokenAmount); + IAggregateToken(nestStakingContract).notifySell( + _getComponentTokenStorage().currencyToken, this, proceeds, componentTokenAmount ); } @@ -197,8 +237,9 @@ contract DinariToken is ComponentToken { function distributeYield(address user, uint256 amount) external { DinariTokenStorage storage $ = _getDinariTokenStorage(); - if (msg.sender != address($.nestStakingContract)) { - revert Unauthorized(msg.sender, address($.nestStakingContract)); + address nestStakingContract = $.nestStakingContract; + if (msg.sender != nestStakingContract) { + revert Unauthorized(msg.sender, nestStakingContract); } ComponentTokenStorage storage cs = _getComponentTokenStorage(); From b008cd6e3c0b1bbabfcb70ae5a983253e34c4a14 Mon Sep 17 00:00:00 2001 From: jaketimothy Date: Fri, 18 Oct 2024 20:12:25 -0300 Subject: [PATCH 06/10] feat: erc7540 dinari adapter token --- nest/src/token/AdapterToken.sol | 29 ++- nest/src/token/DinariToken.sol | 201 ++++++++++---------- nest/src/token/external/IOrderProcessor.sol | 19 +- 3 files changed, 132 insertions(+), 117 deletions(-) diff --git a/nest/src/token/AdapterToken.sol b/nest/src/token/AdapterToken.sol index 25d80c1..c136539 100644 --- a/nest/src/token/AdapterToken.sol +++ b/nest/src/token/AdapterToken.sol @@ -5,8 +5,9 @@ import { IERC20 } from "@openzeppelin/contracts/interfaces/IERC20.sol"; import { IERC4626 } from "@openzeppelin/contracts/interfaces/IERC4626.sol"; import { ComponentToken } from "../ComponentToken.sol"; -import { IComponentToken } from "../interfaces/IComponentToken.sol"; + import { IAggregateToken } from "../interfaces/IAggregateToken.sol"; +import { IComponentToken } from "../interfaces/IComponentToken.sol"; /// @notice Example of an interface for the external contract that manages the external asset interface IExternalContract { @@ -95,7 +96,11 @@ contract USDT is ComponentToken { } /// @inheritdoc IComponentToken - function requestDeposit(uint256 assets, address controller, address owner) public override(ComponentToken) returns (uint256 requestId) { + function requestDeposit( + uint256 assets, + address controller, + address owner + ) public override(ComponentToken) returns (uint256 requestId) { AdapterTokenStorage storage $ = _getAdapterTokenStorage(); if (msg.sender != address($.nestStakingContract)) { revert Unauthorized(msg.sender, address($.nestStakingContract)); @@ -105,7 +110,11 @@ contract USDT is ComponentToken { } /// @inheritdoc IComponentToken - function requestRedeem(uint256 shares, address controller, address owner) public override(ComponentToken) returns (uint256 requestId) { + function requestRedeem( + uint256 shares, + address controller, + address owner + ) public override(ComponentToken) returns (uint256 requestId) { AdapterTokenStorage storage $ = _getAdapterTokenStorage(); if (msg.sender != address($.nestStakingContract)) { revert Unauthorized(msg.sender, address($.nestStakingContract)); @@ -115,7 +124,11 @@ contract USDT is ComponentToken { } /// @inheritdoc IComponentToken - function deposit(uint256 assets, address receiver, address controller) public override(ComponentToken) returns (uint256 shares) { + function deposit( + uint256 assets, + address receiver, + address controller + ) public override(ComponentToken) returns (uint256 shares) { AdapterTokenStorage storage $ = _getAdapterTokenStorage(); if (msg.sender != address($.externalContract)) { revert Unauthorized(msg.sender, address($.externalContract)); @@ -127,7 +140,11 @@ contract USDT is ComponentToken { } /// @inheritdoc IComponentToken - function redeem(uint256 shares, address receiver, address controller) public override(ComponentToken) returns (uint256 assets) { + function redeem( + uint256 shares, + address receiver, + address controller + ) public override(ComponentToken) returns (uint256 assets) { AdapterTokenStorage storage $ = _getAdapterTokenStorage(); if (msg.sender != address($.externalContract)) { revert Unauthorized(msg.sender, address($.externalContract)); @@ -138,4 +155,4 @@ contract USDT is ComponentToken { return super.redeem(shares, receiver, controller); } -} \ No newline at end of file +} diff --git a/nest/src/token/DinariToken.sol b/nest/src/token/DinariToken.sol index ed2f7f4..09c467d 100644 --- a/nest/src/token/DinariToken.sol +++ b/nest/src/token/DinariToken.sol @@ -4,37 +4,22 @@ import { AccessControlUpgradeable } from "@openzeppelin/contracts-upgradeable/ac import { Initializable } from "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol"; import { UUPSUpgradeable } from "@openzeppelin/contracts-upgradeable/proxy/utils/UUPSUpgradeable.sol"; import { ERC20Upgradeable } from "@openzeppelin/contracts-upgradeable/token/ERC20/ERC20Upgradeable.sol"; + +import { IERC4626 } from "@openzeppelin/contracts/interfaces/IERC4626.sol"; import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; import { ComponentToken } from "../ComponentToken.sol"; +import { IAggregateToken } from "../interfaces/IAggregateToken.sol"; import { IOrderProcessor } from "./external/IOrderProcessor.sol"; -/// @notice Example of an interface for the Nest Staking contract -interface IAggregateToken { - - /// @notice Notify the Nest Staking contract that a buy has been executed - function notifyBuy( - IERC20 currencyToken, - IERC20 componentToken, - uint256 currencyTokenAmount, - uint256 componentTokenAmount - ) external; - /// @notice Notify the Nest Staking contract that a sell has been executed - function notifySell( - IERC20 currencyToken, - IERC20 componentToken, - uint256 currencyTokenAmount, - uint256 componentTokenAmount - ) external; - -} - /** * @title DinariToken * @author Jake Timothy, Eugene Y. Q. Shen * @notice Implementation of the abstract ComponentToken that interfaces with external assets. + * @dev Assets is USDC */ contract DinariToken is ComponentToken { + // TODO: name - NestDinariVault? // Storage @@ -43,12 +28,16 @@ contract DinariToken is ComponentToken { struct DinariTokenStorage { /// @dev dShare token underlying component token address dshareToken; + /// @dev Wrapped dShare token underlying component token + address wrappedDshareToken; /// @dev Address of the Nest Staking contract address nestStakingContract; /// @dev Address of the dShares order contract IOrderProcessor externalOrderContract; /// @dev Mapping from request IDs to external request IDs mapping(uint256 requestId => uint256 externalId) requestMap; + // TODO: rename + mapping(uint256 externalId => uint256 amountIn) orderInputAmounts; } // keccak256(abi.encode(uint256(keccak256("plume.storage.DinariToken")) - 1)) & ~bytes32(uint256(0xff)) @@ -70,8 +59,6 @@ contract DinariToken is ComponentToken { */ error Unauthorized(address invalidCaller, address caller); - error OrderNotFilled(); - // Initializer /** @@ -99,6 +86,7 @@ contract DinariToken is ComponentToken { string memory symbol, address currencyToken, address dshareToken, + address wrappedDshareToken, uint8 decimals_, address nestStakingContract, address externalOrderContract @@ -106,30 +94,50 @@ contract DinariToken is ComponentToken { super.initialize(owner, name, symbol, IERC20(currencyToken), decimals_); DinariTokenStorage storage $ = _getDinariTokenStorage(); $.dshareToken = dshareToken; + $.wrappedDshareToken = wrappedDshareToken; $.nestStakingContract = nestStakingContract; $.externalOrderContract = IOrderProcessor(externalOrderContract); } // Override Functions - /** - * @notice Submit a request to send currencyTokenAmount of CurrencyToken to buy ComponentToken - * @param currencyTokenAmount Amount of CurrencyToken to send - * @return requestId Unique identifier for the buy request - */ - function requestBuy(uint256 currencyTokenAmount) public override(ComponentToken) returns (uint256 requestId) { + /// @inheritdoc IERC4626 + function convertToShares(uint256 assets) public view override(ComponentToken) returns (uint256 shares) { + // Apply dshare price and wrapped conversion rate + DinariTokenStorage storage $ = _getDinariTokenStorage(); + IOrderProcessor orderContract = $.externalOrderContract; + IOrderProcessor.PricePoint memory price = orderContract.latestFillPrice($.dshareToken, $.currencyToken); + return IERC4626($.wrappedDshareToken).convertToShares((assets * price.price) / 1 ether); + } + + /// @inheritdoc IERC4626 + function convertToAssets(uint256 shares) public view override(ComponentToken) returns (uint256 assets) { + // Apply wrapped conversion rate and dshare price + DinariTokenStorage storage $ = _getDinariTokenStorage(); + IOrderProcessor orderContract = $.externalOrderContract; + IOrderProcessor.PricePoint memory price = orderContract.latestFillPrice($.dshareToken, $.currencyToken); + return (IERC4626($.wrappedDshareToken).convertToAssets(shares) * 1 ether) / price.price; + } + + /// @inheritdoc IComponentToken + function requestDeposit( + uint256 assets, + address controller, + address owner + ) public override(ComponentToken) returns (uint256 requestId) { DinariTokenStorage storage $ = _getDinariTokenStorage(); address nestStakingContract = $.nestStakingContract; if (msg.sender != nestStakingContract) { revert Unauthorized(msg.sender, nestStakingContract); } - requestId = super.requestBuy(currencyTokenAmount); + requestId = super.requestBuy(assets); IOrderProcessor orderContract = $.externalOrderContract; address paymentToken = _getComponentTokenStorage().currencyToken; - uint256 addedFees = orderContract.totalStandardFee(false, paymentToken, currencyTokenAmount); - // Does not spend all currencyTokenAmount, collect unspent in executeBuy if needed - uint256 paymentTokenQuantity = currencyTokenAmount - addedFees; + uint256 addedFees = orderContract.totalStandardFee(false, paymentToken, assets); + // Does not spend all assets, collect unspent in executeBuy if needed + // FIXME: make more precise? This does not spend all money and does not correspond to convertToShares + uint256 paymentTokenQuantity = assets - addedFees; // TODO: round down to nearest supported decimal IOrderProcessor.Order memory order = IOrderProcessor.Order({ @@ -144,22 +152,62 @@ contract DinariToken is ComponentToken { price: 0, tif: IOrderProcessor.TIF.DAY }); - $.requestMap[requestId] = $.externalOrderContract.createOrderStandardFees(order); + uint256 orderId = $.externalOrderContract.createOrderStandardFees(order); + $.requestMap[requestId] = orderId; + $.orderInputAmounts[orderId] = paymentTokenQuantity; } - /** - * @notice Submit a request to send componentTokenAmount of ComponentToken to sell for CurrencyToken - * @param componentTokenAmount Amount of ComponentToken to send - * @return requestId Unique identifier for the sell request - */ - function requestSell(uint256 componentTokenAmount) public override(ComponentToken) returns (uint256 requestId) { + function notifyBuy(uint256 externalId) public { + // Restrict caller? + DinariTokenStorage storage $ = _getDinariTokenStorage(); + + IOrderProcessor orderContract = $.externalOrderContract; + if (orderContract.getOrderStatus(externalId) != IOrderProcessor.OrderStatus.FULFILLED) { + revert OrderNotFilled(); + } + + uint256 proceeds = orderContract.getReceivedAmount(externalId); + address nestStakingContract = $.nestStakingContract; + // TODO: handle partial refunds, verify assets amount + uint256 totalSpent = $.orderInputAmounts[externalId] + orderContract.getFeesTaken(externalId); + super.notifyDeposit(totalSpent, proceeds, nestStakingContract); + } + + /// @inheritdoc IComponentToken + function deposit( + uint256 assets, + address receiver, + address controller + ) public override(ComponentToken) returns (uint256 shares) { + AdapterTokenStorage storage $ = _getAdapterTokenStorage(); + if (msg.sender != address($.externalContract)) { + revert Unauthorized(msg.sender, address($.externalContract)); + } + if (receiver != address($.nestStakingContract)) { + revert Unauthorized(receiver, address($.nestStakingContract)); + } + return super.deposit(assets, receiver, controller); + } + + /// @inheritdoc IComponentToken + function requestRedeem( + uint256 shares, + address controller, + address owner + ) public override(ComponentToken) returns (uint256 requestId) { DinariTokenStorage storage $ = _getDinariTokenStorage(); address nestStakingContract = $.nestStakingContract; if (msg.sender != nestStakingContract) { revert Unauthorized(msg.sender, nestStakingContract); } - requestId = super.requestSell(componentTokenAmount); + requestId = super.requestSell(shares); + // Unwrap dshares + uint256 dshareAmount = IERC4626($.wrappedDshareToken).redeem(shares); + // Approve dshares + IOrderProcessor orderContract = $.externalOrderContract; + IERC20($.dshareToken).approve(address(orderContract), dshareAmount); + // Sell IOrderProcessor.Order memory order = IOrderProcessor.Order({ requestTimestamp: block.timestamp, recipient: address(this), @@ -167,84 +215,29 @@ contract DinariToken is ComponentToken { paymentToken: _getComponentTokenStorage().currencyToken, sell: true, orderType: IOrderProcessor.OrderType.MARKET, - assetTokenQuantity: componentTokenAmount, + assetTokenQuantity: dshareAmount, paymentTokenQuantity: 0, price: 0, tif: IOrderProcessor.TIF.DAY }); - $.requestMap[requestId] = $.externalOrderContract.createOrderStandardFees(order); - } - - /** - * @notice Executes a request to buy ComponentToken with CurrencyToken - * @param requestor Address of the user or smart contract that requested the buy - * @param requestId Unique identifier for the request - * @param currencyTokenAmount Amount of CurrencyToken to send - * @param componentTokenAmount Amount of ComponentToken to receive - * @dev Dshare order fulfillment does not spend all tokens; called by external keeper - */ - function executeBuy( - address , - uint256 requestId, - uint256 currencyTokenAmount, - uint256 - ) public override(ComponentToken) { - // Restrict caller? - DinariTokenStorage storage $ = _getDinariTokenStorage(); - - IOrderProcessor orderContract = $.externalOrderContract; - uint256 externalId = $.requestMap[requestId]; - if (orderContract.getOrderStatus(externalId) != IOrderProcessor.OrderStatus.FULFILLED) revert OrderNotFilled(); - - // TODO: handle partial refunds - uint256 proceeds = orderContract.getReceivedAmount(externalId); - address nestStakingContract = $.nestStakingContract; - super.executeBuy(nestStakingContract, requestId, currencyTokenAmount, proceeds); - IAggregateToken(nestStakingContract).notifyBuy( - _getComponentTokenStorage().currencyToken, this, currencyTokenAmount, proceeds - ); + uint256 orderId = orderContract.createOrderStandardFees(order); + $.requestMap[requestId] = orderId; + $.orderInputAmounts[orderId] = shares; } - /** - * @notice Executes a request to sell ComponentToken for CurrencyToken - * @param requestor Address of the user or smart contract that requested the sell - * @param requestId Unique identifier for the request - * @param currencyTokenAmount Amount of CurrencyToken to receive - * @param componentTokenAmount Amount of ComponentToken to send - */ - function executeSell( - address , - uint256 requestId, - uint256 , - uint256 componentTokenAmount - ) public override(ComponentToken) { + function notifySell(uint256 externalId) public { // Restrict caller? DinariTokenStorage storage $ = _getDinariTokenStorage(); IOrderProcessor orderContract = $.externalOrderContract; uint256 externalId = $.requestMap[requestId]; - if (orderContract.getOrderStatus(externalId) != IOrderProcessor.OrderStatus.FULFILLED) revert OrderNotFilled(); + if (orderContract.getOrderStatus(externalId) != IOrderProcessor.OrderStatus.FULFILLED) { + revert OrderNotFilled(); + } uint256 proceeds = orderContract.getReceivedAmount(externalId); address nestStakingContract = $.nestStakingContract; - super.executeSell(nestStakingContract, requestId, proceeds, componentTokenAmount); - IAggregateToken(nestStakingContract).notifySell( - _getComponentTokenStorage().currencyToken, this, proceeds, componentTokenAmount - ); - } - - // Admin Functions - - function distributeYield(address user, uint256 amount) external { - DinariTokenStorage storage $ = _getDinariTokenStorage(); - address nestStakingContract = $.nestStakingContract; - if (msg.sender != nestStakingContract) { - revert Unauthorized(msg.sender, nestStakingContract); - } - - ComponentTokenStorage storage cs = _getComponentTokenStorage(); - cs.currencyToken.transfer(user, amount); - cs.yieldAccrued[user] += amount; + super.notifyRedeem(proceeds, $.orderInputAmounts[externalId], nestStakingContract); } } diff --git a/nest/src/token/external/IOrderProcessor.sol b/nest/src/token/external/IOrderProcessor.sol index ebf2a65..e5d5239 100644 --- a/nest/src/token/external/IOrderProcessor.sol +++ b/nest/src/token/external/IOrderProcessor.sol @@ -6,6 +6,7 @@ pragma solidity ^0.8.23; /// This interface provides a standard Order type and order lifecycle events /// Orders are requested on-chain, processed off-chain, then fulfillment is submitted for on-chain settlement interface IOrderProcessor { + /// ------------------ Types ------------------ /// // Market or limit order @@ -157,10 +158,11 @@ interface IOrderProcessor { /// @param sell Sell order /// @param paymentToken Payment token for order /// @param paymentTokenQuantity Payment token quantity for order - function totalStandardFee(bool sell, address paymentToken, uint256 paymentTokenQuantity) - external - view - returns (uint256); + function totalStandardFee( + bool sell, + address paymentToken, + uint256 paymentTokenQuantity + ) external view returns (uint256); /// @notice Check if an account is locked from transferring tokens /// @param token Token to check @@ -196,9 +198,11 @@ interface IOrderProcessor { /// @param feeQuoteSignature Signature for fee quote /// @return id Order id /// @dev Emits OrderCreated event to be sent to fulfillment service (operator) - function createOrder(Order calldata order, FeeQuote calldata feeQuote, bytes calldata feeQuoteSignature) - external - returns (uint256); + function createOrder( + Order calldata order, + FeeQuote calldata feeQuote, + bytes calldata feeQuoteSignature + ) external returns (uint256); /// @notice Request an order with standard fees /// @param order Order request to submit @@ -224,4 +228,5 @@ interface IOrderProcessor { /// @param reason Reason for cancellation /// @dev Only callable by operator function cancelOrder(Order calldata order, string calldata reason) external; + } From e24d612996c970f65d338873b2fb8fd775510761 Mon Sep 17 00:00:00 2001 From: jaketimothy Date: Fri, 18 Oct 2024 23:11:28 -0300 Subject: [PATCH 07/10] feat: add fee and precision logic --- ...DinariToken.sol => DinariAdapterToken.sol} | 118 +++++++++++------- 1 file changed, 75 insertions(+), 43 deletions(-) rename nest/src/token/{DinariToken.sol => DinariAdapterToken.sol} (63%) diff --git a/nest/src/token/DinariToken.sol b/nest/src/token/DinariAdapterToken.sol similarity index 63% rename from nest/src/token/DinariToken.sol rename to nest/src/token/DinariAdapterToken.sol index 09c467d..1be866a 100644 --- a/nest/src/token/DinariToken.sol +++ b/nest/src/token/DinariAdapterToken.sol @@ -13,19 +13,17 @@ import { IAggregateToken } from "../interfaces/IAggregateToken.sol"; import { IOrderProcessor } from "./external/IOrderProcessor.sol"; /** - * @title DinariToken + * @title DinariAdapterToken * @author Jake Timothy, Eugene Y. Q. Shen * @notice Implementation of the abstract ComponentToken that interfaces with external assets. * @dev Assets is USDC */ -contract DinariToken is ComponentToken { - - // TODO: name - NestDinariVault? +contract DinariAdapterToken is ComponentToken { // Storage - /// @custom:storage-location erc7201:plume.storage.DinariToken - struct DinariTokenStorage { + /// @custom:storage-location erc7201:plume.storage.DinariAdapterToken + struct DinariAdapterTokenStorage { /// @dev dShare token underlying component token address dshareToken; /// @dev Wrapped dShare token underlying component token @@ -36,17 +34,17 @@ contract DinariToken is ComponentToken { IOrderProcessor externalOrderContract; /// @dev Mapping from request IDs to external request IDs mapping(uint256 requestId => uint256 externalId) requestMap; - // TODO: rename - mapping(uint256 externalId => uint256 amountIn) orderInputAmounts; + // + mapping(uint256 externalId => uint256 amountIn) adjustedRequestAmounts; } - // keccak256(abi.encode(uint256(keccak256("plume.storage.DinariToken")) - 1)) & ~bytes32(uint256(0xff)) - bytes32 private constant DINARI_TOKEN_STORAGE_LOCATION = - 0x8a42d16a5f4a9dd4fa20afc7735f15e9454454557ef7cacfda35654781bd3100; + // keccak256(abi.encode(uint256(keccak256("plume.storage.DinariAdapterToken")) - 1)) & ~bytes32(uint256(0xff)) + bytes32 private constant DINARI_ADAPTER_TOKEN_STORAGE_LOCATION = + 0x2a49a1f589de6263f42d4846b2f178279aaa9b9efbd070fd2367cbda9b826400; - function _getDinariTokenStorage() private pure returns (DinariTokenStorage storage $) { + function _getDinariAdapterTokenStorage() private pure returns (DinariAdapterTokenStorage storage $) { assembly { - $.slot := DINARI_TOKEN_STORAGE_LOCATION + $.slot := DINARI_ADAPTER_TOKEN_STORAGE_LOCATION } } @@ -70,13 +68,13 @@ contract DinariToken is ComponentToken { } /** - * @notice Initialize the DinariToken - * @param owner Address of the owner of the DinariToken - * @param name Name of the DinariToken - * @param symbol Symbol of the DinariToken - * @param currencyToken CurrencyToken used to mint and burn the DinariToken + * @notice Initialize the DinariAdapterToken + * @param owner Address of the owner of the DinariAdapterToken + * @param name Name of the DinariAdapterToken + * @param symbol Symbol of the DinariAdapterToken + * @param currencyToken CurrencyToken used to mint and burn the DinariAdapterToken * @param dshareToken dShare token underlying component token - * @param decimals_ Number of decimals of the DinariToken + * @param decimals_ Number of decimals of the DinariAdapterToken * @param nestStakingContract Address of the Nest Staking contract * @param externalOrderContract Address of the dShares order contract */ @@ -92,7 +90,7 @@ contract DinariToken is ComponentToken { address externalOrderContract ) public initializer { super.initialize(owner, name, symbol, IERC20(currencyToken), decimals_); - DinariTokenStorage storage $ = _getDinariTokenStorage(); + DinariAdapterTokenStorage storage $ = _getDinariAdapterTokenStorage(); $.dshareToken = dshareToken; $.wrappedDshareToken = wrappedDshareToken; $.nestStakingContract = nestStakingContract; @@ -104,19 +102,28 @@ contract DinariToken is ComponentToken { /// @inheritdoc IERC4626 function convertToShares(uint256 assets) public view override(ComponentToken) returns (uint256 shares) { // Apply dshare price and wrapped conversion rate - DinariTokenStorage storage $ = _getDinariTokenStorage(); + DinariAdapterTokenStorage storage $ = _getDinariAdapterTokenStorage(); IOrderProcessor orderContract = $.externalOrderContract; - IOrderProcessor.PricePoint memory price = orderContract.latestFillPrice($.dshareToken, $.currencyToken); - return IERC4626($.wrappedDshareToken).convertToShares((assets * price.price) / 1 ether); + address paymentToken = _getComponentTokenStorage().currencyToken; + uint256 fees = orderContract.totalStandardFee(false, paymentToken, assets); + IOrderProcessor.PricePoint memory price = orderContract.latestFillPrice($.dshareToken, paymentToken); + return IERC4626($.wrappedDshareToken).convertToShares(((assets - fees) * price.price) / 1 ether); } /// @inheritdoc IERC4626 function convertToAssets(uint256 shares) public view override(ComponentToken) returns (uint256 assets) { - // Apply wrapped conversion rate and dshare price - DinariTokenStorage storage $ = _getDinariTokenStorage(); + // Apply wrapped conversion rate and dshare price, subtract fees + DinariAdapterTokenStorage storage $ = _getDinariAdapterTokenStorage(); IOrderProcessor orderContract = $.externalOrderContract; - IOrderProcessor.PricePoint memory price = orderContract.latestFillPrice($.dshareToken, $.currencyToken); - return (IERC4626($.wrappedDshareToken).convertToAssets(shares) * 1 ether) / price.price; + address paymentToken = _getComponentTokenStorage().currencyToken; + address dshareToken = $.dshareToken; + IOrderProcessor.PricePoint memory price = orderContract.latestFillPrice(dshareToken, paymentToken); + uint256 dshares = IERC4626($.wrappedDshareToken).convertToAssets(shares); + // Round down to nearest supported decimal + uint256 precisionReductionFactor = 10 ** orderContract.orderDecimalReduction(dshareToken); + uint256 proceeds = ((dshares / precisionReductionFactor) * precisionReductionFactor * 1 ether) / price.price; + uint256 fees = orderContract.totalStandardFee(true, paymentToken, proceeds); + return proceeds - fees; } /// @inheritdoc IComponentToken @@ -125,7 +132,7 @@ contract DinariToken is ComponentToken { address controller, address owner ) public override(ComponentToken) returns (uint256 requestId) { - DinariTokenStorage storage $ = _getDinariTokenStorage(); + DinariAdapterTokenStorage storage $ = _getDinariAdapterTokenStorage(); address nestStakingContract = $.nestStakingContract; if (msg.sender != nestStakingContract) { revert Unauthorized(msg.sender, nestStakingContract); @@ -135,10 +142,8 @@ contract DinariToken is ComponentToken { IOrderProcessor orderContract = $.externalOrderContract; address paymentToken = _getComponentTokenStorage().currencyToken; uint256 addedFees = orderContract.totalStandardFee(false, paymentToken, assets); - // Does not spend all assets, collect unspent in executeBuy if needed - // FIXME: make more precise? This does not spend all money and does not correspond to convertToShares + // TODO: make more precise? This does not spend all money, same logic used in convertToShares uint256 paymentTokenQuantity = assets - addedFees; - // TODO: round down to nearest supported decimal IOrderProcessor.Order memory order = IOrderProcessor.Order({ requestTimestamp: block.timestamp, @@ -154,12 +159,12 @@ contract DinariToken is ComponentToken { }); uint256 orderId = $.externalOrderContract.createOrderStandardFees(order); $.requestMap[requestId] = orderId; - $.orderInputAmounts[orderId] = paymentTokenQuantity; + $.adjustedRequestAmounts[orderId] = paymentTokenQuantity; } function notifyBuy(uint256 externalId) public { // Restrict caller? - DinariTokenStorage storage $ = _getDinariTokenStorage(); + DinariAdapterTokenStorage storage $ = _getDinariAdapterTokenStorage(); IOrderProcessor orderContract = $.externalOrderContract; if (orderContract.getOrderStatus(externalId) != IOrderProcessor.OrderStatus.FULFILLED) { @@ -168,8 +173,8 @@ contract DinariToken is ComponentToken { uint256 proceeds = orderContract.getReceivedAmount(externalId); address nestStakingContract = $.nestStakingContract; - // TODO: handle partial refunds, verify assets amount - uint256 totalSpent = $.orderInputAmounts[externalId] + orderContract.getFeesTaken(externalId); + // TODO: handle partial fee refunds, verify assets amount + uint256 totalSpent = $.adjustedRequestAmounts[externalId] + orderContract.getFeesTaken(externalId); super.notifyDeposit(totalSpent, proceeds, nestStakingContract); } @@ -195,39 +200,50 @@ contract DinariToken is ComponentToken { address controller, address owner ) public override(ComponentToken) returns (uint256 requestId) { - DinariTokenStorage storage $ = _getDinariTokenStorage(); + DinariAdapterTokenStorage storage $ = _getDinariAdapterTokenStorage(); address nestStakingContract = $.nestStakingContract; if (msg.sender != nestStakingContract) { revert Unauthorized(msg.sender, nestStakingContract); } + // TODO: should this be called with orderAmount instead? requestId = super.requestSell(shares); // Unwrap dshares - uint256 dshareAmount = IERC4626($.wrappedDshareToken).redeem(shares); + address wrappedDshareToken = $.wrappedDshareToken; + uint256 dshares = IERC4626(wrappedDshareToken).redeem(shares); + // Round down to nearest supported decimal + address dshareToken = $.dshareToken; + uint256 precisionReductionFactor = 10 ** orderContract.orderDecimalReduction(dshareToken); + uint256 orderAmount = (dshares / precisionReductionFactor) * precisionReductionFactor; + // Rewrap dust + uint256 dshareDust = dshares - orderAmount; + if (dshareDust > 0) { + IERC4626(wrappedDshareToken).deposit(dshareDust, address(this)); + } // Approve dshares IOrderProcessor orderContract = $.externalOrderContract; - IERC20($.dshareToken).approve(address(orderContract), dshareAmount); + IERC20(dshareToken).approve(address(orderContract), orderAmount); // Sell IOrderProcessor.Order memory order = IOrderProcessor.Order({ requestTimestamp: block.timestamp, recipient: address(this), - assetToken: $.dshareToken, + assetToken: dshareToken, paymentToken: _getComponentTokenStorage().currencyToken, sell: true, orderType: IOrderProcessor.OrderType.MARKET, - assetTokenQuantity: dshareAmount, + assetTokenQuantity: orderAmount, paymentTokenQuantity: 0, price: 0, tif: IOrderProcessor.TIF.DAY }); uint256 orderId = orderContract.createOrderStandardFees(order); $.requestMap[requestId] = orderId; - $.orderInputAmounts[orderId] = shares; + $.adjustedRequestAmounts[orderId] = shares; } function notifySell(uint256 externalId) public { // Restrict caller? - DinariTokenStorage storage $ = _getDinariTokenStorage(); + DinariAdapterTokenStorage storage $ = _getDinariAdapterTokenStorage(); IOrderProcessor orderContract = $.externalOrderContract; uint256 externalId = $.requestMap[requestId]; @@ -237,7 +253,23 @@ contract DinariToken is ComponentToken { uint256 proceeds = orderContract.getReceivedAmount(externalId); address nestStakingContract = $.nestStakingContract; - super.notifyRedeem(proceeds, $.orderInputAmounts[externalId], nestStakingContract); + super.notifyRedeem(proceeds, $.adjustedRequestAmounts[externalId], nestStakingContract); + } + + /// @inheritdoc IComponentToken + function redeem( + uint256 shares, + address receiver, + address controller + ) public override(ComponentToken) returns (uint256 assets) { + AdapterTokenStorage storage $ = _getAdapterTokenStorage(); + if (msg.sender != address($.externalContract)) { + revert Unauthorized(msg.sender, address($.externalContract)); + } + if (receiver != address($.nestStakingContract)) { + revert Unauthorized(receiver, address($.nestStakingContract)); + } + return super.redeem(shares, receiver, controller); } } From 277a9929db6099008951ce528a06c1b2b3336764 Mon Sep 17 00:00:00 2001 From: jaketimothy Date: Sat, 19 Oct 2024 01:04:23 -0300 Subject: [PATCH 08/10] feat: accounting --- nest/src/token/DinariAdapterToken.sol | 69 ++++++++++++++++++--------- 1 file changed, 46 insertions(+), 23 deletions(-) diff --git a/nest/src/token/DinariAdapterToken.sol b/nest/src/token/DinariAdapterToken.sol index 1be866a..5b84372 100644 --- a/nest/src/token/DinariAdapterToken.sol +++ b/nest/src/token/DinariAdapterToken.sol @@ -32,10 +32,9 @@ contract DinariAdapterToken is ComponentToken { address nestStakingContract; /// @dev Address of the dShares order contract IOrderProcessor externalOrderContract; - /// @dev Mapping from request IDs to external request IDs - mapping(uint256 requestId => uint256 externalId) requestMap; // - mapping(uint256 externalId => uint256 amountIn) adjustedRequestAmounts; + mapping(uint256 externalId => uint256 amountIn) adjustedTotalInput; + mapping(uint256 externalId => uint256) orderAmount; } // keccak256(abi.encode(uint256(keccak256("plume.storage.DinariAdapterToken")) - 1)) & ~bytes32(uint256(0xff)) @@ -101,13 +100,26 @@ contract DinariAdapterToken is ComponentToken { /// @inheritdoc IERC4626 function convertToShares(uint256 assets) public view override(ComponentToken) returns (uint256 shares) { - // Apply dshare price and wrapped conversion rate + // Apply dshare price and wrapped conversion rate, fees DinariAdapterTokenStorage storage $ = _getDinariAdapterTokenStorage(); IOrderProcessor orderContract = $.externalOrderContract; address paymentToken = _getComponentTokenStorage().currencyToken; - uint256 fees = orderContract.totalStandardFee(false, paymentToken, assets); + (uint256 orderAmount, uint256 fees) = _getOrderFromTotalBuy(orderContract, paymentToken, assets); IOrderProcessor.PricePoint memory price = orderContract.latestFillPrice($.dshareToken, paymentToken); - return IERC4626($.wrappedDshareToken).convertToShares(((assets - fees) * price.price) / 1 ether); + return IERC4626($.wrappedDshareToken).convertToShares(((orderAmount + fees) * price.price) / 1 ether); + } + + function _getOrderFromTotalBuy( + IOrderProcessor orderContract, + address paymentToken, + uint256 totalBuy + ) private view returns (uint256 orderAmount, uint256 fees) { + // order * (1 + vfee) + flat = total + // order = (total - flat) / (1 + vfee) + (uint256 flatFee, uint24 percentageFeeRate) = orderContract.getStandardFees(false, paymentToken); + orderAmount = (totalBuy - flatFee) * 1_000_000 / (1_000_000 + percentageFeeRate); + + fees = orderContract.totalStandardFee(false, paymentToken, orderAmount); } /// @inheritdoc IERC4626 @@ -137,14 +149,18 @@ contract DinariAdapterToken is ComponentToken { if (msg.sender != nestStakingContract) { revert Unauthorized(msg.sender, nestStakingContract); } - requestId = super.requestBuy(assets); IOrderProcessor orderContract = $.externalOrderContract; address paymentToken = _getComponentTokenStorage().currencyToken; - uint256 addedFees = orderContract.totalStandardFee(false, paymentToken, assets); - // TODO: make more precise? This does not spend all money, same logic used in convertToShares - uint256 paymentTokenQuantity = assets - addedFees; + (uint256 orderAmount, uint256 fees) = _getOrderFromTotalBuy(orderContract, paymentToken, assets); + uint256 totalInput = orderAmount + fees; + // Subcall with calculated input amount to be safe + requestId = super.requestDeposit(totalInput, controller, owner); + + // Approve dshares + IERC20(paymentToken).approve(address(orderContract), totalInput); + // Buy IOrderProcessor.Order memory order = IOrderProcessor.Order({ requestTimestamp: block.timestamp, recipient: address(this), @@ -153,13 +169,13 @@ contract DinariAdapterToken is ComponentToken { sell: false, orderType: IOrderProcessor.OrderType.MARKET, assetTokenQuantity: 0, - paymentTokenQuantity: paymentTokenQuantity, + paymentTokenQuantity: orderAmount, price: 0, tif: IOrderProcessor.TIF.DAY }); - uint256 orderId = $.externalOrderContract.createOrderStandardFees(order); - $.requestMap[requestId] = orderId; - $.adjustedRequestAmounts[orderId] = paymentTokenQuantity; + uint256 orderId = orderContract.createOrderStandardFees(order); + $.adjustedTotalInput[orderId] = totalInput; + $.orderAmount[orderId] = orderAmount; } function notifyBuy(uint256 externalId) public { @@ -173,9 +189,16 @@ contract DinariAdapterToken is ComponentToken { uint256 proceeds = orderContract.getReceivedAmount(externalId); address nestStakingContract = $.nestStakingContract; - // TODO: handle partial fee refunds, verify assets amount - uint256 totalSpent = $.adjustedRequestAmounts[externalId] + orderContract.getFeesTaken(externalId); - super.notifyDeposit(totalSpent, proceeds, nestStakingContract); + uint256 totalInput = $.adjustedTotalInput[externalId]; + + super.notifyDeposit(totalInput, proceeds, nestStakingContract); + + // Send fee refund to controller + uint256 totalSpent = $.orderAmount[externalId] + orderContract.getFeesTaken(externalId); + uint256 refund = totalInput - totalSpent; + if (refund > 0) { + IERC20(_getComponentTokenStorage().currencyToken).transfer(nestStakingContract, refund); + } } /// @inheritdoc IComponentToken @@ -205,8 +228,6 @@ contract DinariAdapterToken is ComponentToken { if (msg.sender != nestStakingContract) { revert Unauthorized(msg.sender, nestStakingContract); } - // TODO: should this be called with orderAmount instead? - requestId = super.requestSell(shares); // Unwrap dshares address wrappedDshareToken = $.wrappedDshareToken; @@ -215,6 +236,10 @@ contract DinariAdapterToken is ComponentToken { address dshareToken = $.dshareToken; uint256 precisionReductionFactor = 10 ** orderContract.orderDecimalReduction(dshareToken); uint256 orderAmount = (dshares / precisionReductionFactor) * precisionReductionFactor; + + // Subcall with dust removed + requestId = super.requestRedeem(orderAmount, controller, owner); + // Rewrap dust uint256 dshareDust = dshares - orderAmount; if (dshareDust > 0) { @@ -237,8 +262,7 @@ contract DinariAdapterToken is ComponentToken { tif: IOrderProcessor.TIF.DAY }); uint256 orderId = orderContract.createOrderStandardFees(order); - $.requestMap[requestId] = orderId; - $.adjustedRequestAmounts[orderId] = shares; + $.adjustedTotalInput[orderId] = orderAmount; } function notifySell(uint256 externalId) public { @@ -246,14 +270,13 @@ contract DinariAdapterToken is ComponentToken { DinariAdapterTokenStorage storage $ = _getDinariAdapterTokenStorage(); IOrderProcessor orderContract = $.externalOrderContract; - uint256 externalId = $.requestMap[requestId]; if (orderContract.getOrderStatus(externalId) != IOrderProcessor.OrderStatus.FULFILLED) { revert OrderNotFilled(); } uint256 proceeds = orderContract.getReceivedAmount(externalId); address nestStakingContract = $.nestStakingContract; - super.notifyRedeem(proceeds, $.adjustedRequestAmounts[externalId], nestStakingContract); + super.notifyRedeem(proceeds, $.adjustedTotalInput[externalId], nestStakingContract); } /// @inheritdoc IComponentToken From 713f35c2330b17691c7ac6e3b74fa10a94c91ab5 Mon Sep 17 00:00:00 2001 From: jaketimothy Date: Mon, 21 Oct 2024 21:20:46 -0700 Subject: [PATCH 09/10] feat: consolidate processing buys and sells --- nest/src/token/DinariAdapterToken.sol | 171 ++++++++++++++++++-------- 1 file changed, 121 insertions(+), 50 deletions(-) diff --git a/nest/src/token/DinariAdapterToken.sol b/nest/src/token/DinariAdapterToken.sol index 5b84372..cd8a53b 100644 --- a/nest/src/token/DinariAdapterToken.sol +++ b/nest/src/token/DinariAdapterToken.sol @@ -7,6 +7,7 @@ import { ERC20Upgradeable } from "@openzeppelin/contracts-upgradeable/token/ERC2 import { IERC4626 } from "@openzeppelin/contracts/interfaces/IERC4626.sol"; import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; +import { DoubleEndedQueue } from "@openzeppelin/contracts/utils/DoubleEndedQueue.sol"; import { ComponentToken } from "../ComponentToken.sol"; import { IAggregateToken } from "../interfaces/IAggregateToken.sol"; @@ -20,8 +21,16 @@ import { IOrderProcessor } from "./external/IOrderProcessor.sol"; */ contract DinariAdapterToken is ComponentToken { + using DoubleEndedQueue for DoubleEndedQueue.Bytes32Deque; + // Storage + struct DShareOrderInfo { + bool sell; + uint256 orderAmount; + uint256 fees; + } + /// @custom:storage-location erc7201:plume.storage.DinariAdapterToken struct DinariAdapterTokenStorage { /// @dev dShare token underlying component token @@ -33,8 +42,8 @@ contract DinariAdapterToken is ComponentToken { /// @dev Address of the dShares order contract IOrderProcessor externalOrderContract; // - mapping(uint256 externalId => uint256 amountIn) adjustedTotalInput; - mapping(uint256 externalId => uint256) orderAmount; + mapping(uint256 orderId => DShareOrderInfo) submittedOrderInfo; + DoubleEndedQueue.Bytes32Deque submittedOrders; } // keccak256(abi.encode(uint256(keccak256("plume.storage.DinariAdapterToken")) - 1)) & ~bytes32(uint256(0xff)) @@ -56,6 +65,10 @@ contract DinariAdapterToken is ComponentToken { */ error Unauthorized(address invalidCaller, address caller); + error NoOutstandingOrders(); + error OrderDoesNotExist(); + error OrderStillActive(); + // Initializer /** @@ -174,47 +187,8 @@ contract DinariAdapterToken is ComponentToken { tif: IOrderProcessor.TIF.DAY }); uint256 orderId = orderContract.createOrderStandardFees(order); - $.adjustedTotalInput[orderId] = totalInput; - $.orderAmount[orderId] = orderAmount; - } - - function notifyBuy(uint256 externalId) public { - // Restrict caller? - DinariAdapterTokenStorage storage $ = _getDinariAdapterTokenStorage(); - - IOrderProcessor orderContract = $.externalOrderContract; - if (orderContract.getOrderStatus(externalId) != IOrderProcessor.OrderStatus.FULFILLED) { - revert OrderNotFilled(); - } - - uint256 proceeds = orderContract.getReceivedAmount(externalId); - address nestStakingContract = $.nestStakingContract; - uint256 totalInput = $.adjustedTotalInput[externalId]; - - super.notifyDeposit(totalInput, proceeds, nestStakingContract); - - // Send fee refund to controller - uint256 totalSpent = $.orderAmount[externalId] + orderContract.getFeesTaken(externalId); - uint256 refund = totalInput - totalSpent; - if (refund > 0) { - IERC20(_getComponentTokenStorage().currencyToken).transfer(nestStakingContract, refund); - } - } - - /// @inheritdoc IComponentToken - function deposit( - uint256 assets, - address receiver, - address controller - ) public override(ComponentToken) returns (uint256 shares) { - AdapterTokenStorage storage $ = _getAdapterTokenStorage(); - if (msg.sender != address($.externalContract)) { - revert Unauthorized(msg.sender, address($.externalContract)); - } - if (receiver != address($.nestStakingContract)) { - revert Unauthorized(receiver, address($.nestStakingContract)); - } - return super.deposit(assets, receiver, controller); + $.submittedOrderInfo[orderId] = DShareOrderInfo({ sell: false, orderAmount: orderAmount, fees: fees }); + $.submittedOrders.pushBack(bytes32(orderId)); } /// @inheritdoc IComponentToken @@ -262,21 +236,118 @@ contract DinariAdapterToken is ComponentToken { tif: IOrderProcessor.TIF.DAY }); uint256 orderId = orderContract.createOrderStandardFees(order); - $.adjustedTotalInput[orderId] = orderAmount; + $.submittedOrderInfo[orderId] = DShareOrderInfo({ sell: true, orderAmount: orderAmount, fees: 0 }); + $.submittedOrders.pushBack(bytes32(orderId)); } - function notifySell(uint256 externalId) public { - // Restrict caller? + /// @dev Panic + function getNextSubmittedOrderStatus() public view returns (IOrderProcessor.OrderStatus) { DinariAdapterTokenStorage storage $ = _getDinariAdapterTokenStorage(); + if ($.submittedOrders.length() == 0) { + revert NoOutstandingOrders(); + } + uint256 orderId = uint256($.submittedOrders.front()); + return $.externalOrderContract.getOrderStatus(orderId); + } + function processSubmittedOrders() public { + DinariAdapterTokenStorage storage $ = _getDinariAdapterTokenStorage(); IOrderProcessor orderContract = $.externalOrderContract; - if (orderContract.getOrderStatus(externalId) != IOrderProcessor.OrderStatus.FULFILLED) { - revert OrderNotFilled(); + address paymentToken = _getComponentTokenStorage().currencyToken; + address nestStakingContract = $.nestStakingContract; + while ($.submittedOrders.length() > 0) { + uint256 orderId = uint256($.submittedOrders.front()); + IOrderProcessor.OrderStatus status = orderContract.getOrderStatus(orderId); + if (status == IOrderProcessor.OrderStatus.ACTIVE) { + break; + } else if (status == IOrderProcessor.OrderStatus.NONE) { + revert OrderDoesNotExist(); + } + + DShareOrderInfo memory orderInfo = $.submittedOrderInfo[orderId]; + uint256 totalInput = orderInfo.orderAmount + orderInfo.fees; + + if (status == IOrderProcessor.OrderStatus.CANCELLED) { + // Assets have been refunded + $.pendingDepositRequest[controller] -= totalInput; + } else if (status == IOrderProcessor.OrderStatus.FULFILLED) { + uint256 proceeds = orderContract.getReceivedAmount(orderId); + + if (orderInfo.sell) { + super.notifyRedeem(proceeds, orderInfo.orderAmount, nestStakingContract); + } else { + super.notifyDeposit(totalInput, proceeds, nestStakingContract); + + // Send fee refund to controller + uint256 totalSpent = orderInfo.orderAmount + orderContract.getFeesTaken(orderId); + uint256 refund = totalInput - totalSpent; + if (refund > 0) { + IERC20(paymentToken).transfer(nestStakingContract, refund); + } + } + } + + $.submittedOrders.popFront(); } + } - uint256 proceeds = orderContract.getReceivedAmount(externalId); + /// @dev Single order processing if gas limit is reached + function processNextSubmittedOrder() public { + DinariAdapterTokenStorage storage $ = _getDinariAdapterTokenStorage(); + IOrderProcessor orderContract = $.externalOrderContract; address nestStakingContract = $.nestStakingContract; - super.notifyRedeem(proceeds, $.adjustedTotalInput[externalId], nestStakingContract); + + if ($.submittedOrders.length() == 0) { + revert NoOutstandingOrders(); + } + uint256 orderId = uint256($.submittedOrders.front()); + IOrderProcessor.OrderStatus status = orderContract.getOrderStatus(orderId); + if (status == IOrderProcessor.OrderStatus.ACTIVE) { + revert OrderStillActive(); + } else if (status == IOrderProcessor.OrderStatus.NONE) { + revert OrderDoesNotExist(); + } + + DShareOrderInfo memory orderInfo = $.submittedOrderInfo[orderId]; + uint256 totalInput = orderInfo.orderAmount + orderInfo.fees; + + if (status == IOrderProcessor.OrderStatus.CANCELLED) { + // Assets have been refunded + $.pendingDepositRequest[controller] -= totalInput; + } else if (status == IOrderProcessor.OrderStatus.FULFILLED) { + uint256 proceeds = orderContract.getReceivedAmount(orderId); + + if (orderInfo.sell) { + super.notifyRedeem(proceeds, orderInfo.orderAmount, nestStakingContract); + } else { + super.notifyDeposit(totalInput, proceeds, nestStakingContract); + + // Send fee refund to controller + uint256 totalSpent = orderInfo.orderAmount + orderContract.getFeesTaken(orderId); + uint256 refund = totalInput - totalSpent; + if (refund > 0) { + IERC20(_getComponentTokenStorage().currencyToken).transfer(nestStakingContract, refund); + } + } + } + + $.submittedOrders.popFront(); + } + + /// @inheritdoc IComponentToken + function deposit( + uint256 assets, + address receiver, + address controller + ) public override(ComponentToken) returns (uint256 shares) { + AdapterTokenStorage storage $ = _getAdapterTokenStorage(); + if (msg.sender != address($.externalContract)) { + revert Unauthorized(msg.sender, address($.externalContract)); + } + if (receiver != address($.nestStakingContract)) { + revert Unauthorized(receiver, address($.nestStakingContract)); + } + return super.deposit(assets, receiver, controller); } /// @inheritdoc IComponentToken From 3687cedb95839ba1b05403b09387c4816471f70d Mon Sep 17 00:00:00 2001 From: jaketimothy Date: Mon, 21 Oct 2024 21:34:01 -0700 Subject: [PATCH 10/10] docs: license --- nest/src/token/DinariAdapterToken.sol | 9 ++++-- nest/src/token/external/IOrderProcessor.sol | 36 +++++++++++++++------ 2 files changed, 34 insertions(+), 11 deletions(-) diff --git a/nest/src/token/DinariAdapterToken.sol b/nest/src/token/DinariAdapterToken.sol index cd8a53b..b1a857f 100644 --- a/nest/src/token/DinariAdapterToken.sol +++ b/nest/src/token/DinariAdapterToken.sol @@ -1,3 +1,4 @@ +// SPDX-License-Identifier: MIT pragma solidity ^0.8.25; import { AccessControlUpgradeable } from "@openzeppelin/contracts-upgradeable/access/AccessControlUpgradeable.sol"; @@ -112,7 +113,9 @@ contract DinariAdapterToken is ComponentToken { // Override Functions /// @inheritdoc IERC4626 - function convertToShares(uint256 assets) public view override(ComponentToken) returns (uint256 shares) { + function convertToShares( + uint256 assets + ) public view override(ComponentToken) returns (uint256 shares) { // Apply dshare price and wrapped conversion rate, fees DinariAdapterTokenStorage storage $ = _getDinariAdapterTokenStorage(); IOrderProcessor orderContract = $.externalOrderContract; @@ -136,7 +139,9 @@ contract DinariAdapterToken is ComponentToken { } /// @inheritdoc IERC4626 - function convertToAssets(uint256 shares) public view override(ComponentToken) returns (uint256 assets) { + function convertToAssets( + uint256 shares + ) public view override(ComponentToken) returns (uint256 assets) { // Apply wrapped conversion rate and dshare price, subtract fees DinariAdapterTokenStorage storage $ = _getDinariAdapterTokenStorage(); IOrderProcessor orderContract = $.externalOrderContract; diff --git a/nest/src/token/external/IOrderProcessor.sol b/nest/src/token/external/IOrderProcessor.sol index e5d5239..3bea337 100644 --- a/nest/src/token/external/IOrderProcessor.sol +++ b/nest/src/token/external/IOrderProcessor.sol @@ -120,32 +120,46 @@ interface IOrderProcessor { /// @notice Hash order data for validation and create unique order ID /// @param order Order data /// @dev EIP-712 typed data hash of order - function hashOrder(Order calldata order) external pure returns (uint256); + function hashOrder( + Order calldata order + ) external pure returns (uint256); /// @notice Status of a given order /// @param id Order ID - function getOrderStatus(uint256 id) external view returns (OrderStatus); + function getOrderStatus( + uint256 id + ) external view returns (OrderStatus); /// @notice Get remaining order quantity to fill /// @param id Order ID - function getUnfilledAmount(uint256 id) external view returns (uint256); + function getUnfilledAmount( + uint256 id + ) external view returns (uint256); /// @notice Get received amount for an order /// @param id Order ID - function getReceivedAmount(uint256 id) external view returns (uint256); + function getReceivedAmount( + uint256 id + ) external view returns (uint256); /// @notice Get fees in payment token escrowed for a buy order /// @param id Order ID - function getFeesEscrowed(uint256 id) external view returns (uint256); + function getFeesEscrowed( + uint256 id + ) external view returns (uint256); /// @notice Get cumulative payment token fees taken for an order /// @param id Order ID /// @dev Only valid for ACTIVE orders - function getFeesTaken(uint256 id) external view returns (uint256); + function getFeesTaken( + uint256 id + ) external view returns (uint256); /// @notice Reduces the precision allowed for the asset token quantity of an order /// @param token The address of the token - function orderDecimalReduction(address token) external view returns (uint8); + function orderDecimalReduction( + address token + ) external view returns (uint8); /// @notice Get worst case fees for an order /// @param sell Sell order @@ -208,7 +222,9 @@ interface IOrderProcessor { /// @param order Order request to submit /// @return id Order id /// @dev Emits OrderCreated event to be sent to fulfillment service (operator) - function createOrderStandardFees(Order calldata order) external returns (uint256); + function createOrderStandardFees( + Order calldata order + ) external returns (uint256); /// @notice Fill an order /// @param order Order request to fill @@ -221,7 +237,9 @@ interface IOrderProcessor { /// @param id Order id /// @dev Only callable by initial order requester /// @dev Emits CancelRequested event to be sent to fulfillment service (operator) - function requestCancel(uint256 id) external; + function requestCancel( + uint256 id + ) external; /// @notice Cancel an order /// @param order Order request to cancel