-
Notifications
You must be signed in to change notification settings - Fork 2
Expand file tree
/
Copy pathLitGhost.sol
More file actions
477 lines (368 loc) 路 12.4 KB
/
Copy pathLitGhost.sol
File metadata and controls
477 lines (368 loc) 路 12.4 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
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
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
// SPDX-License-Identifier: AGPL3.0-only
pragma solidity ^0.8.0;
import {IERC20} from './IERC20.sol';
import {IERC3009} from './IERC3009.sol';
interface IERC20_With_Extensions is IERC20, IERC3009 {}
struct Leaf {
bytes4[6] encryptedBalances;
uint32 idx;
uint32 nonce;
}
struct Signature {
uint8 v;
bytes32 r;
bytes32 s;
}
struct DepositTo {
bytes32 rand;
bytes32 user;
}
struct Auth3009 {
address from;
uint256 value;
uint256 validAfter;
uint256 validBefore;
Signature sig;
}
struct Payout {
address toWho;
uint256 amount;
}
struct Deposit3009 {
DepositTo to;
Auth3009 auth;
}
struct OpCounters {
uint64 opCount;
uint64 processedOps;
uint32 userCount;
uint64 lastProcessedBlock;
}
struct UserInfo {
uint32 userIndex;
Leaf leaf;
}
struct Entropy {
string ciphertext;
bytes32 digest;
string ipfsCid;
Signature sig;
bytes32 teeEncPublicKey;
}
function packLeaf(Leaf memory leaf) pure returns (bytes32) {
// Manual packing to avoid abi.encodePacked bug with calldata arrays
bytes32 packed = bytes32(abi.encodePacked(
leaf.encryptedBalances[0],
leaf.encryptedBalances[1],
leaf.encryptedBalances[2],
leaf.encryptedBalances[3],
leaf.encryptedBalances[4],
leaf.encryptedBalances[5],
leaf.idx,
leaf.nonce
));
return packed;
}
contract LitGhost {
uint8 constant internal DECIMALS = 2;
mapping(uint32 => Leaf) internal m_leaves;
mapping(bytes32 => uint32) internal m_userIndices;
mapping(uint32 => bytes32) m_indexToUser;
OpCounters internal m_counters;
address internal m_owner;
IERC20_With_Extensions internal m_token;
uint256 m_dust;
mapping(address => uint256) m_failedWithdraw;
uint8 immutable m_decimals;
event OpDeposit(uint64 indexed idx, bytes32 randKey, bytes32 toUser, uint32 amount, address from);
event LeafChange(uint32 indexed idx, bytes32 leaf);
Entropy internal m_entropy;
bool internal m_initialized;
function setEntropy(Entropy calldata in_entropy)
public
{
// Can only be set once, to initialize it
require( m_initialized == false, "403.1" );
// Verify the signature matches the entropy data
bytes32 dataHash = in_entropy.digest;
bytes memory ciphertextBytes = bytes(in_entropy.ciphertext);
bytes memory cidBytes = bytes(in_entropy.ipfsCid);
bytes32 messageHash = keccak256(abi.encodePacked(dataHash, ciphertextBytes, cidBytes));
address recovered = ecrecover(
messageHash,
in_entropy.sig.v,
in_entropy.sig.r,
in_entropy.sig.s
);
require(recovered == msg.sender, "403.2");
m_entropy = in_entropy;
m_owner = msg.sender;
m_initialized = true;
}
function getOwner()
public view returns (address)
{
return m_owner;
}
function getEntropy()
public view returns (Entropy memory)
{
return m_entropy;
}
function getTeePublicKey()
public view returns (bytes32)
{
return m_entropy.teeEncPublicKey;
}
constructor(IERC20_With_Extensions in_token)
{
m_owner = msg.sender;
m_token = in_token;
m_decimals = in_token.decimals();
// Initialize userCount to 1, treating user ID 0 as a sentinel value
// This allows us to distinguish "user doesn't exist" (returns 0) from actual users (>= 1)
m_counters.userCount = 1;
// Initialize lastProcessedBlock to deployment block
// Manager will process deposits starting from the block after deployment
m_counters.lastProcessedBlock = uint64(block.number);
}
function getLeaves(uint32[] calldata leafIndices)
public view returns (Leaf[] memory leaves)
{
uint n = leafIndices.length;
leaves = new Leaf[](n);
for( uint i = 0; i < n; i++ )
{
leaves[i] = m_leaves[leafIndices[i]];
}
}
function getUserLeaves(bytes32[] calldata encryptedUserIdList)
public view returns (uint32[] memory userLeafIndices)
{
uint n = encryptedUserIdList.length;
userLeafIndices = new uint32[](n);
for( uint i = 0; i < n; i++ )
{
userLeafIndices[i] = m_userIndices[encryptedUserIdList[i]];
}
}
function getUserPublicKeys(uint32[] calldata userIndices)
public view returns (bytes32[] memory publicKeys)
{
uint n = userIndices.length;
publicKeys = new bytes32[](n);
for( uint i = 0; i < n; i++ )
{
publicKeys[i] = m_indexToUser[userIndices[i]];
}
}
function decimals ()
public pure returns (uint8)
{
return DECIMALS;
}
function getStatus()
public view returns (OpCounters memory counters, uint256 dust)
{
counters = m_counters;
dust = m_dust;
}
function getUserInfo(bytes32 encryptedUserId)
public view returns (UserInfo memory info)
{
info.userIndex = m_userIndices[encryptedUserId];
if (info.userIndex > 0) {
uint32 leafIdx = (info.userIndex) / 6;
info.leaf = m_leaves[leafIdx];
}
}
function getUserInfoBatch(bytes32[] calldata encryptedUserIds)
public view returns (UserInfo[] memory infos)
{
uint n = encryptedUserIds.length;
infos = new UserInfo[](n);
for (uint i = 0; i < n; i++) {
infos[i] = getUserInfo(encryptedUserIds[i]);
}
}
function getUpdateContext(bytes32[] calldata encryptedUserIds)
public view returns (
OpCounters memory counters,
uint256 dust,
UserInfo[] memory userInfos
)
{
counters = m_counters;
dust = m_dust;
userInfos = getUserInfoBatch(encryptedUserIds);
}
function _convertToTwoDecimals(uint256 amount, uint8 inputDecimals)
internal pure returns (uint32 roundedAmount, uint256 dust)
{
require(inputDecimals >= DECIMALS, "DECIMALS1!");
uint8 decimalDiff = inputDecimals - DECIMALS;
uint256 divisor = 10 ** decimalDiff;
uint256 rounded = amount / divisor;
require(rounded <= type(uint32).max, "DECIMALS2!");
roundedAmount = uint32(rounded);
dust = amount % divisor;
}
function _finishDeposit(DepositTo calldata to, uint256 in_amount, address from)
internal
{
(uint32 leafAmount, uint256 leafDust) = _convertToTwoDecimals(in_amount, m_decimals);
require( leafAmount < ((2**32)*(10**DECIMALS)), "401!" );
if( leafDust > 0 )
{
m_dust += leafDust;
}
m_counters.opCount += 1;
emit OpDeposit(m_counters.opCount, to.rand, to.user, leafAmount, from);
}
function _safeTransfer(address to, uint256 amount)
internal returns (bool)
{
try m_token.transfer(to, amount) returns (bool success)
{
return success;
}
catch {
return false;
}
}
// See `blindUserId` in packages/core/src/crypto.ts to get DepositTo
// Only user can use ERC-20 deposit to pull their own tokens
// Any other case must use ERC-3009, which uses receiveWithAuthorization
function depositERC20(DepositTo calldata to, uint256 in_amount)
public
{
_depositFromERC20(to, in_amount, msg.sender);
}
function _depositFromERC20(DepositTo calldata to, uint256 in_amount, address in_from)
internal
{
uint256 bb = m_token.balanceOf(address(this));
m_token.transferFrom(in_from, address(this), in_amount);
uint256 ba = m_token.balanceOf(address(this));
require( (bb + in_amount) == ba, "500!" );
_finishDeposit(to, in_amount, in_from);
}
function depositERC3009(DepositTo calldata to, Auth3009 calldata auth)
public
{
depositERC3009WithIncentive(to, auth, 0);
}
// See `blindUserId` in packages/core/src/crypto.ts to get DepositTo
// callerIncentive lets MEV bots deposit for you
function depositERC3009WithIncentive(DepositTo calldata to, Auth3009 calldata auth, uint256 callerIncentive)
public
{
require( (auth.value - callerIncentive) > 0, "400!" );
bytes32 nonce = keccak256(abi.encode(to, callerIncentive));
uint256 depositAmount = auth.value - callerIncentive;
uint256 bb = m_token.balanceOf(address(this));
m_token.receiveWithAuthorization(auth.from, address(this), auth.value, auth.validAfter, auth.validBefore, nonce, auth.sig.v, auth.sig.r, auth.sig.s);
uint256 ba = m_token.balanceOf(address(this));
require( (bb + auth.value) == ba, "500!" );
_finishDeposit(to, depositAmount, auth.from);
if( callerIncentive > 0 )
{
m_token.transfer(msg.sender, callerIncentive);
}
}
function depositManyERC3009(DepositTo[] calldata to, Auth3009[] calldata auth, uint256[] calldata callerIncentive)
public
{
require( to.length == auth.length, "400.1!" );
require( auth.length == callerIncentive.length, "400.2!" );
uint n = to.length;
for( uint i = 0; i < n; i++ )
{
depositERC3009WithIncentive(to[i], auth[i], callerIncentive[i]);
}
}
function doUpdate(
uint64 in_opStart,
uint64 in_opCount,
uint64 in_nextBlock,
Leaf[] calldata in_updates,
bytes32[] calldata in_newUsers,
Payout[] calldata in_pay,
bytes32 in_transcript
)
public
{
// NOTE: any changes to the transcript also need modifying in packages/core/src/transcript.ts
require( msg.sender == m_owner, "403" );
// Load counters into memory
OpCounters memory counters = m_counters;
require( counters.processedOps == in_opStart, "Invalid opStart" );
// Update leaves
uint256 lc = in_updates.length;
bytes32 transcript = keccak256(abi.encode(in_opStart, in_opCount, in_nextBlock, lc));
for( uint256 i = 0; i < lc; i++ )
{
Leaf calldata leaf = in_updates[i];
transcript = keccak256(abi.encode(transcript, m_leaves[leaf.idx], leaf));
m_leaves[leaf.idx] = leaf;
emit LeafChange(leaf.idx, packLeaf(leaf));
}
// Insert new users
uint32 nul = uint32(in_newUsers.length);
uint32 uc = counters.userCount;
transcript = keccak256(abi.encode(transcript, uc, nul));
for( uint32 i = 0; i < nul; i++ )
{
uint32 nui = uc+i;
transcript = keccak256(abi.encode(transcript, nui, in_newUsers[i]));
m_userIndices[in_newUsers[i]] = nui;
m_indexToUser[nui] = in_newUsers[i];
}
counters.userCount += nul;
// Perform payouts
uint256 pc = in_pay.length;
transcript = keccak256(abi.encode(transcript, pc));
for( uint256 i = 0; i < pc; i++ )
{
Payout calldata p = in_pay[i];
transcript = keccak256(abi.encode(transcript, p));
// Failure paranoia, anything fails, just say we tried our best, owner can fetch it later
if (!_safeTransfer(p.toWho, p.amount))
{
m_failedWithdraw[p.toWho] += p.amount;
}
}
require( transcript == in_transcript, "500!" );
// Update counters and save back to storage
counters.processedOps += in_opCount;
counters.lastProcessedBlock = in_nextBlock;
m_counters = counters;
}
// NOTE: because the ERC20 token may not do the transfer (if it's centralized trash)
// Owner decides who to send funds to, it's their choice
function handleFailedWithdraw(address in_to)
public returns (bool)
{
uint amount = m_failedWithdraw[msg.sender];
if( amount > 0 )
{
m_failedWithdraw[msg.sender] = 0;
if (_safeTransfer(in_to, amount))
{
return true;
}
m_failedWithdraw[msg.sender] = amount;
}
return false;
}
function collectDust()
public
{
uint256 dust = m_dust;
if (dust > 0)
{
m_dust = 0;
require(_safeTransfer(msg.sender, dust), "Dust transfer failed");
}
}
}