-
Notifications
You must be signed in to change notification settings - Fork 23
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
[WIP] Implement a conditional swap #304
Open
alex0207s
wants to merge
10
commits into
master
Choose a base branch
from
implement-conditional-swap
base: master
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Open
Changes from all commits
Commits
Show all changes
10 commits
Select commit
Hold shift + click to select a range
1201ae7
add a conditionalSwap skelton
alex0207s 374475d
implement fillConOrder function
alex0207s a647fbb
add test functions for the conditional swap
alex0207s 68f223e
add `--via-ir` parameter to avoid stack too deep error
alex0207s 4067352
add a sign function for conditional order
alex0207s 48fbfef
refine code based on comment and add some test functions
alex0207s 712a687
refine code based on comments, add partial fill test cases, and refac…
alex0207s 344d6a4
add a feature for a maker to maintain a relayer list
alex0207s fad1859
fix the period mask
alex0207s d4347b2
fix order hash function
alex0207s File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,140 @@ | ||
// SPDX-License-Identifier: MIT | ||
pragma solidity 0.8.17; | ||
|
||
import { IConditionalSwap } from "./interfaces/IConditionalSwap.sol"; | ||
import { IStrategy } from "./interfaces/IStrategy.sol"; | ||
import { TokenCollector } from "./abstracts/TokenCollector.sol"; | ||
import { Ownable } from "./abstracts/Ownable.sol"; | ||
import { EIP712 } from "./abstracts/EIP712.sol"; | ||
import { Asset } from "./libraries/Asset.sol"; | ||
import { SignatureValidator } from "./libraries/SignatureValidator.sol"; | ||
import { ConOrder, getConOrderHash } from "./libraries/ConditionalOrder.sol"; | ||
|
||
/// @title ConditionalSwap Contract | ||
/// @author imToken Labs | ||
contract ConditionalSwap is IConditionalSwap, Ownable, TokenCollector, EIP712 { | ||
using Asset for address; | ||
|
||
uint256 private constant FLG_SINGLE_AMOUNT_CAP_MASK = 1 << 255; // ConOrder.amount is the cap of single execution, not total cap | ||
uint256 private constant FLG_PERIODIC_MASK = 1 << 254; // ConOrder can be executed periodically | ||
uint256 private constant FLG_PARTIAL_FILL_MASK = 1 << 253; // ConOrder can be fill partially | ||
uint256 private constant PERIOD_MASK = (1 << 128) - 1; // this is a 128-bit mask where all bits are set to 1 | ||
|
||
// record how many taker tokens have been filled in an order | ||
mapping(bytes32 => uint256) public orderHashToTakerTokenFilledAmount; | ||
mapping(bytes32 => uint256) public orderHashToLastExecutedTime; | ||
mapping(address => mapping(address => bool)) public makerToRelayer; | ||
|
||
constructor(address _owner, address _uniswapPermit2, address _allowanceTarget) Ownable(_owner) TokenCollector(_uniswapPermit2, _allowanceTarget) {} | ||
|
||
//@note if this contract has the ability to transfer out ETH, implement the receive function | ||
// receive() external {} | ||
|
||
function fillConOrder( | ||
ConOrder calldata order, | ||
bytes calldata takerSignature, | ||
uint256 takerTokenAmount, | ||
uint256 makerTokenAmount, | ||
bytes calldata settlementData | ||
) external payable override { | ||
if (block.timestamp > order.expiry) revert ExpiredOrder(); | ||
if (msg.sender != order.maker && !makerToRelayer[order.maker][msg.sender]) revert NotOrderExecutor(); | ||
if (order.recipient == address(0)) revert InvalidRecipient(); | ||
if (takerTokenAmount == 0) revert ZeroTokenAmount(); | ||
|
||
// validate takerSignature | ||
bytes32 orderHash = getConOrderHash(order); | ||
if (orderHashToTakerTokenFilledAmount[orderHash] == 0) { | ||
if (!SignatureValidator.validateSignature(order.taker, getEIP712Hash(orderHash), takerSignature)) { | ||
revert InvalidSignature(); | ||
} | ||
} | ||
|
||
// validate the takerTokenAmount | ||
if (order.flagsAndPeriod & FLG_SINGLE_AMOUNT_CAP_MASK != 0) { | ||
// single cap amount | ||
if (takerTokenAmount > order.takerTokenAmount) revert InvalidTakingAmount(); | ||
} else { | ||
// total cap amount | ||
if (orderHashToTakerTokenFilledAmount[orderHash] + takerTokenAmount > order.takerTokenAmount) { | ||
revert InvalidTakingAmount(); | ||
} | ||
} | ||
orderHashToTakerTokenFilledAmount[orderHash] += takerTokenAmount; | ||
|
||
// validate the makerTokenAmounts | ||
uint256 minMakerTokenAmount; | ||
if (order.flagsAndPeriod & FLG_PARTIAL_FILL_MASK != 0) { | ||
// support partial fill | ||
minMakerTokenAmount = (takerTokenAmount * order.makerTokenAmount) / order.takerTokenAmount; | ||
} else { | ||
if (takerTokenAmount != order.takerTokenAmount) revert InvalidTakingAmount(); | ||
minMakerTokenAmount = order.makerTokenAmount; | ||
} | ||
if (makerTokenAmount < minMakerTokenAmount) revert InvalidMakingAmount(); | ||
|
||
// validate time constrain | ||
if (order.flagsAndPeriod & FLG_PERIODIC_MASK != 0) { | ||
uint256 duration = order.flagsAndPeriod & PERIOD_MASK; | ||
if (block.timestamp - orderHashToLastExecutedTime[orderHash] < duration) revert InsufficientTimePassed(); | ||
orderHashToLastExecutedTime[orderHash] = block.timestamp; | ||
} | ||
|
||
bytes1 settlementType = settlementData[0]; | ||
bytes memory strategyData = settlementData[1:]; | ||
|
||
uint256 returnedAmount; | ||
if (settlementType == 0x0) { | ||
// direct settlement type | ||
returnedAmount = makerTokenAmount; | ||
|
||
_collect(order.takerToken, order.taker, msg.sender, takerTokenAmount, order.takerTokenPermit); | ||
_collect(order.makerToken, msg.sender, order.recipient, makerTokenAmount, order.takerTokenPermit); | ||
} else if (settlementType == 0x01) { | ||
// strategy settlement type | ||
(address strategy, bytes memory data) = abi.decode(strategyData, (address, bytes)); | ||
_collect(order.takerToken, order.taker, strategy, takerTokenAmount, order.takerTokenPermit); | ||
|
||
uint256 makerTokenBalanceBefore = order.makerToken.getBalance(address(this)); | ||
//@todo Create a separate strategy contract specifically for conditionalSwap | ||
IStrategy(strategy).executeStrategy(order.takerToken, order.makerToken, takerTokenAmount, data); | ||
returnedAmount = order.makerToken.getBalance(address(this)) - makerTokenBalanceBefore; | ||
|
||
// We only compare returnedAmount with makerTokenAmount here | ||
// because we ensure that makerTokenAmount is greater than minMakerTokenAmount before | ||
if (returnedAmount < makerTokenAmount) revert InsufficientOutput(); | ||
order.makerToken.transferTo(order.recipient, returnedAmount); | ||
} else revert InvalidSettlementType(); | ||
|
||
_emitConOrderFilled(order, orderHash, takerTokenAmount, returnedAmount); | ||
} | ||
|
||
function addRelayers(address[] calldata relayers) external { | ||
// the relayers is stored in calldata, there is no need to cache the relayers length | ||
for (uint256 i; i < relayers.length; ++i) { | ||
makerToRelayer[msg.sender][relayers[i]] = true; | ||
emit AddRelayer(msg.sender, relayers[i]); | ||
} | ||
} | ||
|
||
function removeRelayers(address[] calldata relayers) external { | ||
// the relayers is stored in calldata, there is no need to cache the relayers length | ||
for (uint256 i; i < relayers.length; ++i) { | ||
delete makerToRelayer[msg.sender][relayers[i]]; | ||
emit RemoveRelayer(msg.sender, relayers[i]); | ||
} | ||
} | ||
|
||
function _emitConOrderFilled(ConOrder calldata order, bytes32 orderHash, uint256 takerTokenSettleAmount, uint256 makerTokenSettleAmount) internal { | ||
emit ConditionalOrderFilled( | ||
orderHash, | ||
order.taker, | ||
order.maker, | ||
order.takerToken, | ||
takerTokenSettleAmount, | ||
order.makerToken, | ||
makerTokenSettleAmount, | ||
order.recipient | ||
); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,42 @@ | ||
// SPDX-License-Identifier: MIT | ||
pragma solidity 0.8.17; | ||
|
||
import { ConOrder } from "../libraries/ConditionalOrder.sol"; | ||
|
||
interface IConditionalSwap { | ||
error ExpiredOrder(); | ||
error InsufficientTimePassed(); | ||
error InvalidSignature(); | ||
error ZeroTokenAmount(); | ||
error InvalidTakingAmount(); | ||
error InvalidMakingAmount(); | ||
error InsufficientOutput(); | ||
error NotOrderExecutor(); | ||
error InvalidRecipient(); | ||
error InvalidSettlementType(); | ||
|
||
/// @notice Emitted when a conditional order is filled | ||
event ConditionalOrderFilled( | ||
bytes32 indexed orderHash, | ||
address indexed taker, | ||
address indexed maker, | ||
address takerToken, | ||
uint256 takerTokenFilledAmount, | ||
address makerToken, | ||
uint256 makerTokenSettleAmount, | ||
address recipient | ||
); | ||
|
||
event AddRelayer(address indexed maker, address indexed relayer); | ||
|
||
event RemoveRelayer(address indexed maker, address indexed relayer); | ||
|
||
// function | ||
function fillConOrder( | ||
ConOrder calldata order, | ||
bytes calldata takerSignature, | ||
uint256 takerTokenAmount, | ||
uint256 makerTokenAmount, | ||
bytes calldata settlementData | ||
) external payable; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,41 @@ | ||
// SPDX-License-Identifier: MIT | ||
pragma solidity ^0.8.0; | ||
|
||
string constant CONORDER_TYPESTRING = "ConOrder(address taker,address maker,address recipient,address takerToken,uint256 takerTokenAmount,address makerToken,uint256 makerTokenAmount,bytes takerTokenPermit,uint256 flagsAndPeriod,uint256 expiry,uint256 salt)"; | ||
|
||
bytes32 constant CONORDER_DATA_TYPEHASH = keccak256(bytes(CONORDER_TYPESTRING)); | ||
|
||
// @note remember to modify the CONORDER_TYPESTRING if modify the ConOrder struct | ||
struct ConOrder { | ||
address taker; | ||
address payable maker; // only maker can fill this ConOrder | ||
address payable recipient; | ||
address takerToken; // from user to maker | ||
uint256 takerTokenAmount; | ||
address makerToken; // from maker to recipient | ||
uint256 makerTokenAmount; | ||
bytes takerTokenPermit; | ||
uint256 flagsAndPeriod; // first 16 bytes as flags, rest as period duration | ||
uint256 expiry; | ||
uint256 salt; | ||
} | ||
|
||
// solhint-disable-next-line func-visibility | ||
function getConOrderHash(ConOrder memory order) pure returns (bytes32 conOrderHash) { | ||
conOrderHash = keccak256( | ||
abi.encode( | ||
CONORDER_DATA_TYPEHASH, | ||
order.taker, | ||
order.maker, | ||
order.recipient, | ||
order.takerToken, | ||
order.takerTokenAmount, | ||
order.makerToken, | ||
order.makerTokenAmount, | ||
keccak256(order.takerTokenPermit), | ||
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.
|
||
order.flagsAndPeriod, | ||
order.expiry, | ||
order.salt | ||
) | ||
); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
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.
move declaration into the
settlementType == 0x01
blockThere 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.
fix
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.
As discussed, this should not be changed.