forked from layer3xyz/cubes
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Escrow.sol
252 lines (221 loc) · 9.17 KB
/
Escrow.sol
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
// SPDX-License-Identifier: Apache-2.0
/*
.____ ________
| | _____ ___.__. __________\_____ \
| | \__ \< | |/ __ \_ __ \_(__ <
| |___ / __ \\___ \ ___/| | \/ \
|_______ (____ / ____|\___ >__| /______ /
\/ \/\/ \/ \/
*/
pragma solidity 0.8.20;
import {IERC20} from "@openzeppelin/contracts/interfaces/IERC20.sol";
import {IERC721} from "@openzeppelin/contracts/interfaces/IERC721.sol";
import {IERC1155} from "@openzeppelin/contracts/interfaces/IERC1155.sol";
import {ERC721Holder} from "@openzeppelin/contracts/token/ERC721/utils/ERC721Holder.sol";
import {ERC1155Holder} from "@openzeppelin/contracts/token/ERC1155/utils/ERC1155Holder.sol";
import {Ownable2Step} from "@openzeppelin/contracts/access/Ownable2Step.sol";
import {Ownable} from "@openzeppelin/contracts/access/Ownable.sol";
import {IEscrow} from "./interfaces/IEscrow.sol";
contract Escrow is IEscrow, ERC721Holder, ERC1155Holder, Ownable2Step {
error Escrow__TokenNotWhitelisted();
error Escrow__InsufficientEscrowBalance();
error Escrow__ZeroAddress();
error Escrow__NativeRakeError();
error Escrow__NativePayoutError();
error Escrow__InvalidRakeBps();
error Escrow__ERC20TransferFailed();
error Escrow__IsNotAContract();
event EscrowERC20Transfer(
address indexed token,
address indexed to,
uint256 amount,
uint256 rake,
address rakePayoutAddress
);
event EscrowNativeTransfer(
address indexed to, uint256 amount, uint256 rake, address rakePayoutAddress
);
event EscrowERC1155Transfer(
address indexed token, address indexed to, uint256 amount, uint256 tokenId
);
event EscrowERC721Transfer(address indexed token, address indexed to, uint256 tokenId);
event TokenWhitelisted(address indexed token);
event TokenRemovedFromWhitelist(address indexed token);
bytes4 private constant TRANSFER_ERC20 = bytes4(keccak256(bytes("transfer(address,uint256)")));
address public immutable i_treasury;
uint16 constant MAX_BPS = 10_000;
uint16 constant GAS_CAP = 35_000;
mapping(address => bool) public s_whitelistedTokens;
/// @notice Initializes the escrow contract with specified whitelisted tokens and treasury address.
/// @param tokenAddr An array of addresses of tokens to whitelist upon initialization.
/// @param treasury The address of the treasury for receiving rake payments.
constructor(address _owner, address[] memory tokenAddr, address treasury) Ownable(_owner) {
i_treasury = treasury;
uint256 length = tokenAddr.length;
for (uint256 i = 0; i < length;) {
s_whitelistedTokens[tokenAddr[i]] = true;
unchecked {
++i;
}
}
}
/// @notice Adds a token to the whitelist, allowing it to be used in the escrow.
/// @param token The address of the token to whitelist.
function addTokenToWhitelist(address token) external override onlyOwner {
if (token == address(0)) {
revert Escrow__ZeroAddress();
}
s_whitelistedTokens[token] = true;
emit TokenWhitelisted(token);
}
/// @notice Removes a token from the whitelist.
/// @param token The address of the token to remove from the whitelist.
function removeTokenFromWhitelist(address token) external override onlyOwner {
s_whitelistedTokens[token] = false;
emit TokenRemovedFromWhitelist(token);
}
/// @notice Returns the ERC20 token balance held in escrow.
/// @param token The address of the token.
/// @return The balance of the specified token held in escrow.
function escrowERC20Reserves(address token) public view override returns (uint256) {
return IERC20(token).balanceOf(address(this));
}
/// @notice Returns the ERC1155 token balance held in escrow for a specific tokenId.
/// @param token The address of the token.
/// @param tokenId The ID of the token.
/// @return The balance of the specified token ID held in escrow.
function escrowERC1155Reserves(address token, uint256 tokenId)
external
view
override
returns (uint256)
{
return IERC1155(token).balanceOf(address(this), tokenId);
}
/// @notice Returns the native balance of the escrow smart contract
function escrowNativeBalance() public view override returns (uint256) {
return address(this).balance;
}
/// @notice Returns the ERC721 token balance held in escrow.
function escrowERC721BalanceOf(address token) external view override returns (uint256) {
return IERC721(token).balanceOf(address(this));
}
/// @notice Withdraws ERC20 tokens from the escrow to a specified address.
/// @dev Can only be called by the owner. Applies a rake before sending to the recipient.
/// @param token The token address.
/// @param to The recipient address.
/// @param amount The amount to withdraw.
/// @param rakeBps The basis points of the total amount to be taken as rake.
function withdrawERC20(address token, address to, uint256 amount, uint256 rakeBps)
external
override
onlyOwner
{
if (!s_whitelistedTokens[token]) {
revert Escrow__TokenNotWhitelisted();
}
if (amount > escrowERC20Reserves(token)) {
revert Escrow__InsufficientEscrowBalance();
}
if (rakeBps > MAX_BPS) {
revert Escrow__InvalidRakeBps();
}
// rake payment in basis points
uint256 rake = (amount * rakeBps) / MAX_BPS;
if (rake > 0) {
_rakePayoutERC20(token, rake);
}
_safeTransferERC20(token, to, amount - rake);
emit EscrowERC20Transfer(token, to, amount, rake, i_treasury);
}
function _rakePayoutERC20(address token, uint256 amount) internal {
_safeTransferERC20(token, i_treasury, amount);
}
function _safeTransferERC20(address token, address to, uint256 value) internal {
if (token.code.length == 0) {
revert Escrow__IsNotAContract();
}
(bool success, bytes memory data) =
token.call(abi.encodeWithSelector(TRANSFER_ERC20, to, value));
if (!success || (data.length > 0 && !abi.decode(data, (bool)))) {
revert Escrow__ERC20TransferFailed();
}
}
/// @notice Withdraws ERC721 tokens from the escrow to a specified address.
/// @dev Can only be called by the owner.
/// @param token The token address.
/// @param to The recipient address.
/// @param tokenId The token ID to withdraw.
function withdrawERC721(address token, address to, uint256 tokenId)
external
override
onlyOwner
{
if (!s_whitelistedTokens[token]) {
revert Escrow__TokenNotWhitelisted();
}
IERC721(token).safeTransferFrom(address(this), to, tokenId);
emit EscrowERC721Transfer(token, to, tokenId);
}
/// @notice Withdraws ERC1155 tokens from the escrow to a specified address.
/// @dev Can only be called by the owner.
/// @param token The token address.
/// @param to The recipient address.
/// @param amount The amount to withdraw.
/// @param tokenId The token ID to withdraw.
function withdrawERC1155(address token, address to, uint256 amount, uint256 tokenId)
external
override
onlyOwner
{
if (!s_whitelistedTokens[token]) {
revert Escrow__TokenNotWhitelisted();
}
IERC1155(token).safeTransferFrom(address(this), to, tokenId, amount, "");
emit EscrowERC1155Transfer(token, to, amount, tokenId);
}
/// @notice Withdraws native tokens from the escrow to a specified address.
/// @dev Can only be called by the owner.
/// @param to The recipient address.
/// @param amount The amount to withdraw.
/// @param rakeBps The basis points of the total amount to be taken as rake.
function withdrawNative(address to, uint256 amount, uint256 rakeBps)
external
override
onlyOwner
{
if (amount > escrowNativeBalance()) {
revert Escrow__InsufficientEscrowBalance();
}
if (to == address(0)) {
revert Escrow__ZeroAddress();
}
if (rakeBps > MAX_BPS) {
revert Escrow__InvalidRakeBps();
}
// rake payment in basis points
uint256 rake = (amount * rakeBps) / MAX_BPS;
if (rake > 0) {
(bool rakeSuccess,) = payable(i_treasury).call{value: rake}("");
if (!rakeSuccess) {
revert Escrow__NativeRakeError();
}
}
(bool rewardSuccess,) = payable(to).call{value: amount - rake, gas: GAS_CAP}("");
if (!rewardSuccess) {
revert Escrow__NativePayoutError();
}
emit EscrowNativeTransfer(to, amount, rake, i_treasury);
}
function supportsInterface(bytes4 interfaceId)
public
view
override(ERC1155Holder)
returns (bool)
{
return super.supportsInterface(interfaceId);
}
function renounceOwnership() public override onlyOwner {}
fallback() external payable {}
receive() external payable {}
}