Skip to content

Commit

Permalink
Neat interfaces for getMakerAmount() and getTakerAmount()
Browse files Browse the repository at this point in the history
  • Loading branch information
k06a committed Sep 27, 2023
1 parent 313a1f8 commit e83596c
Show file tree
Hide file tree
Showing 23 changed files with 283 additions and 147 deletions.
58 changes: 27 additions & 31 deletions contracts/OrderLib.sol
Original file line number Diff line number Diff line change
Expand Up @@ -9,12 +9,13 @@ import "./interfaces/IOrderMixin.sol";
import "./libraries/MakerTraitsLib.sol";
import "./libraries/ExtensionLib.sol";
import "./helpers/AmountCalculator.sol";
import "./interfaces/IAmountGetter.sol";

/**
* @title OrderLib
* @dev The library provides common functionality for processing and manipulating limit orders.
* It provides functionality to calculate and verify order hashes, calculate trade amounts, and validate
* extension data associated with orders. The library also contains helper methods to get the receiver of
* @dev The library provides common functionality for processing and manipulating limit orders.
* It provides functionality to calculate and verify order hashes, calculate trade amounts, and validate
* extension data associated with orders. The library also contains helper methods to get the receiver of
* an order and call getter functions.
*/
library OrderLib {
Expand Down Expand Up @@ -78,7 +79,7 @@ import "./helpers/AmountCalculator.sol";
return receiver != address(0) ? receiver : order.maker.get();
}

/**
/**
* @notice Calculates the making amount based on the requested taking amount.
* @dev If getter is specified in the extension data, the getter is called to calculate the making amount,
* otherwise the making amount is calculated linearly.
Expand All @@ -96,12 +97,20 @@ import "./helpers/AmountCalculator.sol";
uint256 remainingMakingAmount,
bytes32 orderHash
) internal view returns(uint256) {
bytes calldata getter = extension.makingAmountGetter();
if (getter.length == 0) {
bytes calldata data = extension.makingAmountData();
if (data.length == 0) {
// Linear proportion
return AmountCalculator.getMakingAmount(order.makingAmount, order.takingAmount, requestedTakingAmount);
}
return _callGetter(getter, requestedTakingAmount, remainingMakingAmount, orderHash);
return IAmountGetter(address(bytes20(data))).getMakingAmount(
order,
extension,
orderHash,
msg.sender,
requestedTakingAmount,
remainingMakingAmount,
data[20:]
);
}

/**
Expand All @@ -122,33 +131,20 @@ import "./helpers/AmountCalculator.sol";
uint256 remainingMakingAmount,
bytes32 orderHash
) internal view returns(uint256) {
bytes calldata getter = extension.takingAmountGetter();
if (getter.length == 0) {
bytes calldata data = extension.takingAmountData();
if (data.length == 0) {
// Linear proportion
return AmountCalculator.getTakingAmount(order.makingAmount, order.takingAmount, requestedMakingAmount);
}
return _callGetter(getter, requestedMakingAmount, remainingMakingAmount, orderHash);
}

/**
* @notice Calls the getter function to calculate an amount for a trade.
* @param getter The address of the getter function.
* @param requestedAmount The amount requested by the taker.
* @param remainingMakingAmount The remaining amount of the asset left to fill.
* @param orderHash The hash of the order.
* @return amount The calculated amount.
*/
function _callGetter(
bytes calldata getter,
uint256 requestedAmount,
uint256 remainingMakingAmount,
bytes32 orderHash
) private view returns(uint256) {
if (getter.length < 20) revert WrongGetter();

(bool success, bytes memory result) = address(bytes20(getter)).staticcall(abi.encodePacked(getter[20:], requestedAmount, remainingMakingAmount, orderHash));
if (!success || result.length != 32) revert GetAmountCallFailed();
return abi.decode(result, (uint256));
return IAmountGetter(address(bytes20(data))).getTakingAmount(
order,
extension,
orderHash,
msg.sender,
requestedMakingAmount,
remainingMakingAmount,
data[20:]
);
}

/**
Expand Down
6 changes: 3 additions & 3 deletions contracts/OrderMixin.sol
Original file line number Diff line number Diff line change
Expand Up @@ -375,7 +375,7 @@ abstract contract OrderMixin is IOrderMixin, EIP712, OnlyWethReceiver, Predicate
data = data[20:];
}
IPreInteraction(listener).preInteraction(
order, orderHash, msg.sender, makingAmount, takingAmount, remainingMakingAmount, data
order, extension, orderHash, msg.sender, makingAmount, takingAmount, remainingMakingAmount, data
);
}

Expand Down Expand Up @@ -403,7 +403,7 @@ abstract contract OrderMixin is IOrderMixin, EIP712, OnlyWethReceiver, Predicate
if (interaction.length >= 20) {
// proceed only if interaction length is enough to store address
uint256 offeredTakingAmount = ITakerInteraction(address(bytes20(interaction))).takerInteraction(
order, orderHash, extension, msg.sender, makingAmount, takingAmount, remainingMakingAmount, interaction[20:]
order, extension, orderHash, msg.sender, makingAmount, takingAmount, remainingMakingAmount, interaction[20:]
);
if (offeredTakingAmount > takingAmount && order.makerTraits.allowImproveRateViaInteraction()) {
takingAmount = offeredTakingAmount;
Expand Down Expand Up @@ -461,7 +461,7 @@ abstract contract OrderMixin is IOrderMixin, EIP712, OnlyWethReceiver, Predicate
data = data[20:];
}
IPostInteraction(listener).postInteraction(
order, orderHash, msg.sender, makingAmount, takingAmount, remainingMakingAmount, data
order, extension, orderHash, msg.sender, makingAmount, takingAmount, remainingMakingAmount, data
);
}

Expand Down
68 changes: 49 additions & 19 deletions contracts/helpers/ChainlinkCalculator.sol
Original file line number Diff line number Diff line change
Expand Up @@ -4,33 +4,63 @@ pragma solidity 0.8.19;

import "@chainlink/contracts/src/v0.8/interfaces/AggregatorV3Interface.sol";
import "@openzeppelin/contracts/utils/math/SafeCast.sol";

import "../interfaces/IOrderMixin.sol";
import "../interfaces/IAmountGetter.sol";

/// @title A helper contract for interactions with https://docs.chain.link
contract ChainlinkCalculator {
contract ChainlinkCalculator is IAmountGetter {
using SafeCast for int256;

error DifferentOracleDecimals();

uint256 private constant _SPREAD_DENOMINATOR = 1e9;
uint256 private constant _INVERSE_MASK = 1 << 255;

/// @notice Calculates price of token relative to oracle unit (ETH or USD)
/// @param inverseAndSpread concatenated inverse flag and spread.
/// Lowest 254 bits specify spread amount. Spread is scaled by 1e9, i.e. 101% = 1.01e9, 99% = 0.99e9.
/// Highest bit is set when oracle price should be inverted,
/// e.g. for DAI-ETH oracle, inverse=false means that we request DAI price in ETH
/// and inverse=true means that we request ETH price in DAI
/// @return Amount * spread * oracle price
function singlePrice(AggregatorV3Interface oracle, uint256 inverseAndSpread, uint256 amount) external view returns(uint256) {

function getMakingAmount(
IOrderMixin.Order calldata /* order */,
bytes calldata /* extension */,
bytes32 /* orderHash */,
address /* taker */,
uint256 takingAmount,
uint256 /* remainingMakingAmount */,
bytes calldata extraData
) external view returns (uint256) {
(
AggregatorV3Interface oracle,
uint256 spread
) = abi.decode(extraData, (AggregatorV3Interface, uint256));

/// @notice Calculates price of token relative to oracle unit (ETH or USD)
/// Lowest 254 bits specify spread amount. Spread is scaled by 1e9, i.e. 101% = 1.01e9, 99% = 0.99e9.
/// Highest bit is set when oracle price should be inverted,
/// e.g. for DAI-ETH oracle, inverse=false means that we request DAI price in ETH
/// and inverse=true means that we request ETH price in DAI
/// @return Amount * spread * oracle price
(, int256 latestAnswer,,,) = oracle.latestRoundData();
bool inverse = inverseAndSpread & _INVERSE_MASK > 0;
uint256 spread = inverseAndSpread & (~_INVERSE_MASK);
if (inverse) {
return amount * spread * (10 ** oracle.decimals()) / latestAnswer.toUint256() / _SPREAD_DENOMINATOR;
} else {
return amount * spread * latestAnswer.toUint256() / (10 ** oracle.decimals()) / _SPREAD_DENOMINATOR;
}
return takingAmount * spread * latestAnswer.toUint256() / (10 ** oracle.decimals()) / _SPREAD_DENOMINATOR;
}

function getTakingAmount(
IOrderMixin.Order calldata /* order */,
bytes calldata /* extension */,
bytes32 /* orderHash */,
address /* taker */,
uint256 makingAmount,
uint256 /* remainingMakingAmount */,
bytes calldata extraData
) external view returns (uint256) {
(
AggregatorV3Interface oracle,
uint256 spread
) = abi.decode(extraData, (AggregatorV3Interface, uint256));

/// @notice Calculates price of token relative to oracle unit (ETH or USD)
/// Lowest 254 bits specify spread amount. Spread is scaled by 1e9, i.e. 101% = 1.01e9, 99% = 0.99e9.
/// Highest bit is set when oracle price should be inverted,
/// e.g. for DAI-ETH oracle, inverse=false means that we request DAI price in ETH
/// and inverse=true means that we request ETH price in DAI
/// @return Amount * spread * oracle price
(, int256 latestAnswer,,,) = oracle.latestRoundData();
return makingAmount * spread * (10 ** oracle.decimals()) / latestAnswer.toUint256() / _SPREAD_DENOMINATOR;
}

/// @notice Calculates price of token A relative to token B. Note that order is important
Expand Down
41 changes: 36 additions & 5 deletions contracts/helpers/DutchAuctionCalculator.sol
Original file line number Diff line number Diff line change
Expand Up @@ -3,18 +3,49 @@
pragma solidity 0.8.19;

import "@openzeppelin/contracts/utils/math/Math.sol";
import "../interfaces/IAmountGetter.sol";

contract DutchAuctionCalculator is IAmountGetter {
using Math for uint256;

contract DutchAuctionCalculator {
uint256 private constant _LOW_128_BITS = 0xffffffffffffffffffffffffffffffff;

function getMakingAmount(uint256 startTimeEndTime, uint256 takingAmountStart, uint256 takingAmountEnd, uint256 makingAmount, uint256 requestedTakingAmount) external view returns(uint256) {
function getMakingAmount(
IOrderMixin.Order calldata order,
bytes calldata /* extension */,
bytes32 /* orderHash */,
address /* taker */,
uint256 takingAmount,
uint256 /* remainingMakingAmount */,
bytes calldata extraData
) external view returns (uint256) {
(
uint256 startTimeEndTime,
uint256 takingAmountStart,
uint256 takingAmountEnd
) = abi.decode(extraData, (uint256, uint256, uint256));

uint256 calculatedTakingAmount = _calculateAuctionTakingAmount(startTimeEndTime, takingAmountStart, takingAmountEnd);
return requestedTakingAmount * makingAmount / calculatedTakingAmount;
return order.makingAmount * takingAmount / calculatedTakingAmount;
}

function getTakingAmount(uint256 startTimeEndTime, uint256 takingAmountStart, uint256 takingAmountEnd, uint256 makingAmount, uint256 requestedMakingAmount) external view returns(uint256) {
function getTakingAmount(
IOrderMixin.Order calldata order,
bytes calldata /* extension */,
bytes32 /* orderHash */,
address /* taker */,
uint256 makingAmount,
uint256 /* remainingMakingAmount */,
bytes calldata extraData
) external view returns (uint256) {
(
uint256 startTimeEndTime,
uint256 takingAmountStart,
uint256 takingAmountEnd
) = abi.decode(extraData, (uint256, uint256, uint256));

uint256 calculatedTakingAmount = _calculateAuctionTakingAmount(startTimeEndTime, takingAmountStart, takingAmountEnd);
return (requestedMakingAmount * calculatedTakingAmount + makingAmount - 1) / makingAmount;
return (calculatedTakingAmount * makingAmount).ceilDiv(order.makingAmount);
}

function _calculateAuctionTakingAmount(uint256 startTimeEndTime, uint256 takingAmountStart, uint256 takingAmountEnd) private view returns(uint256) {
Expand Down
1 change: 1 addition & 0 deletions contracts/helpers/ETHOrders.sol
Original file line number Diff line number Diff line change
Expand Up @@ -111,6 +111,7 @@ contract ETHOrders is IPostInteraction, OnlyWethReceiver {
*/
function postInteraction(
IOrderMixin.Order calldata /*order*/,
bytes calldata /* extension */,
bytes32 orderHash,
address /*taker*/,
uint256 makingAmount,
Expand Down
1 change: 1 addition & 0 deletions contracts/helpers/OrderIdInvalidator.sol
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ contract OrderIdInvalidator is IPreInteraction {

function preInteraction(
IOrderMixin.Order calldata order,
bytes calldata /* extension */,
bytes32 orderHash,
address /* taker */,
uint256 /* makingAmount */,
Expand Down
55 changes: 44 additions & 11 deletions contracts/helpers/RangeAmountCalculator.sol
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@
pragma solidity 0.8.19;

import "@openzeppelin/contracts/utils/math/Math.sol";
import "../interfaces/IOrderMixin.sol";
import "../interfaces/IAmountGetter.sol";

/**
* A range limit order is a strategy used to sell an asset within a specified price range.
Expand All @@ -20,23 +22,54 @@ import "@openzeppelin/contracts/utils/math/Math.sol";
* y = ----------------------- * x + priceStart
* totalAmount
*/
contract RangeAmountCalculator {

contract RangeAmountCalculator is IAmountGetter {
error IncorrectRange();

modifier correctPrices(uint256 priceStart, uint256 priceEnd) {
if (priceEnd <= priceStart) revert IncorrectRange();
_;
}

function getTakingAmount(
IOrderMixin.Order calldata order,
bytes calldata /* extension */,
bytes32 /* orderHash */,
address /* taker */,
uint256 makingAmount,
uint256 remainingMakingAmount,
bytes calldata extraData
) external pure returns (uint256) {
(
uint256 priceStart,
uint256 priceEnd
) = abi.decode(extraData, (uint256, uint256));
return getRangeTakerAmount(priceStart, priceEnd, order.makingAmount, makingAmount, remainingMakingAmount);
}

function getMakingAmount(
IOrderMixin.Order calldata order,
bytes calldata /* extension */,
bytes32 /* orderHash */,
address /* taker */,
uint256 takingAmount,
uint256 remainingMakingAmount,
bytes calldata extraData
) external pure returns (uint256) {
(
uint256 priceStart,
uint256 priceEnd
) = abi.decode(extraData, (uint256, uint256));
return getRangeMakerAmount(priceStart, priceEnd, order.makingAmount, takingAmount, remainingMakingAmount);
}

function getRangeTakerAmount(
uint256 priceStart,
uint256 priceEnd,
uint256 totalAmount,
uint256 fillAmount,
uint256 orderMakingAmount,
uint256 makingAmount,
uint256 remainingMakingAmount
) public correctPrices(priceStart, priceEnd) pure returns(uint256) {
uint256 alreadyFilledMakingAmount = totalAmount - remainingMakingAmount;
uint256 alreadyFilledMakingAmount = orderMakingAmount - remainingMakingAmount;
/**
* rangeTakerAmount = (
* f(makerAmountFilled) + f(makerAmountFilled + fillAmount)
Expand All @@ -45,22 +78,22 @@ contract RangeAmountCalculator {
* scaling to 1e18 happens to have better price accuracy
*/
return (
(priceEnd - priceStart) * (2 * alreadyFilledMakingAmount + fillAmount) / totalAmount +
(priceEnd - priceStart) * (2 * alreadyFilledMakingAmount + makingAmount) / orderMakingAmount +
2 * priceStart
) * fillAmount / 2e18;
) * makingAmount / 2e18;
}

function getRangeMakerAmount(
uint256 priceStart,
uint256 priceEnd,
uint256 totalLiquidity,
uint256 orderMakingAmount,
uint256 takingAmount,
uint256 remainingMakingAmount
) public correctPrices(priceStart, priceEnd) pure returns(uint256) {
uint256 alreadyFilledMakingAmount = totalLiquidity - remainingMakingAmount;
uint256 alreadyFilledMakingAmount = orderMakingAmount - remainingMakingAmount;
uint256 b = priceStart;
uint256 k = (priceEnd - priceStart) * 1e18 / totalLiquidity;
uint256 bDivK = priceStart * totalLiquidity / (priceEnd - priceStart);
uint256 k = (priceEnd - priceStart) * 1e18 / orderMakingAmount;
uint256 bDivK = priceStart * orderMakingAmount / (priceEnd - priceStart);
return (Math.sqrt(
(
b * bDivK +
Expand Down
Loading

0 comments on commit e83596c

Please sign in to comment.