forked from tokencard/contracts
-
Notifications
You must be signed in to change notification settings - Fork 0
/
tokenWhitelist.sol
371 lines (322 loc) · 17.4 KB
/
tokenWhitelist.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
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
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
/**
* TokenWhitelist - The Consumer Contract Wallet
* Copyright (C) 2019 The Contract Wallet Company Limited
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
pragma solidity 0.5.17;
import "./internals/controllable.sol";
import "./internals/transferrable.sol";
import "./internals/bytesUtils.sol";
import "./externals/strings.sol";
import "./externals/SafeMath.sol";
/// @title The ITokenWhitelist interface provides access to a whitelist of tokens.
interface ITokenWhitelist {
function getTokenInfo(address) external view returns (string memory, uint256, uint256, bool, bool, bool, uint256);
function getStablecoinInfo() external view returns (string memory, uint256, uint256, bool, bool, bool, uint256);
function tokenAddressArray() external view returns (address[] memory);
function redeemableTokens() external view returns (address[] memory);
function methodIdWhitelist(bytes4) external view returns (bool);
function getERC20RecipientAndAmount(address, bytes calldata) external view returns (address, uint256);
function stablecoin() external view returns (address);
function updateTokenRate(address, uint256, uint256) external;
}
/// @title TokenWhitelist stores a list of tokens used by the Consumer Contract Wallet, the Oracle, the TKN Holder and the TKN Licence Contract
contract TokenWhitelist is ENSResolvable, Controllable, Transferrable {
using strings for *;
using SafeMath for uint256;
using BytesUtils for bytes;
event UpdatedTokenRate(address _sender, address _token, uint256 _rate);
event UpdatedTokenLoadable(address _sender, address _token, bool _loadable);
event UpdatedTokenRedeemable(address _sender, address _token, bool _redeemable);
event AddedToken(address _sender, address _token, string _symbol, uint256 _magnitude, bool _loadable, bool _redeemable);
event RemovedToken(address _sender, address _token);
event AddedMethodId(bytes4 _methodId);
event RemovedMethodId(bytes4 _methodId);
event AddedExclusiveMethod(address _token, bytes4 _methodId);
event RemovedExclusiveMethod(address _token, bytes4 _methodId);
event Claimed(address _to, address _asset, uint256 _amount);
/// @dev these are the methods whitelisted by default in executeTransaction() for protected tokens
bytes4 private constant _APPROVE = 0x095ea7b3; // keccak256(approve(address,uint256)) => 0x095ea7b3
bytes4 private constant _BURN = 0x42966c68; // keccak256(burn(uint256)) => 0x42966c68
bytes4 private constant _TRANSFER = 0xa9059cbb; // keccak256(transfer(address,uint256)) => 0xa9059cbb
bytes4 private constant _TRANSFER_FROM = 0x23b872dd; // keccak256(transferFrom(address,address,uint256)) => 0x23b872dd
struct Token {
string symbol; // Token symbol
uint256 magnitude; // 10^decimals
uint256 rate; // Token exchange rate in wei
bool available; // Flags if the token is available or not
bool loadable; // Flags if token is loadable to the TokenCard
bool redeemable; // Flags if token is redeemable in the TKN Holder contract
uint256 lastUpdate; // Time of the last rate update
}
mapping(address => Token) private _tokenInfoMap;
// @notice specifies whitelisted methodIds for protected tokens in wallet's excuteTranaction() e.g. keccak256(transfer(address,uint256)) => 0xa9059cbb
mapping(bytes4 => bool) private _methodIdWhitelist;
address[] private _tokenAddressArray;
/// @notice keeping track of how many redeemable tokens are in the tokenWhitelist
uint256 private _redeemableCounter;
/// @notice Address of the stablecoin.
address private _stablecoin;
/// @notice is registered ENS node identifying the oracle contract.
bytes32 private _oracleNode;
/// @notice Constructor initializes ENSResolvable, and Controllable.
/// @param _ens_ is the ENS registry address.
/// @param _oracleNode_ is the ENS node of the Oracle.
/// @param _controllerNode_ is our Controllers node.
/// @param _stablecoinAddress_ is the address of the stablecoint used by the wallet for the card load limit.
constructor(address _ens_, bytes32 _oracleNode_, bytes32 _controllerNode_, address _stablecoinAddress_) public {
_initializeENSResolvable(_ens_);
_initializeControllable(_controllerNode_);
_oracleNode = _oracleNode_;
_stablecoin = _stablecoinAddress_;
//a priori ERC20 whitelisted methods
_methodIdWhitelist[_APPROVE] = true;
_methodIdWhitelist[_BURN] = true;
_methodIdWhitelist[_TRANSFER] = true;
_methodIdWhitelist[_TRANSFER_FROM] = true;
}
modifier onlyAdminOrOracle() {
address oracleAddress = _ensResolve(_oracleNode);
require(_isAdmin(msg.sender) || msg.sender == oracleAddress, "either oracle or admin");
_;
}
/// @notice Add ERC20 tokens to the list of whitelisted tokens.
/// @param _tokens ERC20 token contract addresses.
/// @param _symbols ERC20 token names.
/// @param _magnitude 10 to the power of number of decimal places used by each ERC20 token.
/// @param _loadable is a bool that states whether or not a token is loadable to the TokenCard.
/// @param _redeemable is a bool that states whether or not a token is redeemable in the TKN Holder Contract.
/// @param _lastUpdate is a unit representing an ISO datetime e.g. 20180913153211.
function addTokens(
address[] calldata _tokens,
bytes32[] calldata _symbols,
uint256[] calldata _magnitude,
bool[] calldata _loadable,
bool[] calldata _redeemable,
uint256 _lastUpdate
) external onlyAdmin {
// Require that all parameters have the same length.
require(
_tokens.length == _symbols.length &&
_tokens.length == _magnitude.length &&
_tokens.length == _loadable.length &&
_tokens.length == _loadable.length,
"parameter lengths do not match"
);
// Add each token to the list of supported tokens.
for (uint256 i = 0; i < _tokens.length; i++) {
// Require that the token isn't already available.
require(!_tokenInfoMap[_tokens[i]].available, "token already available");
// Store the intermediate values.
string memory symbol = _symbols[i].toSliceB32().toString();
// Add the token to the token list.
_tokenInfoMap[_tokens[i]] = Token({
symbol: symbol,
magnitude: _magnitude[i],
rate: 0,
available: true,
loadable: _loadable[i],
redeemable: _redeemable[i],
lastUpdate: _lastUpdate
});
// Add the token address to the address list.
_tokenAddressArray.push(_tokens[i]);
//if the token is redeemable, increase the redeemableCounter
if (_redeemable[i]) {
_redeemableCounter = _redeemableCounter.add(1);
}
// Emit token addition event.
emit AddedToken(msg.sender, _tokens[i], symbol, _magnitude[i], _loadable[i], _redeemable[i]);
}
}
/// @notice Remove ERC20 tokens from the whitelist of tokens.
/// @param _tokens ERC20 token contract addresses.
function removeTokens(address[] calldata _tokens) external onlyAdmin {
// Delete each token object from the list of supported tokens based on the addresses provided.
for (uint256 i = 0; i < _tokens.length; i++) {
// Store the token address.
address token = _tokens[i];
//token must be available, reverts on duplicates as well
require(_tokenInfoMap[token].available, "token is not available");
//if the token is redeemable decrease the redeemableCounter
if (_tokenInfoMap[token].redeemable) {
_redeemableCounter = _redeemableCounter.sub(1);
}
// Delete the token object.
delete _tokenInfoMap[token];
// Remove the token address from the address list.
for (uint256 j = 0; j < _tokenAddressArray.length.sub(1); j++) {
if (_tokenAddressArray[j] == token) {
_tokenAddressArray[j] = _tokenAddressArray[_tokenAddressArray.length.sub(1)];
break;
}
}
_tokenAddressArray.length--;
// Emit token removal event.
emit RemovedToken(msg.sender, token);
}
}
/// @notice based on the method it returns the recipient address and amount/value, ERC20 specific.
/// @param _token ERC20 token contract address.
/// @param _data is the transaction payload.
function getERC20RecipientAndAmount(address _token, bytes calldata _data) external view returns (address, uint256) {
// Require that there exist enough bytes for encoding at least a method signature + data in the transaction payload:
// 4 (signature) + 32(address or uint256)
require(_data.length >= 4 + 32, "not enough method-encoding bytes");
// Get the method signature
bytes4 signature = _data._bytesToBytes4(0);
// Check if method Id is supported
require(isERC20MethodSupported(_token, signature), "unsupported method");
// returns the recipient's address and amount is the value to be transferred
if (signature == _BURN) {
// 4 (signature) + 32(uint256)
return (_token, _data._bytesToUint256(4));
} else if (signature == _TRANSFER_FROM) {
// 4 (signature) + 32(address) + 32(address) + 32(uint256)
require(_data.length >= 4 + 32 + 32 + 32, "not enough data for transferFrom");
return (_data._bytesToAddress(4 + 32 + 12), _data._bytesToUint256(4 + 32 + 32));
} else {
//transfer or approve
// 4 (signature) + 32(address) + 32(uint)
require(_data.length >= 4 + 32 + 32, "not enough data for transfer/appprove");
return (_data._bytesToAddress(4 + 12), _data._bytesToUint256(4 + 32));
}
}
/// @notice Toggles whether or not a token is loadable.
/// @param _token ERC20 token contract address.
/// @param _loadable bool that toggles the corresponding parameter.
function setTokenLoadable(address _token, bool _loadable) external onlyAdmin {
// Require that the token exists.
require(_tokenInfoMap[_token].available, "loadable: token is not available");
// this sets the loadable flag to the value passed in
_tokenInfoMap[_token].loadable = _loadable;
emit UpdatedTokenLoadable(msg.sender, _token, _loadable);
}
/// @notice Toggles whether or not a token is redeemable.
/// @param _token ERC20 token contract address.
/// @param _redeemable bool that toggles the corresponding parameter
function setTokenRedeemable(address _token, bool _redeemable) external onlyAdmin {
// Require that the token exists.
require(_tokenInfoMap[_token].available, "redeemable: token not available");
// If it does not change the current state i.e. _tokenInfoMap[_token].redeemable == _redeemable, revert!
require(_tokenInfoMap[_token].redeemable != _redeemable, "redeemable: no state change");
if (_redeemable) {
_redeemableCounter = _redeemableCounter.add(1);
} else {
_redeemableCounter = _redeemableCounter.sub(1);
}
// This sets the redeemable flag to the value passed in
_tokenInfoMap[_token].redeemable = _redeemable;
emit UpdatedTokenRedeemable(msg.sender, _token, _redeemable);
}
/// @notice Update ERC20 token exchange rate.
/// @param _token ERC20 token contract address.
/// @param _rate ERC20 token exchange rate in wei.
/// @param _updateDate date for the token updates. This will be compared to when oracle updates are received.
function updateTokenRate(address _token, uint256 _rate, uint256 _updateDate) external onlyAdminOrOracle {
// Require that the token exists.
require(_tokenInfoMap[_token].available, "token is not available");
// Update the token's rate.
_tokenInfoMap[_token].rate = _rate;
// Update the token's last update timestamp.
_tokenInfoMap[_token].lastUpdate = _updateDate;
// Emit the rate update event.
emit UpdatedTokenRate(msg.sender, _token, _rate);
}
//// @notice Withdraw tokens from the smart contract to the specified account.
function claim(address payable _to, address _asset, uint256 _amount) external onlyAdmin {
_safeTransfer(_to, _asset, _amount);
emit Claimed(_to, _asset, _amount);
}
/// @notice This returns all of the fields for a given token.
/// @param _a is the address of a given token.
/// @return string of the token's symbol.
/// @return uint of the token's magnitude.
/// @return uint of the token's exchange rate to ETH.
/// @return bool whether the token is available.
/// @return bool whether the token is loadable to the TokenCard.
/// @return bool whether the token is redeemable to the TKN Holder Contract.
/// @return uint of the lastUpdated time of the token's exchange rate.
function getTokenInfo(address _a) external view returns (string memory, uint256, uint256, bool, bool, bool, uint256) {
Token storage tokenInfo = _tokenInfoMap[_a];
return (tokenInfo.symbol, tokenInfo.magnitude, tokenInfo.rate, tokenInfo.available, tokenInfo.loadable, tokenInfo.redeemable, tokenInfo.lastUpdate);
}
/// @notice This returns all of the fields for our StableCoin.
/// @return string of the token's symbol.
/// @return uint of the token's magnitude.
/// @return uint of the token's exchange rate to ETH.
/// @return bool whether the token is available.
/// @return bool whether the token is loadable to the TokenCard.
/// @return bool whether the token is redeemable to the TKN Holder Contract.
/// @return uint of the lastUpdated time of the token's exchange rate.
function getStablecoinInfo() external view returns (string memory, uint256, uint256, bool, bool, bool, uint256) {
Token storage stablecoinInfo = _tokenInfoMap[_stablecoin];
return (
stablecoinInfo.symbol,
stablecoinInfo.magnitude,
stablecoinInfo.rate,
stablecoinInfo.available,
stablecoinInfo.loadable,
stablecoinInfo.redeemable,
stablecoinInfo.lastUpdate
);
}
/// @notice This returns an array of all whitelisted token addresses.
/// @return address[] of whitelisted tokens.
function tokenAddressArray() external view returns (address[] memory) {
return _tokenAddressArray;
}
/// @notice This returns an array of all redeemable token addresses.
/// @return address[] of redeemable tokens.
function redeemableTokens() external view returns (address[] memory) {
address[] memory redeemableAddresses = new address[](_redeemableCounter);
uint256 redeemableIndex = 0;
for (uint256 i = 0; i < _tokenAddressArray.length; i++) {
address token = _tokenAddressArray[i];
if (_tokenInfoMap[token].redeemable) {
redeemableAddresses[redeemableIndex] = token;
redeemableIndex += 1;
}
}
return redeemableAddresses;
}
/// @notice This returns true if a method Id is supported for the specific token.
/// @param _token ERC20 token contract address.
/// @param _methodId The first four bytes of the Keccak256 hash of the function signature...
/// ...i.e the canonical expression of the basic prototype without data location specifier.
/// @return True if _methodId is supported in general or just for the specific token.
function isERC20MethodSupported(address _token, bytes4 _methodId) public view returns (bool) {
require(_tokenInfoMap[_token].available, "non-existing token");
return (_methodIdWhitelist[_methodId]);
}
/// @notice This returns true if the method is supported for all protected tokens.
/// @return true if _methodId is in the method whitelist.
function isERC20MethodWhitelisted(bytes4 _methodId) external view returns (bool) {
return (_methodIdWhitelist[_methodId]);
}
/// @notice This returns the number of redeemable tokens.
/// @return current # of redeemables.
function redeemableCounter() external view returns (uint256) {
return _redeemableCounter;
}
/// @notice This returns the address of our stablecoin of choice.
/// @return the address of the stablecoin contract.
function stablecoin() external view returns (address) {
return _stablecoin;
}
/// @notice this returns the node hash of our Oracle.
/// @return the oracle node registered in ENS.
function oracleNode() external view returns (bytes32) {
return _oracleNode;
}
}