-
Notifications
You must be signed in to change notification settings - Fork 1
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Feat/operator restrictions #4
base: feat/generic-sanity-checks
Are you sure you want to change the base?
Changes from 35 commits
6a9cd63
305fe99
74b3fe4
f7620c6
f514692
9650e28
c77178c
e2bf7f6
de85df4
8009baa
a396f4c
9aaac8a
f762069
410a120
2f7baa3
a419357
99211c4
aa21e29
1646350
c8ede00
a9af474
8821760
44d6b9f
edee642
5479a53
7ea9136
583ae79
3010816
9ed479a
52a9bea
5d1d7e3
a09b9b1
dd87423
5c9f6b5
d2a300a
efe5a3b
48d19f2
caa07f0
564a5e4
20e8888
6a6b68f
caf8f71
6af3d10
31880f4
ccaaaf0
4508197
c4921e1
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,31 +1,85 @@ | ||
// SPDX-License-Identifier: MIT | ||
pragma solidity 0.8.21; | ||
|
||
import {EIP712, ECDSA} from "openzeppelin/utils/cryptography/EIP712.sol"; | ||
import {IERC1271} from "openzeppelin/interfaces/IERC1271.sol"; | ||
import {OrderEngine} from "./libraries/OrderEngine.sol"; | ||
import {IPreInteractionNotificationReceiver} from "./interfaces/IPreInteractionNotificationReceiver.sol"; | ||
import {IPostInteractionNotificationReceiver} from "./interfaces/IPostInteractionNotificationReceiver.sol"; | ||
|
||
import {IInteractionNotificationReceiver} from "./interfaces/IInteractionNotificationReceiver.sol"; | ||
|
||
import {Decoder} from "./libraries/Decoder.sol"; | ||
import "./AdvancedOrderEngineErrors.sol"; | ||
import {Vault} from "./Vault.sol"; | ||
import {Ownable2Step} from "openzeppelin/access/Ownable2Step.sol"; | ||
|
||
contract AdvancedOrderEngine is Vault, Ownable2Step, EIP712 { | ||
// TBD: consider making extraData a separate param | ||
// TBD: consider making interfaces generic | ||
// TBD: consider changing data type to IERC20 of buy and sell token | ||
// TBD: consider allowing facilitator to tell offeredAmounts in its interaction | ||
|
||
contract AdvancedOrderEngine { | ||
using OrderEngine for OrderEngine.Order; | ||
using Decoder for bytes; | ||
|
||
mapping(address => bool) public isOperator; | ||
|
||
event OrderFill( | ||
address operator, | ||
address maker, | ||
bytes32 orderHash, | ||
uint256 offeredAmount | ||
); | ||
event OperatorAccessModified(address indexed authorized, bool access); | ||
|
||
constructor( | ||
string memory name, | ||
string memory version | ||
) EIP712(name, version) {} | ||
|
||
modifier onlyOperator() { | ||
if (!isOperator[msg.sender]) { | ||
revert NotAnOperator(msg.sender); | ||
} | ||
_; | ||
} | ||
|
||
function manageOperatorPrivilege( | ||
address _address, | ||
bool _access | ||
) external onlyOwner { | ||
if (_address == address(0)) { | ||
revert ZeroAddress(); | ||
} | ||
|
||
// TBD: should we not allow if owner is trying to set same access? (con: additional gas) | ||
// Overwrites the access previously granted. | ||
isOperator[_address] = _access; | ||
|
||
emit OperatorAccessModified(_address, _access); | ||
} | ||
|
||
/** | ||
* @notice Fills multiple orders by processing the specified orders and clearing prices. | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. @0xhammadghazi There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. acknowledged |
||
* | ||
* @param orders An array of order structs representing the orders to be filled. | ||
* @param clearingPrices An array of clearing prices that the facilitator is offering to the makers. | ||
* @param facilitatorInteractionCalldata The calldata for the facilitator's interaction. | ||
* @param facilitatorInteractionTargetContract The address of the facilitator's target contract. | ||
* @param offeredAmounts An array of clearing prices that the facilitator is offering to the makers. | ||
* @param facilitatorInteraction The calldata for the facilitator's interaction. | ||
*/ | ||
function fillOrders( | ||
OrderEngine.Order[] calldata orders, | ||
uint256[] calldata clearingPrices, | ||
bytes calldata facilitatorInteractionCalldata, | ||
address facilitatorInteractionTargetContract | ||
) external { | ||
uint256[] calldata offeredAmounts, | ||
bytes[] calldata signatures, | ||
bytes calldata facilitatorInteraction | ||
) external onlyOperator { | ||
// TBD: Private orders? | ||
|
||
// TBD: max array length check needed? Considering fn will be restricted to operators only | ||
|
||
/** | ||
TBD: no need to check for clearingPrices length to be equal to 0 as if that's the case, txn will revert in subsequent check | ||
but should we check for clearingPrices length to be equal to zero explicitly for better error reporting? | ||
TBD: no need to check for offeredAmounts length to be equal to 0 as if that's the case, txn will revert in subsequent check | ||
but should we check for offeredAmounts length to be equal to zero explicitly for better error reporting? | ||
Also consider generic error message | ||
*/ | ||
// Revert if the orders array length is zero. | ||
|
@@ -34,30 +88,137 @@ contract AdvancedOrderEngine { | |
} | ||
|
||
// Revert if the length of the orders array does not match the clearing prices array. | ||
if (orders.length != clearingPrices.length) { | ||
revert ArraysLengthMismatch(orders.length, clearingPrices.length); | ||
if (orders.length != offeredAmounts.length) { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. @0xhammadghazi There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. yes indeed |
||
revert ArraysLengthMismatch(orders.length, offeredAmounts.length); | ||
} | ||
|
||
for (uint256 i; i < orders.length; ) { | ||
OrderEngine.Order calldata order = orders[i]; | ||
bytes calldata signature = signatures[i]; | ||
|
||
bytes32 orderHash = order.hash(); | ||
bytes32 orderMessageHash = _hashTypedDataV4(orderHash); | ||
|
||
if (block.timestamp > order.validTill) { | ||
revert OrderExpired(orderHash); | ||
} | ||
|
||
if (order.buyTokenAmount == 0 || order.sellTokenAmount == 0) { | ||
revert ZeroTokenAmounts(); | ||
} | ||
|
||
if ( | ||
order.maker == address(0) || | ||
order.buyToken == address(0) || | ||
order.sellToken == address(0) || | ||
order.recipient == address(0) | ||
) { | ||
revert ZeroAddress(); | ||
} | ||
|
||
// STUB: PARTIAL FILL FEATURE // | ||
|
||
// TBD: debatable, can take signing scheme in order schema or can verify like 1inch | ||
if (order.isContract()) { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Can we make seperate function for processing orders from contract or account like 1inch is doing , better for readibility.. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Okay, I will take it into consideration. |
||
if ( | ||
!(IERC1271(order.maker).isValidSignature( | ||
orderMessageHash, | ||
signature | ||
) == IERC1271.isValidSignature.selector) | ||
) { | ||
revert BadSignature(); | ||
} | ||
} else { | ||
address signer = ECDSA.recover(orderMessageHash, signature); | ||
if (signer != order.maker) { | ||
revert BadSignature(); | ||
} | ||
} | ||
|
||
// STUB: VERIFY PREDICATES // | ||
|
||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. @0xhammadghazi Will there be any more checks related to predicates ? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I'm not sure yet, I will work on the predicates section tomorrow, so only then can I tell. |
||
if (order.preInteraction.length >= 20) { | ||
// proceed only if interaction length is enough to store address | ||
( | ||
address interactionTarget, | ||
bytes calldata interactionData | ||
) = order.preInteraction.decodeTargetAndCalldata(); | ||
IPreInteractionNotificationReceiver(interactionTarget) | ||
.fillOrderPreInteraction( | ||
orderMessageHash, | ||
order.maker, | ||
offeredAmounts[i], | ||
interactionData | ||
); | ||
} | ||
|
||
// TODO: reorder params type | ||
_receiveAsset(order.sellToken, order.sellTokenAmount, order.maker); | ||
|
||
unchecked { | ||
++i; | ||
} | ||
} | ||
|
||
// Revert if the facilitator has provided calldata for its interaction but has provided null target contract address. | ||
if ( | ||
facilitatorInteractionCalldata.length != 0 && | ||
facilitatorInteractionTargetContract == address(0) | ||
) { | ||
revert ZeroFacilitatorTargetAddress(); // TBD: use generic error message i.e. ZeroAddress() | ||
if (facilitatorInteraction.length >= 20) { | ||
// proceed only if interaction length is enough to store address | ||
( | ||
address interactionTarget, | ||
bytes calldata interactionData | ||
) = facilitatorInteraction.decodeTargetAndCalldata(); | ||
IInteractionNotificationReceiver(interactionTarget) | ||
.fillOrderInteraction( | ||
msg.sender, | ||
orders, | ||
offeredAmounts, | ||
interactionData | ||
); | ||
} | ||
|
||
// Loop start | ||
// Perform order specific sanity checks | ||
// Verify signatjures | ||
// Verify predicates | ||
// Call pre-interaction hook | ||
// Transfer funds from maker to vault | ||
// Loop end | ||
// Call facilitator interaction | ||
// Loop start | ||
// Ensure facilitator is respecting maker price | ||
// Transfer funds from vault to maker | ||
// Call post-interaction hook | ||
// Emit event (decide where to emit event, as its considered as an effect so maybe do it somewhere in the start) | ||
// TODO: Need optimization | ||
for (uint256 i; i < orders.length; ) { | ||
OrderEngine.Order calldata order = orders[i]; | ||
|
||
bytes32 orderHash = order.hash(); | ||
bytes32 orderMessageHash = _hashTypedDataV4(orderHash); | ||
|
||
if (order.buyTokenAmount > offeredAmounts[i]) { | ||
revert LimitPriceNotRespected( | ||
order.buyTokenAmount, | ||
offeredAmounts[i] | ||
); | ||
} | ||
|
||
// TODO: reorder params type | ||
_sendAsset(order.buyToken, offeredAmounts[i], order.recipient); | ||
|
||
if (order.postInteraction.length >= 20) { | ||
// proceed only if interaction length is enough to store address | ||
( | ||
address interactionTarget, | ||
bytes calldata interactionData | ||
) = order.postInteraction.decodeTargetAndCalldata(); | ||
IPostInteractionNotificationReceiver(interactionTarget) | ||
.fillOrderPostInteraction( | ||
orderMessageHash, | ||
order.maker, | ||
offeredAmounts[i], | ||
interactionData | ||
); | ||
} | ||
|
||
// TODO: decide where to emit event, as its considered as an effect so maybe do it somewhere in the start; what params to log; | ||
// events spam? ; consider emitting just one event | ||
emit OrderFill( | ||
msg.sender, | ||
order.maker, | ||
orderMessageHash, | ||
offeredAmounts[i] | ||
); | ||
|
||
unchecked { | ||
++i; | ||
} | ||
} | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,28 @@ | ||
// SPDX-License-Identifier: MIT | ||
pragma solidity 0.8.21; | ||
|
||
import {SafeERC20} from "openzeppelin/token/ERC20/utils/SafeERC20.sol"; | ||
import {IERC20} from "openzeppelin/token/ERC20/IERC20.sol"; | ||
|
||
abstract contract Vault { | ||
using SafeERC20 for IERC20; | ||
|
||
function _receiveAsset( | ||
address asset, | ||
uint256 amount, | ||
address maker | ||
) internal { | ||
IERC20 token = _asIERC20(asset); | ||
|
||
token.safeTransferFrom(maker, address(this), amount); | ||
} | ||
|
||
function _sendAsset(address asset, uint256 amount, address maker) internal { | ||
IERC20 token = _asIERC20(asset); | ||
token.safeTransfer(maker, amount); | ||
} | ||
|
||
function _asIERC20(address asset) internal pure returns (IERC20) { | ||
return IERC20(asset); | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,24 @@ | ||
// SPDX-License-Identifier: MIT | ||
|
||
pragma solidity 0.8.21; | ||
|
||
import {OrderEngine} from "../libraries/OrderEngine.sol"; | ||
|
||
/** | ||
* @notice Interface for facilitator interaction hook, it is invoked after funds are transferred from the 'maker' to the 'vault'. | ||
*/ | ||
interface IInteractionNotificationReceiver { | ||
/** | ||
* @notice Callback method that gets called after funds transfer from the 'maker' to the 'vault'. | ||
* @param operator Address of the caller who executed orders on behalf of the facilitator. | ||
* @param orders Orders the facilitator is willing to fill. | ||
* @param offeredAmounts Amounts of the asset the facilitator is offering to the makers. | ||
* @param interactionData Interaction calldata | ||
*/ | ||
function fillOrderInteraction( | ||
address operator, | ||
OrderEngine.Order[] calldata orders, | ||
uint256[] calldata offeredAmounts, | ||
bytes memory interactionData | ||
) external; | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,22 @@ | ||
// SPDX-License-Identifier: MIT | ||
|
||
pragma solidity 0.8.21; | ||
|
||
/** | ||
* @notice Interface for maker post-interaction hook, it is invoked after funds are transferred from the 'vault' to the 'maker'. | ||
*/ | ||
interface IPostInteractionNotificationReceiver { | ||
/** | ||
* @notice Callback method that gets called after all funds transfers | ||
* @param orderHash Hash of the order being processed | ||
* @param maker Maker address | ||
* @param clearingPrice Actual amount maker will receive | ||
* @param interactionData Interaction calldata | ||
*/ | ||
function fillOrderPostInteraction( | ||
bytes32 orderHash, | ||
address maker, | ||
uint256 clearingPrice, | ||
bytes memory interactionData | ||
) external; | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,22 @@ | ||
// SPDX-License-Identifier: MIT | ||
|
||
pragma solidity 0.8.21; | ||
|
||
/** | ||
* @notice Interface for maker pre-interaction hook, it is invoked before funds are transferred from the 'maker' to the 'vault'. | ||
*/ | ||
interface IPreInteractionNotificationReceiver { | ||
/** | ||
* @notice Callback method that gets called before any funds transfers | ||
* @param orderHash Hash of the order being processed | ||
* @param maker Maker address | ||
* @param clearingPrice Actual amount maker will receive | ||
* @param interactionData Interaction calldata | ||
*/ | ||
function fillOrderPreInteraction( | ||
bytes32 orderHash, | ||
address maker, | ||
uint256 clearingPrice, | ||
bytes memory interactionData | ||
) external; | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,19 @@ | ||
// SPDX-License-Identifier: MIT | ||
pragma solidity 0.8.21; | ||
|
||
import "../AdvancedOrderEngineErrors.sol"; | ||
|
||
library Decoder { | ||
function decodeTargetAndCalldata( | ||
bytes calldata data | ||
) internal pure returns (address target, bytes calldata args) { | ||
if (data.length < 20) revert IncorrectDataLength(); | ||
// no memory ops inside so this insertion is automatically memory safe | ||
assembly { | ||
// solhint-disable-line no-inline-assembly | ||
target := shr(96, calldataload(data.offset)) | ||
args.offset := add(data.offset, 20) | ||
args.length := sub(data.length, 20) | ||
} | ||
} | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@0xhammadghazi Refactor a modifier to call a local function instead of directly having the code in the modifier, saving bytecode size and thereby deployment cost Ref
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Acknowledged. The bytecode of the function modifier is duplicated/copied to each method, but we are currently using onlyOperator in only one function. Therefore, it won't make any difference at the moment, but yes, it's a good point to consider during the gas optimization phase.