Skip to content

Commit 7ddad3f

Browse files
authored
Merge d4347b2 into 2b54a4f
2 parents 2b54a4f + d4347b2 commit 7ddad3f

File tree

7 files changed

+712
-3
lines changed

7 files changed

+712
-3
lines changed

contracts/ConditionalSwap.sol

Lines changed: 140 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,140 @@
1+
// SPDX-License-Identifier: MIT
2+
pragma solidity 0.8.17;
3+
4+
import { IConditionalSwap } from "./interfaces/IConditionalSwap.sol";
5+
import { IStrategy } from "./interfaces/IStrategy.sol";
6+
import { TokenCollector } from "./abstracts/TokenCollector.sol";
7+
import { Ownable } from "./abstracts/Ownable.sol";
8+
import { EIP712 } from "./abstracts/EIP712.sol";
9+
import { Asset } from "./libraries/Asset.sol";
10+
import { SignatureValidator } from "./libraries/SignatureValidator.sol";
11+
import { ConOrder, getConOrderHash } from "./libraries/ConditionalOrder.sol";
12+
13+
/// @title ConditionalSwap Contract
14+
/// @author imToken Labs
15+
contract ConditionalSwap is IConditionalSwap, Ownable, TokenCollector, EIP712 {
16+
using Asset for address;
17+
18+
uint256 private constant FLG_SINGLE_AMOUNT_CAP_MASK = 1 << 255; // ConOrder.amount is the cap of single execution, not total cap
19+
uint256 private constant FLG_PERIODIC_MASK = 1 << 254; // ConOrder can be executed periodically
20+
uint256 private constant FLG_PARTIAL_FILL_MASK = 1 << 253; // ConOrder can be fill partially
21+
uint256 private constant PERIOD_MASK = (1 << 128) - 1; // this is a 128-bit mask where all bits are set to 1
22+
23+
// record how many taker tokens have been filled in an order
24+
mapping(bytes32 => uint256) public orderHashToTakerTokenFilledAmount;
25+
mapping(bytes32 => uint256) public orderHashToLastExecutedTime;
26+
mapping(address => mapping(address => bool)) public makerToRelayer;
27+
28+
constructor(address _owner, address _uniswapPermit2, address _allowanceTarget) Ownable(_owner) TokenCollector(_uniswapPermit2, _allowanceTarget) {}
29+
30+
//@note if this contract has the ability to transfer out ETH, implement the receive function
31+
// receive() external {}
32+
33+
function fillConOrder(
34+
ConOrder calldata order,
35+
bytes calldata takerSignature,
36+
uint256 takerTokenAmount,
37+
uint256 makerTokenAmount,
38+
bytes calldata settlementData
39+
) external payable override {
40+
if (block.timestamp > order.expiry) revert ExpiredOrder();
41+
if (msg.sender != order.maker && !makerToRelayer[order.maker][msg.sender]) revert NotOrderExecutor();
42+
if (order.recipient == address(0)) revert InvalidRecipient();
43+
if (takerTokenAmount == 0) revert ZeroTokenAmount();
44+
45+
// validate takerSignature
46+
bytes32 orderHash = getConOrderHash(order);
47+
if (orderHashToTakerTokenFilledAmount[orderHash] == 0) {
48+
if (!SignatureValidator.validateSignature(order.taker, getEIP712Hash(orderHash), takerSignature)) {
49+
revert InvalidSignature();
50+
}
51+
}
52+
53+
// validate the takerTokenAmount
54+
if (order.flagsAndPeriod & FLG_SINGLE_AMOUNT_CAP_MASK != 0) {
55+
// single cap amount
56+
if (takerTokenAmount > order.takerTokenAmount) revert InvalidTakingAmount();
57+
} else {
58+
// total cap amount
59+
if (orderHashToTakerTokenFilledAmount[orderHash] + takerTokenAmount > order.takerTokenAmount) {
60+
revert InvalidTakingAmount();
61+
}
62+
}
63+
orderHashToTakerTokenFilledAmount[orderHash] += takerTokenAmount;
64+
65+
// validate the makerTokenAmounts
66+
uint256 minMakerTokenAmount;
67+
if (order.flagsAndPeriod & FLG_PARTIAL_FILL_MASK != 0) {
68+
// support partial fill
69+
minMakerTokenAmount = (takerTokenAmount * order.makerTokenAmount) / order.takerTokenAmount;
70+
} else {
71+
if (takerTokenAmount != order.takerTokenAmount) revert InvalidTakingAmount();
72+
minMakerTokenAmount = order.makerTokenAmount;
73+
}
74+
if (makerTokenAmount < minMakerTokenAmount) revert InvalidMakingAmount();
75+
76+
// validate time constrain
77+
if (order.flagsAndPeriod & FLG_PERIODIC_MASK != 0) {
78+
uint256 duration = order.flagsAndPeriod & PERIOD_MASK;
79+
if (block.timestamp - orderHashToLastExecutedTime[orderHash] < duration) revert InsufficientTimePassed();
80+
orderHashToLastExecutedTime[orderHash] = block.timestamp;
81+
}
82+
83+
bytes1 settlementType = settlementData[0];
84+
bytes memory strategyData = settlementData[1:];
85+
86+
uint256 returnedAmount;
87+
if (settlementType == 0x0) {
88+
// direct settlement type
89+
returnedAmount = makerTokenAmount;
90+
91+
_collect(order.takerToken, order.taker, msg.sender, takerTokenAmount, order.takerTokenPermit);
92+
_collect(order.makerToken, msg.sender, order.recipient, makerTokenAmount, order.takerTokenPermit);
93+
} else if (settlementType == 0x01) {
94+
// strategy settlement type
95+
(address strategy, bytes memory data) = abi.decode(strategyData, (address, bytes));
96+
_collect(order.takerToken, order.taker, strategy, takerTokenAmount, order.takerTokenPermit);
97+
98+
uint256 makerTokenBalanceBefore = order.makerToken.getBalance(address(this));
99+
//@todo Create a separate strategy contract specifically for conditionalSwap
100+
IStrategy(strategy).executeStrategy(order.takerToken, order.makerToken, takerTokenAmount, data);
101+
returnedAmount = order.makerToken.getBalance(address(this)) - makerTokenBalanceBefore;
102+
103+
// We only compare returnedAmount with makerTokenAmount here
104+
// because we ensure that makerTokenAmount is greater than minMakerTokenAmount before
105+
if (returnedAmount < makerTokenAmount) revert InsufficientOutput();
106+
order.makerToken.transferTo(order.recipient, returnedAmount);
107+
} else revert InvalidSettlementType();
108+
109+
_emitConOrderFilled(order, orderHash, takerTokenAmount, returnedAmount);
110+
}
111+
112+
function addRelayers(address[] calldata relayers) external {
113+
// the relayers is stored in calldata, there is no need to cache the relayers length
114+
for (uint256 i; i < relayers.length; ++i) {
115+
makerToRelayer[msg.sender][relayers[i]] = true;
116+
emit AddRelayer(msg.sender, relayers[i]);
117+
}
118+
}
119+
120+
function removeRelayers(address[] calldata relayers) external {
121+
// the relayers is stored in calldata, there is no need to cache the relayers length
122+
for (uint256 i; i < relayers.length; ++i) {
123+
delete makerToRelayer[msg.sender][relayers[i]];
124+
emit RemoveRelayer(msg.sender, relayers[i]);
125+
}
126+
}
127+
128+
function _emitConOrderFilled(ConOrder calldata order, bytes32 orderHash, uint256 takerTokenSettleAmount, uint256 makerTokenSettleAmount) internal {
129+
emit ConditionalOrderFilled(
130+
orderHash,
131+
order.taker,
132+
order.maker,
133+
order.takerToken,
134+
takerTokenSettleAmount,
135+
order.makerToken,
136+
makerTokenSettleAmount,
137+
order.recipient
138+
);
139+
}
140+
}
Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
// SPDX-License-Identifier: MIT
2+
pragma solidity 0.8.17;
3+
4+
import { ConOrder } from "../libraries/ConditionalOrder.sol";
5+
6+
interface IConditionalSwap {
7+
error ExpiredOrder();
8+
error InsufficientTimePassed();
9+
error InvalidSignature();
10+
error ZeroTokenAmount();
11+
error InvalidTakingAmount();
12+
error InvalidMakingAmount();
13+
error InsufficientOutput();
14+
error NotOrderExecutor();
15+
error InvalidRecipient();
16+
error InvalidSettlementType();
17+
18+
/// @notice Emitted when a conditional order is filled
19+
event ConditionalOrderFilled(
20+
bytes32 indexed orderHash,
21+
address indexed taker,
22+
address indexed maker,
23+
address takerToken,
24+
uint256 takerTokenFilledAmount,
25+
address makerToken,
26+
uint256 makerTokenSettleAmount,
27+
address recipient
28+
);
29+
30+
event AddRelayer(address indexed maker, address indexed relayer);
31+
32+
event RemoveRelayer(address indexed maker, address indexed relayer);
33+
34+
// function
35+
function fillConOrder(
36+
ConOrder calldata order,
37+
bytes calldata takerSignature,
38+
uint256 takerTokenAmount,
39+
uint256 makerTokenAmount,
40+
bytes calldata settlementData
41+
) external payable;
42+
}
Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
// SPDX-License-Identifier: MIT
2+
pragma solidity ^0.8.0;
3+
4+
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)";
5+
6+
bytes32 constant CONORDER_DATA_TYPEHASH = keccak256(bytes(CONORDER_TYPESTRING));
7+
8+
// @note remember to modify the CONORDER_TYPESTRING if modify the ConOrder struct
9+
struct ConOrder {
10+
address taker;
11+
address payable maker; // only maker can fill this ConOrder
12+
address payable recipient;
13+
address takerToken; // from user to maker
14+
uint256 takerTokenAmount;
15+
address makerToken; // from maker to recipient
16+
uint256 makerTokenAmount;
17+
bytes takerTokenPermit;
18+
uint256 flagsAndPeriod; // first 16 bytes as flags, rest as period duration
19+
uint256 expiry;
20+
uint256 salt;
21+
}
22+
23+
// solhint-disable-next-line func-visibility
24+
function getConOrderHash(ConOrder memory order) pure returns (bytes32 conOrderHash) {
25+
conOrderHash = keccak256(
26+
abi.encode(
27+
CONORDER_DATA_TYPEHASH,
28+
order.taker,
29+
order.maker,
30+
order.recipient,
31+
order.takerToken,
32+
order.takerTokenAmount,
33+
order.makerToken,
34+
order.makerTokenAmount,
35+
keccak256(order.takerTokenPermit),
36+
order.flagsAndPeriod,
37+
order.expiry,
38+
order.salt
39+
)
40+
);
41+
}

package.json

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -13,9 +13,9 @@
1313
"format": "prettier --write .",
1414
"check-pretty": "prettier --check .",
1515
"lint": "solhint \"contracts/**/*.sol\"",
16-
"compile": "forge build --force",
17-
"test-foundry-local": "DEPLOYED=false forge test --no-match-path 'test/forkMainnet/*.t.sol'",
18-
"test-foundry-fork": "DEPLOYED=false forge test --fork-url $MAINNET_NODE_RPC_URL --fork-block-number 17900000 --match-path 'test/forkMainnet/*.t.sol'",
16+
"compile": "forge build --force --via-ir",
17+
"test-foundry-local": "DEPLOYED=false forge test --via-ir --no-match-path 'test/forkMainnet/*.t.sol'",
18+
"test-foundry-fork": "DEPLOYED=false forge test --via-ir --fork-url $MAINNET_NODE_RPC_URL --fork-block-number 17900000 --match-path 'test/forkMainnet/*.t.sol'",
1919
"gas-report-local": "yarn test-foundry-local --gas-report",
2020
"gas-report-fork": "yarn test-foundry-fork --gas-report"
2121
},

0 commit comments

Comments
 (0)