@bananapus/suckers-v6 0.0.57 → 0.0.58
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +2 -2
- package/src/JBBaseSucker.sol +1 -1
- package/src/JBCeloSucker.sol +2 -2
- package/src/JBOptimismSucker.sol +1 -1
- package/src/JBSucker.sol +10 -16
- package/src/JBSuckerRegistry.sol +16 -5
- package/src/JBSwapCCIPSucker.sol +58 -51
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@bananapus/suckers-v6",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.58",
|
|
4
4
|
"license": "MIT",
|
|
5
5
|
"repository": {
|
|
6
6
|
"type": "git",
|
|
@@ -26,7 +26,7 @@
|
|
|
26
26
|
},
|
|
27
27
|
"dependencies": {
|
|
28
28
|
"@arbitrum/nitro-contracts": "3.2.0",
|
|
29
|
-
"@bananapus/core-v6": "^0.0.
|
|
29
|
+
"@bananapus/core-v6": "^0.0.66",
|
|
30
30
|
"@bananapus/permission-ids-v6": "^0.0.27",
|
|
31
31
|
"@chainlink/contracts-ccip": "1.6.4",
|
|
32
32
|
"@chainlink/local": "0.2.7",
|
package/src/JBBaseSucker.sol
CHANGED
|
@@ -16,7 +16,7 @@ contract JBBaseSucker is JBOptimismSucker {
|
|
|
16
16
|
// ---------------------------- constructor -------------------------- //
|
|
17
17
|
//*********************************************************************//
|
|
18
18
|
|
|
19
|
-
/// @param deployer A contract that deploys
|
|
19
|
+
/// @param deployer A contract that deploys clones of this contract.
|
|
20
20
|
/// @param directory A contract storing directories of terminals and controllers for each project.
|
|
21
21
|
/// @param permissions A contract storing permissions.
|
|
22
22
|
/// @param prices The price oracle used to convert peer-chain balances and surplus.
|
package/src/JBCeloSucker.sol
CHANGED
|
@@ -35,7 +35,7 @@ contract JBCeloSucker is JBOptimismSucker {
|
|
|
35
35
|
// ---------------------------- constructor -------------------------- //
|
|
36
36
|
//*********************************************************************//
|
|
37
37
|
|
|
38
|
-
/// @param deployer A contract that deploys
|
|
38
|
+
/// @param deployer A contract that deploys clones of this contract.
|
|
39
39
|
/// @param directory A contract storing directories of terminals and controllers for each project.
|
|
40
40
|
/// @param permissions A contract storing permissions.
|
|
41
41
|
/// @param prices The price oracle used to convert peer-chain balances and surplus.
|
|
@@ -135,7 +135,7 @@ contract JBCeloSucker is JBOptimismSucker {
|
|
|
135
135
|
{
|
|
136
136
|
index; // Silence unused parameter warning (not needed for Celo bridge).
|
|
137
137
|
|
|
138
|
-
// Revert if there's a `msg.value`. The
|
|
138
|
+
// Revert if there's a `msg.value`. The Celo bridge does not expect to be paid.
|
|
139
139
|
if (transportPayment != 0) {
|
|
140
140
|
revert JBSucker_UnexpectedMsgValue({value: transportPayment});
|
|
141
141
|
}
|
package/src/JBOptimismSucker.sol
CHANGED
|
@@ -36,7 +36,7 @@ contract JBOptimismSucker is JBSucker, IJBOptimismSucker {
|
|
|
36
36
|
// ---------------------------- constructor -------------------------- //
|
|
37
37
|
//*********************************************************************//
|
|
38
38
|
|
|
39
|
-
/// @param deployer A contract that deploys
|
|
39
|
+
/// @param deployer A contract that deploys clones of this contract.
|
|
40
40
|
/// @param directory A contract storing directories of terminals and controllers for each project.
|
|
41
41
|
/// @param permissions A contract storing permissions.
|
|
42
42
|
/// @param prices The price oracle used to convert peer-chain balances and surplus.
|
package/src/JBSucker.sol
CHANGED
|
@@ -95,12 +95,10 @@ abstract contract JBSucker is ERC2771Context, JBPermissioned, Initializable, ERC
|
|
|
95
95
|
// ------------------------- public constants ------------------------ //
|
|
96
96
|
//*********************************************************************//
|
|
97
97
|
|
|
98
|
-
/// @notice A reasonable minimum gas limit for a basic cross-chain call
|
|
99
|
-
/// the `fromRemote` (successfully/safely) on the remote chain.
|
|
98
|
+
/// @notice A reasonable minimum gas limit for a basic cross-chain call to `fromRemote` on the remote chain.
|
|
100
99
|
uint32 public constant override MESSENGER_BASE_GAS_LIMIT = 300_000;
|
|
101
100
|
|
|
102
|
-
/// @notice A reasonable minimum gas limit
|
|
103
|
-
/// (successfully/safely) perform a transfer on the remote chain.
|
|
101
|
+
/// @notice A reasonable minimum gas limit for performing an ERC-20 transfer on the remote chain.
|
|
104
102
|
uint32 public constant override MESSENGER_ERC20_MIN_GAS_LIMIT = 200_000;
|
|
105
103
|
|
|
106
104
|
/// @notice The message format version. Used to reject incompatible messages from remote chains.
|
|
@@ -601,7 +599,7 @@ abstract contract JBSucker is ERC2771Context, JBPermissioned, Initializable, ERC
|
|
|
601
599
|
account: _ownerOf(_projectId), projectId: _projectId, permissionId: JBPermissionIds.SET_SUCKER_DEPRECATION
|
|
602
600
|
});
|
|
603
601
|
|
|
604
|
-
// This is the earliest time
|
|
602
|
+
// This is the earliest time the sucker can be considered deprecated.
|
|
605
603
|
// There is a mandatory delay to allow for remaining messages to be received.
|
|
606
604
|
// This should be called on both sides of the suckers, preferably with a matching timestamp.
|
|
607
605
|
uint256 nextEarliestDeprecationTime = block.timestamp + _maxMessagingDelay();
|
|
@@ -804,7 +802,7 @@ abstract contract JBSucker is ERC2771Context, JBPermissioned, Initializable, ERC
|
|
|
804
802
|
return JBSuckerState.ENABLED;
|
|
805
803
|
}
|
|
806
804
|
|
|
807
|
-
// The sucker
|
|
805
|
+
// The sucker is close to deprecation; this state only warns users.
|
|
808
806
|
// Deprecation state is intentionally time-based.
|
|
809
807
|
// forge-lint: disable-next-line(block-timestamp)
|
|
810
808
|
if (block.timestamp < _deprecatedAfter - _maxMessagingDelay()) {
|
|
@@ -960,7 +958,7 @@ abstract contract JBSucker is ERC2771Context, JBPermissioned, Initializable, ERC
|
|
|
960
958
|
}
|
|
961
959
|
}
|
|
962
960
|
|
|
963
|
-
/// @notice
|
|
961
|
+
/// @notice Actions to perform after a user has successfully proven their claim.
|
|
964
962
|
/// @param terminalToken The terminal token to suck.
|
|
965
963
|
/// @param terminalTokenAmount The amount of terminal tokens.
|
|
966
964
|
/// @param projectTokenAmount The amount of project tokens.
|
|
@@ -1204,10 +1202,8 @@ abstract contract JBSucker is ERC2771Context, JBPermissioned, Initializable, ERC
|
|
|
1204
1202
|
}
|
|
1205
1203
|
|
|
1206
1204
|
/// @notice Send the outbox root for the specified token to the remote peer.
|
|
1207
|
-
/// @dev
|
|
1208
|
-
///
|
|
1209
|
-
/// @param transportPayment the amount of `msg.value` that is going to get paid for sending this message. (usually
|
|
1210
|
-
/// derived from `msg.value`)
|
|
1205
|
+
/// @dev Some bridges require a nonzero `transportPayment`; zero-cost bridges must reject nonzero values.
|
|
1206
|
+
/// @param transportPayment The amount of `msg.value` paid to the transport for this message.
|
|
1211
1207
|
/// @param token The terminal token to bridge the merkle tree of.
|
|
1212
1208
|
/// @param remoteToken The remote token which the `token` is mapped to.
|
|
1213
1209
|
function _sendRoot(uint256 transportPayment, address token, JBRemoteToken memory remoteToken) internal virtual {
|
|
@@ -1324,8 +1320,7 @@ abstract contract JBSucker is ERC2771Context, JBPermissioned, Initializable, ERC
|
|
|
1324
1320
|
// Register the leaf as executed to prevent double-spending.
|
|
1325
1321
|
_executedFor[terminalToken].set(index);
|
|
1326
1322
|
|
|
1327
|
-
// Calculate the root
|
|
1328
|
-
// Compare to the current root, Revert if they do not match.
|
|
1323
|
+
// Calculate the root and compare it to the current inbox root.
|
|
1329
1324
|
_validateBranchRoot({
|
|
1330
1325
|
expectedRoot: _inboxOf[terminalToken].root,
|
|
1331
1326
|
projectTokenCount: projectTokenCount,
|
|
@@ -1371,7 +1366,7 @@ abstract contract JBSucker is ERC2771Context, JBPermissioned, Initializable, ERC
|
|
|
1371
1366
|
index: index
|
|
1372
1367
|
});
|
|
1373
1368
|
|
|
1374
|
-
//
|
|
1369
|
+
// Revert if the computed root does not match the expected inbox root.
|
|
1375
1370
|
if (root != expectedRoot) {
|
|
1376
1371
|
revert JBSucker_InvalidProof({root: root, inboxRoot: expectedRoot});
|
|
1377
1372
|
}
|
|
@@ -1453,8 +1448,7 @@ abstract contract JBSucker is ERC2771Context, JBPermissioned, Initializable, ERC
|
|
|
1453
1448
|
_executedFor[emergencyExitAddress].set(index);
|
|
1454
1449
|
}
|
|
1455
1450
|
|
|
1456
|
-
// Calculate the root
|
|
1457
|
-
// Compare to the current root, Revert if they do not match.
|
|
1451
|
+
// Calculate the root and compare it to the current outbox root.
|
|
1458
1452
|
_validateBranchRoot({
|
|
1459
1453
|
expectedRoot: _computeOutboxRoot(_outboxOf[terminalToken].tree),
|
|
1460
1454
|
projectTokenCount: projectTokenCount,
|
package/src/JBSuckerRegistry.sol
CHANGED
|
@@ -35,6 +35,7 @@ contract JBSuckerRegistry is ERC2771Context, Ownable, JBPermissioned, IJBSuckerR
|
|
|
35
35
|
error JBSuckerRegistry_InvalidDeployer(IJBSuckerDeployer deployer);
|
|
36
36
|
error JBSuckerRegistry_SuckerDoesNotBelongToProject(uint256 projectId, address sucker);
|
|
37
37
|
error JBSuckerRegistry_SuckerIsNotDeprecated(address sucker, JBSuckerState suckerState);
|
|
38
|
+
error JBSuckerRegistry_ZeroPeerChainId(address sucker);
|
|
38
39
|
|
|
39
40
|
//*********************************************************************//
|
|
40
41
|
// ------------------------- public constants ------------------------ //
|
|
@@ -142,8 +143,9 @@ contract JBSuckerRegistry is ERC2771Context, Ownable, JBPermissioned, IJBSuckerR
|
|
|
142
143
|
(, uint256 val) = _suckersOf[projectId].tryGet(allSuckers[i]);
|
|
143
144
|
if (val == _SUCKER_EXISTS) {
|
|
144
145
|
IJBSucker sucker = IJBSucker(allSuckers[i]);
|
|
145
|
-
pairs[j] =
|
|
146
|
-
|
|
146
|
+
pairs[j] = JBSuckersPair({
|
|
147
|
+
local: address(sucker), remote: sucker.peer(), remoteChainId: _peerChainIdOf(sucker)
|
|
148
|
+
});
|
|
147
149
|
unchecked {
|
|
148
150
|
++j;
|
|
149
151
|
}
|
|
@@ -220,7 +222,7 @@ contract JBSuckerRegistry is ERC2771Context, Ownable, JBPermissioned, IJBSuckerR
|
|
|
220
222
|
try IJBSucker(allSuckers[i]).peerChainBalanceOf({decimals: decimals, currency: currency}) returns (
|
|
221
223
|
JBDenominatedAmount memory amt
|
|
222
224
|
) {
|
|
223
|
-
uint256 chainId = IJBSucker(allSuckers[i])
|
|
225
|
+
uint256 chainId = _peerChainIdOf(IJBSucker(allSuckers[i]));
|
|
224
226
|
scratch.chainCount = _recordPeerValue({
|
|
225
227
|
scratch: scratch,
|
|
226
228
|
chainId: chainId,
|
|
@@ -276,7 +278,7 @@ contract JBSuckerRegistry is ERC2771Context, Ownable, JBPermissioned, IJBSuckerR
|
|
|
276
278
|
try IJBSucker(allSuckers[i]).peerChainSurplusOf({decimals: decimals, currency: currency}) returns (
|
|
277
279
|
JBDenominatedAmount memory amt
|
|
278
280
|
) {
|
|
279
|
-
uint256 chainId = IJBSucker(allSuckers[i])
|
|
281
|
+
uint256 chainId = _peerChainIdOf(IJBSucker(allSuckers[i]));
|
|
280
282
|
scratch.chainCount = _recordPeerValue({
|
|
281
283
|
scratch: scratch,
|
|
282
284
|
chainId: chainId,
|
|
@@ -319,7 +321,7 @@ contract JBSuckerRegistry is ERC2771Context, Ownable, JBPermissioned, IJBSuckerR
|
|
|
319
321
|
// Include both active and deprecated suckers in aggregate economic views.
|
|
320
322
|
if (val == _SUCKER_EXISTS || val == _SUCKER_DEPRECATED) {
|
|
321
323
|
try IJBSucker(allSuckers[i]).peerChainTotalSupply() returns (uint256 supply) {
|
|
322
|
-
uint256 chainId = IJBSucker(allSuckers[i])
|
|
324
|
+
uint256 chainId = _peerChainIdOf(IJBSucker(allSuckers[i]));
|
|
323
325
|
scratch.chainCount = _recordPeerValue({
|
|
324
326
|
scratch: scratch,
|
|
325
327
|
chainId: chainId,
|
|
@@ -377,6 +379,14 @@ contract JBSuckerRegistry is ERC2771Context, Ownable, JBPermissioned, IJBSuckerR
|
|
|
377
379
|
scratch.hasActiveValue = new bool[](len);
|
|
378
380
|
}
|
|
379
381
|
|
|
382
|
+
/// @notice Reads a sucker's peer chain ID, reverting if the sucker cannot identify a real peer chain.
|
|
383
|
+
/// @param sucker The sucker to query.
|
|
384
|
+
/// @return chainId The non-zero peer chain ID.
|
|
385
|
+
function _peerChainIdOf(IJBSucker sucker) internal view returns (uint256 chainId) {
|
|
386
|
+
chainId = sucker.peerChainId();
|
|
387
|
+
if (chainId == 0) revert JBSuckerRegistry_ZeroPeerChainId({sucker: address(sucker)});
|
|
388
|
+
}
|
|
389
|
+
|
|
380
390
|
/// @notice Records a project-scoped peer-chain aggregate value.
|
|
381
391
|
/// @dev Callers pass scratch arrays sized from `_suckersOf[projectId].keys()`, so entries are already scoped to
|
|
382
392
|
/// the project being aggregated. For each peer chain, active suckers replace deprecated suckers; deprecated
|
|
@@ -544,6 +554,7 @@ contract JBSuckerRegistry is ERC2771Context, Ownable, JBPermissioned, IJBSuckerR
|
|
|
544
554
|
// Create the sucker.
|
|
545
555
|
IJBSucker sucker = configuration.deployer
|
|
546
556
|
.createForSender({localProjectId: projectId, salt: salt, peer: configuration.peer});
|
|
557
|
+
_peerChainIdOf(sucker);
|
|
547
558
|
suckers[i] = address(sucker);
|
|
548
559
|
|
|
549
560
|
// Store the sucker as being deployed for this project.
|
package/src/JBSwapCCIPSucker.sol
CHANGED
|
@@ -88,6 +88,7 @@ contract JBSwapCCIPSucker is JBCCIPSucker, IUnlockCallback, IUniswapV3SwapCallba
|
|
|
88
88
|
|
|
89
89
|
error JBSwapCCIPSucker_BatchNotReceived(uint64 nonce);
|
|
90
90
|
error JBSwapCCIPSucker_CallerNotPoolManager(address caller);
|
|
91
|
+
error JBSwapCCIPSucker_DuplicateBatch(uint64 nonce);
|
|
91
92
|
error JBSwapCCIPSucker_InvalidBridgeToken(address bridgeToken, address wrappedNativeToken);
|
|
92
93
|
error JBSwapCCIPSucker_NoPendingSwap(address localToken, uint64 nonce, bool retrySwapLocked);
|
|
93
94
|
error JBSwapCCIPSucker_OnlySelf(address caller, address expected);
|
|
@@ -336,6 +337,19 @@ contract JBSwapCCIPSucker is JBCCIPSucker, IUnlockCallback, IUniswapV3SwapCallba
|
|
|
336
337
|
}
|
|
337
338
|
}
|
|
338
339
|
|
|
340
|
+
// Detect an already-processed batch before the swap path. The inbox nonce alone cannot be used here:
|
|
341
|
+
// CCIP can deliver nonce 2 before nonce 1, and nonce 1 still needs its self-described batch metadata.
|
|
342
|
+
if (
|
|
343
|
+
_batchEndOf[localToken][nonce] != 0 || _conversionRateOf[localToken][nonce].leafTotal != 0
|
|
344
|
+
|| pendingSwapOf[localToken][nonce].bridgeAmount != 0
|
|
345
|
+
) {
|
|
346
|
+
if (deliveredAmount != 0) {
|
|
347
|
+
revert JBSwapCCIPSucker_DuplicateBatch({nonce: nonce});
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
return;
|
|
351
|
+
}
|
|
352
|
+
|
|
339
353
|
// After the validation block above, `deliveredToken != address(0)` iff a delivery was present,
|
|
340
354
|
// because the invariants ensure it equals `BRIDGE_TOKEN` (a non-zero ERC-20) whenever there is one.
|
|
341
355
|
if (deliveredToken != address(0)) {
|
|
@@ -378,61 +392,54 @@ contract JBSwapCCIPSucker is JBCCIPSucker, IUnlockCallback, IUniswapV3SwapCallba
|
|
|
378
392
|
//
|
|
379
393
|
// Detect "already seen" without extra storage: a nonce has been processed if it has
|
|
380
394
|
// either a batch range (batchEnd > 0) or a conversion rate / pending swap recorded.
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
//
|
|
386
|
-
//
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
// batches than `uint64.max`, which the inbox can never produce.
|
|
405
|
-
uint64 priorCount = _populatedNonceCount[localToken];
|
|
406
|
-
_populatedNonceByIndex[localToken][priorCount] = nonce;
|
|
407
|
-
unchecked {
|
|
408
|
-
_populatedNonceCount[localToken] = priorCount + 1;
|
|
409
|
-
}
|
|
395
|
+
// Record the batch range so _findNonceForLeafIndex can resolve leaf ownership
|
|
396
|
+
// independently of nonce ordering. Each nonce is self-describing: [start, end).
|
|
397
|
+
if (batchEnd > 0) {
|
|
398
|
+
// Record this batch's half-open leaf range `[batchStart, batchEnd)`. Self-
|
|
399
|
+
// describing per-nonce — no implicit chain across nonces — so out-of-order
|
|
400
|
+
// delivery can still resolve a leaf to its batch.
|
|
401
|
+
_batchStartOf[localToken][nonce] = batchStart;
|
|
402
|
+
_batchEndOf[localToken][nonce] = batchEnd;
|
|
403
|
+
|
|
404
|
+
// Append `nonce` to the populated-nonce list for this token. The duplicate guard
|
|
405
|
+
// above fires at most once per (token, nonce), so each populated nonce is appended
|
|
406
|
+
// exactly once — the array stays duplicate-free without extra checks.
|
|
407
|
+
//
|
|
408
|
+
// Reading `_populatedNonceCount[localToken]` first into a local lets us write
|
|
409
|
+
// the new slot and the new count in a single read-modify-write pair (one
|
|
410
|
+
// SLOAD, two SSTOREs to distinct slots). The `unchecked` increment is safe:
|
|
411
|
+
// `priorCount` is bounded by the total number of populated nonces, which is
|
|
412
|
+
// upper-bounded by the CCIP nonce space (`uint64`) — overflow requires more
|
|
413
|
+
// batches than `uint64.max`, which the inbox can never produce.
|
|
414
|
+
uint64 priorCount = _populatedNonceCount[localToken];
|
|
415
|
+
_populatedNonceByIndex[localToken][priorCount] = nonce;
|
|
416
|
+
unchecked {
|
|
417
|
+
_populatedNonceCount[localToken] = priorCount + 1;
|
|
410
418
|
}
|
|
419
|
+
}
|
|
420
|
+
|
|
421
|
+
// Store pendingSwapOf for failed swaps now that nonce is validated.
|
|
422
|
+
if (swapFailed) {
|
|
423
|
+
pendingSwapOf[localToken][nonce] =
|
|
424
|
+
PendingSwap({bridgeToken: deliveredToken, bridgeAmount: deliveredAmount, leafTotal: leafTotal});
|
|
425
|
+
}
|
|
411
426
|
|
|
412
|
-
|
|
413
|
-
|
|
427
|
+
// Zero-output swap guard: When a swap succeeds but returns zero local tokens, the
|
|
428
|
+
// batch must NOT be marked claimable. Without this guard, `_addToBalance` would see
|
|
429
|
+
// `pendingSwapOf.bridgeAmount == 0` (no pending swap stored) and allow claims to
|
|
430
|
+
// proceed — minting the full bridged project-token amount while adding zero terminal
|
|
431
|
+
// backing, breaking cross-chain solvency.
|
|
432
|
+
//
|
|
433
|
+
// Route zero-output swaps into `pendingSwapOf` so the swap can be retried via
|
|
434
|
+
// `retrySwap` once pool conditions improve. Only store the conversion rate when
|
|
435
|
+
// the swap produced a positive local amount.
|
|
436
|
+
if (leafTotal > 0 && !swapFailed) {
|
|
437
|
+
if (localAmount == 0 && deliveredAmount > 0) {
|
|
414
438
|
pendingSwapOf[localToken][nonce] =
|
|
415
439
|
PendingSwap({bridgeToken: deliveredToken, bridgeAmount: deliveredAmount, leafTotal: leafTotal});
|
|
416
|
-
}
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
// batch must NOT be marked claimable. Without this guard, `_addToBalance` would see
|
|
420
|
-
// `pendingSwapOf.bridgeAmount == 0` (no pending swap stored) and allow claims to
|
|
421
|
-
// proceed — minting the full bridged project-token amount while adding zero terminal
|
|
422
|
-
// backing, breaking cross-chain solvency.
|
|
423
|
-
//
|
|
424
|
-
// Route zero-output swaps into `pendingSwapOf` so the swap can be retried via
|
|
425
|
-
// `retrySwap` once pool conditions improve. Only store the conversion rate when
|
|
426
|
-
// the swap produced a positive local amount.
|
|
427
|
-
if (leafTotal > 0 && !swapFailed) {
|
|
428
|
-
if (localAmount == 0 && deliveredAmount > 0) {
|
|
429
|
-
pendingSwapOf[localToken][nonce] = PendingSwap({
|
|
430
|
-
bridgeToken: deliveredToken, bridgeAmount: deliveredAmount, leafTotal: leafTotal
|
|
431
|
-
});
|
|
432
|
-
} else {
|
|
433
|
-
_conversionRateOf[localToken][nonce] =
|
|
434
|
-
ConversionRate({leafTotal: leafTotal, localTotal: localAmount});
|
|
435
|
-
}
|
|
440
|
+
} else {
|
|
441
|
+
_conversionRateOf[localToken][nonce] =
|
|
442
|
+
ConversionRate({leafTotal: leafTotal, localTotal: localAmount});
|
|
436
443
|
}
|
|
437
444
|
}
|
|
438
445
|
} else {
|