-
Notifications
You must be signed in to change notification settings - Fork 82
Expand file tree
/
Copy pathSablierEscrow.sol
More file actions
288 lines (235 loc) · 11.3 KB
/
Copy pathSablierEscrow.sol
File metadata and controls
288 lines (235 loc) · 11.3 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
// SPDX-License-Identifier: GPL-3.0-or-later
pragma solidity >=0.8.22;
import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import { SafeERC20 } from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";
import { ud, UD60x18 } from "@prb/math/src/UD60x18.sol";
import { Comptrollerable } from "@sablier/evm-utils/src/Comptrollerable.sol";
import { SablierEscrowState } from "./abstracts/SablierEscrowState.sol";
import { ISablierEscrow } from "./interfaces/ISablierEscrow.sol";
import { Errors } from "./libraries/Errors.sol";
import { Escrow } from "./types/Escrow.sol";
/*
███████╗ █████╗ ██████╗ ██╗ ██╗███████╗██████╗ ███████╗███████╗ ██████╗██████╗ ██████╗ ██╗ ██╗
██╔════╝██╔══██╗██╔══██╗██║ ██║██╔════╝██╔══██╗ ██╔════╝██╔════╝██╔════╝██╔══██╗██╔═══██╗██║ ██║
███████╗███████║██████╔╝██║ ██║█████╗ ██████╔╝ █████╗ ███████╗██║ ██████╔╝██║ ██║██║ █╗ ██║
╚════██║██╔══██║██╔══██╗██║ ██║██╔══╝ ██╔══██╗ ██╔══╝ ╚════██║██║ ██╔══██╗██║ ██║██║███╗██║
███████║██║ ██║██████╔╝███████╗██║███████╗██║ ██║ ███████╗███████║╚██████╗██║ ██║╚██████╔╝╚███╔███╔╝
╚══════╝╚═╝ ╚═╝╚═════╝ ╚══════╝╚═╝╚══════╝╚═╝ ╚═╝ ╚══════╝╚══════╝ ╚═════╝╚═╝ ╚═╝ ╚═════╝ ╚══╝╚══╝
*/
/// @title SablierEscrow
/// @notice See the documentation in {ISablierEscrow}.
contract SablierEscrow is
Comptrollerable, // 1 inherited component
ISablierEscrow, // 2 inherited components
SablierEscrowState // 1 inherited component
{
using SafeERC20 for IERC20;
/*//////////////////////////////////////////////////////////////////////////
CONSTRUCTOR
//////////////////////////////////////////////////////////////////////////*/
/// @param initialComptroller The address of the initial comptroller contract.
/// @param initialTradeFee The initial trade fee percentage.
constructor(
address initialComptroller,
UD60x18 initialTradeFee
)
Comptrollerable(initialComptroller)
SablierEscrowState(initialTradeFee)
{ }
/*//////////////////////////////////////////////////////////////////////////
USER-FACING STATE-CHANGING FUNCTIONS
//////////////////////////////////////////////////////////////////////////*/
/// @inheritdoc ISablierEscrow
function cancelOrder(uint256 orderId) external override notNull(orderId) {
Escrow.Order storage order = _orders[orderId];
// Check: the caller is the seller.
if (msg.sender != order.seller) {
revert Errors.SablierEscrow_CallerNotAuthorized(orderId, msg.sender, order.seller);
}
// Check: the order has not been filled.
if (order.wasFilled) {
revert Errors.SablierEscrow_OrderFilled(orderId);
}
// Check: the order has not been canceled.
if (order.wasCanceled) {
revert Errors.SablierEscrow_OrderCancelled(orderId);
}
// Effect: mark the order as canceled.
order.wasCanceled = true;
// Cache the sell amount for this order.
uint128 sellAmount = order.sellAmount;
// Interaction: transfer sell tokens to caller.
order.sellToken.safeTransfer(msg.sender, sellAmount);
// Log the event.
emit CancelOrder({ orderId: orderId, seller: msg.sender, sellAmount: sellAmount });
}
/// @inheritdoc ISablierEscrow
function createOrder(
IERC20 sellToken,
uint128 sellAmount,
IERC20 buyToken,
uint128 minBuyAmount,
address buyer,
uint40 expiryTime
)
external
override
returns (uint256 orderId)
{
// Check: sell token is not the zero address.
if (address(sellToken) == address(0)) {
revert Errors.SablierEscrow_SellTokenZero();
}
// Check: sell token is not the native token.
if (address(sellToken) == nativeToken) {
revert Errors.SablierEscrow_ForbidNativeToken(address(sellToken));
}
// Check: buy token is not the zero address.
if (address(buyToken) == address(0)) {
revert Errors.SablierEscrow_BuyTokenZero();
}
// Check: buy token is not the native token.
if (address(buyToken) == nativeToken) {
revert Errors.SablierEscrow_ForbidNativeToken(address(buyToken));
}
// Check: sell and buy tokens are not the same.
if (sellToken == buyToken) {
revert Errors.SablierEscrow_SameToken(sellToken);
}
// Check: sell amount is not zero.
if (sellAmount == 0) {
revert Errors.SablierEscrow_SellAmountZero();
}
// Check: minimum buy amount is not zero.
if (minBuyAmount == 0) {
revert Errors.SablierEscrow_MinBuyAmountZero();
}
// Check: expiryTime is in the future. Zero is sentinel for orders that never expire.
if (expiryTime > 0 && expiryTime <= block.timestamp) {
revert Errors.SablierEscrow_ExpiryTimeInPast(expiryTime, uint40(block.timestamp));
}
// Use the current next order ID as this order's ID.
orderId = nextOrderId;
// Effect: increment the next order ID.
unchecked {
nextOrderId = orderId + 1;
}
// Effect: create the order.
_orders[orderId] = Escrow.Order({
seller: msg.sender,
buyer: buyer,
sellToken: sellToken,
buyToken: buyToken,
sellAmount: sellAmount,
minBuyAmount: minBuyAmount,
expiryTime: expiryTime,
wasCanceled: false,
wasFilled: false
});
// Interaction: transfer sell tokens from caller to this contract.
sellToken.safeTransferFrom(msg.sender, address(this), sellAmount);
// Log the event.
emit CreateOrder({
orderId: orderId,
seller: msg.sender,
buyer: buyer,
sellToken: sellToken,
buyToken: buyToken,
sellAmount: sellAmount,
minBuyAmount: minBuyAmount,
expiryTime: expiryTime
});
}
/// @inheritdoc ISablierEscrow
function fillOrder(
uint256 orderId,
uint128 buyAmount
)
external
override
notNull(orderId)
returns (
uint128 amountToTransferToSeller,
uint128 amountToTransferToBuyer,
uint128 feeDeductedFromBuyerAmount,
uint128 feeDeductedFromSellerAmount
)
{
// Check: the order is open.
Escrow.Status status = _statusOf(orderId);
if (status != Escrow.Status.OPEN) {
revert Errors.SablierEscrow_OrderNotOpen(orderId, status);
}
Escrow.Order storage order = _orders[orderId];
// Check: if the order has buyer specified, the caller must be the buyer.
if (order.buyer != address(0) && msg.sender != order.buyer) {
revert Errors.SablierEscrow_CallerNotAuthorized(orderId, msg.sender, order.buyer);
}
// Check: the buy amount meets the minimum asked.
if (buyAmount < order.minBuyAmount) {
revert Errors.SablierEscrow_InsufficientBuyAmount(buyAmount, order.minBuyAmount);
}
// Effect: mark the order as filled.
order.wasFilled = true;
// Get the trade fee from storage.
UD60x18 currentTradeFee = tradeFee;
amountToTransferToSeller = buyAmount;
amountToTransferToBuyer = order.sellAmount;
// If the fee is non-zero, deduct the fee from both sides.
if (currentTradeFee.unwrap() > 0) {
// Calculate the fee on the sell amount.
feeDeductedFromBuyerAmount = ud(amountToTransferToBuyer).mul(currentTradeFee).intoUint128();
amountToTransferToBuyer -= feeDeductedFromBuyerAmount;
// Calculate the fee on the buy amount.
feeDeductedFromSellerAmount = ud(buyAmount).mul(currentTradeFee).intoUint128();
amountToTransferToSeller -= feeDeductedFromSellerAmount;
// Set comptroller as the fee recipient.
address feeRecipient = address(comptroller);
// Interaction: transfer fees to the fee recipient.
order.buyToken.safeTransferFrom(msg.sender, feeRecipient, feeDeductedFromSellerAmount);
order.sellToken.safeTransfer(feeRecipient, feeDeductedFromBuyerAmount);
}
// Cache the seller address.
address seller = order.seller;
// Interaction: transfer buy token to the seller.
order.buyToken.safeTransferFrom(msg.sender, seller, amountToTransferToSeller);
// Interaction: transfer sell tokens to the buyer.
order.sellToken.safeTransfer(msg.sender, amountToTransferToBuyer);
// Log the event.
emit FillOrder({
orderId: orderId,
buyer: msg.sender,
seller: seller,
sellAmount: amountToTransferToBuyer,
buyAmount: amountToTransferToSeller,
feeDeductedFromBuyerAmount: feeDeductedFromBuyerAmount,
feeDeductedFromSellerAmount: feeDeductedFromSellerAmount
});
}
/// @inheritdoc ISablierEscrow
function setNativeToken(address newNativeToken) external override onlyComptroller {
// Check: provided token is not zero address.
if (newNativeToken == address(0)) {
revert Errors.SablierEscrow_NativeTokenZeroAddress();
}
// Check: native token is not set.
if (nativeToken != address(0)) {
revert Errors.SablierEscrow_NativeTokenAlreadySet(nativeToken);
}
// Effect: set the native token.
nativeToken = newNativeToken;
// Log the update.
emit SetNativeToken({ comptroller: msg.sender, nativeToken: newNativeToken });
}
/// @inheritdoc ISablierEscrow
function setTradeFee(UD60x18 newTradeFee) external override onlyComptroller {
// Check: the new trade fee does not exceed the maximum trade fee.
_notTooHigh(newTradeFee);
// Cache the current trade fee for the event.
UD60x18 previousTradeFee = tradeFee;
// Effect: set the new trade fee.
tradeFee = newTradeFee;
// Log the event.
emit SetTradeFee({ caller: msg.sender, previousTradeFee: previousTradeFee, newTradeFee: newTradeFee });
}
}