@@ -12,6 +12,7 @@ import {IERC20Permit, IERC20} from '../../interfaces/IERC20Permit.sol';
12
12
import {IWrappedPosition} from './interfaces/IWrappedPosition.sol ' ;
13
13
import {IRollupProcessor} from '../../interfaces/IRollupProcessor.sol ' ;
14
14
import {MinHeap} from './MinHeap.sol ' ;
15
+ import {FullMath} from '../uniswapv3/libraries/FullMath.sol ' ;
15
16
16
17
import {IDefiBridge} from '../../interfaces/IDefiBridge.sol ' ;
17
18
@@ -50,6 +51,9 @@ contract ElementBridge is IDefiBridge {
50
51
error UNREGISTERED_POOL ();
51
52
error UNREGISTERED_POSITION ();
52
53
error UNREGISTERED_PAIR ();
54
+ error INVALID_TOKEN_BALANCE_RECEIVED ();
55
+ error INVALID_CHANGE_IN_BALANCE ();
56
+ error RECEIVED_LESS_THAN_LIMIT ();
53
57
54
58
/*----------------------------------------
55
59
STRUCTS
@@ -144,21 +148,21 @@ contract ElementBridge is IDefiBridge {
144
148
// 48 hours in seconds, usd for calculating speeedbump expiries
145
149
uint256 internal constant _FORTY_EIGHT_HOURS = 172800 ;
146
150
147
- uint256 internal constant MAX_INT = 2 ** 256 - 1 ;
151
+ uint256 internal constant MAX_UINT = type ( uint256 ).max ;
148
152
149
153
uint256 internal constant MIN_GAS_FOR_CHECK_AND_FINALISE = 50000 ;
150
154
uint256 internal constant MIN_GAS_FOR_FUNCTION_COMPLETION = 5000 ;
151
155
uint256 internal constant MIN_GAS_FOR_FAILED_INTERACTION = 20000 ;
152
156
uint256 internal constant MIN_GAS_FOR_EXPIRY_REMOVAL = 25000 ;
153
157
154
158
// event emitted on every successful convert call
155
- event Convert (uint256 indexed nonce , uint256 totalInputValue );
159
+ event LogConvert (uint256 indexed nonce , uint256 totalInputValue );
156
160
157
161
// event emitted on every attempt to finalise, successful or otherwise
158
- event Finalise (uint256 indexed nonce , bool success , string message );
162
+ event LogFinalise (uint256 indexed nonce , bool success , string message );
159
163
160
164
// event emitted on wvery newly configured pool
161
- event PoolAdded (address poolAddress , address wrappedPositionAddress , uint64 expiry );
165
+ event LogPoolAdded (address poolAddress , address wrappedPositionAddress , uint64 expiry );
162
166
163
167
/**
164
168
* @dev Constructor
@@ -320,7 +324,7 @@ contract ElementBridge is IDefiBridge {
320
324
}
321
325
322
326
// verify with Element that the provided contracts are registered
323
- validatePositionAndPoolAddresses (wrappedPositionAddress, poolAddress);
327
+ validatePositionAndPoolAddressesWithElementRegistry (wrappedPositionAddress, poolAddress);
324
328
325
329
// we store the pool information against a hash of the asset and expiry
326
330
uint256 assetExpiryHash = hashAssetAndExpiry (poolSpec.underlyingAsset, trancheExpiry);
@@ -338,9 +342,9 @@ contract ElementBridge is IDefiBridge {
338
342
// initialising the expiry -> nonce mapping here like this reduces a chunk of gas later when we start to add interactions for this expiry
339
343
uint256 [] storage nonces = expiryToNonce[trancheExpiry];
340
344
if (nonces.length == 0 ) {
341
- expiryToNonce[trancheExpiry].push (MAX_INT );
345
+ expiryToNonce[trancheExpiry].push (MAX_UINT );
342
346
}
343
- emit PoolAdded (poolAddress, wrappedPositionAddress, trancheExpiry);
347
+ emit LogPoolAdded (poolAddress, wrappedPositionAddress, trancheExpiry);
344
348
}
345
349
346
350
/**
@@ -375,7 +379,7 @@ contract ElementBridge is IDefiBridge {
375
379
* @param wrappedPosition address of a wrapped position contract
376
380
* @param pool address of a balancer pool contract
377
381
*/
378
- function validatePositionAndPoolAddresses (address wrappedPosition , address pool ) internal {
382
+ function validatePositionAndPoolAddressesWithElementRegistry (address wrappedPosition , address pool ) internal {
379
383
IDeploymentValidator validator = IDeploymentValidator (elementDeploymentValidatorAddress);
380
384
if (! validator.checkPoolValidation (pool)) {
381
385
revert UNREGISTERED_POOL ();
@@ -458,26 +462,7 @@ contract ElementBridge is IDefiBridge {
458
462
// approve the transfer of tokens to the balancer address
459
463
ERC20 (inputAssetA.erc20Address).approve (balancerAddress, totalInputValue);
460
464
// execute the swap on balancer
461
- address inputAsset = inputAssetA.erc20Address;
462
-
463
- uint256 principalTokensAmount = IVault (balancerAddress).swap (
464
- IVault.SingleSwap ({
465
- poolId: pool.poolId,
466
- kind: IVault.SwapKind.GIVEN_IN,
467
- assetIn: IAsset (inputAsset),
468
- assetOut: IAsset (pool.trancheAddress),
469
- amount: totalInputValue,
470
- userData: '0x00 '
471
- }),
472
- IVault.FundManagement ({
473
- sender: address (this ), // the bridge has already received the tokens from the rollup so it owns totalInputValue of inputAssetA
474
- fromInternalBalance: false ,
475
- recipient: payable (address (this )),
476
- toInternalBalance: false
477
- }),
478
- totalInputValue,
479
- block .timestamp
480
- );
465
+ uint256 principalTokensAmount = exchangeAssetForTrancheTokens (inputAssetA.erc20Address, pool, totalInputValue);
481
466
// store the tranche that underpins our interaction, the expiry and the number of received tokens against the nonce
482
467
Interaction storage newInteraction = interactions[interactionNonce];
483
468
newInteraction.expiry = trancheExpiry;
@@ -486,17 +471,65 @@ contract ElementBridge is IDefiBridge {
486
471
newInteraction.quantityPT = principalTokensAmount;
487
472
newInteraction.trancheAddress = pool.trancheAddress;
488
473
// add the nonce and expiry to our expiry heap
489
- addNonceAndExpiry (interactionNonce, trancheExpiry);
474
+ addNonceAndExpiryToNonceMapping (interactionNonce, trancheExpiry);
490
475
// increase our tranche account deposits and holdings
491
476
// other members are left as their initial values (all zeros)
492
477
TrancheAccount storage trancheAccount = trancheAccounts[newInteraction.trancheAddress];
493
478
trancheAccount.numDeposits++ ;
494
479
trancheAccount.quantityTokensHeld += newInteraction.quantityPT;
495
- emit Convert (interactionNonce, totalInputValue);
480
+ emit LogConvert (interactionNonce, totalInputValue);
496
481
finaliseExpiredInteractions (MIN_GAS_FOR_FUNCTION_COMPLETION);
497
482
// we need to get here with MIN_GAS_FOR_FUNCTION_COMPLETION gas to exit.
498
483
}
499
484
485
+ /**
486
+ * @dev Function to exchange the input asset for tranche tokens on Balancer
487
+ * @param inputAsset the address of the asset we want to swap
488
+ * @param pool storage struct containing details of the pool we wish to use for the swap
489
+ * @param inputQuantity the quantity of the input asset we wish to swap
490
+ * @return quantityReceived amount of tokens recieved
491
+ */
492
+ function exchangeAssetForTrancheTokens (address inputAsset , Pool storage pool , uint256 inputQuantity ) internal returns (uint256 quantityReceived ) {
493
+ IVault.SingleSwap memory singleSwap = IVault.SingleSwap ({
494
+ poolId: pool.poolId, // the id of the pool we want to use
495
+ kind: IVault.SwapKind.GIVEN_IN, // We are exchanging a given number of input tokens
496
+ assetIn: IAsset (inputAsset), // the input asset for the swap
497
+ assetOut: IAsset (pool.trancheAddress), // the tranche token address as the output asset
498
+ amount: inputQuantity, // the total amount of input asset we wish to swap
499
+ userData: '0x00 ' // set to 0 as per the docs, this is unused in current balancer pools
500
+ });
501
+ IVault.FundManagement memory fundManagement = IVault.FundManagement ({
502
+ sender: address (this ), // the bridge has already received the tokens from the rollup so it owns totalInputValue of inputAssetA
503
+ fromInternalBalance: false ,
504
+ recipient: payable (address (this )), // we want the output tokens transferred back to us
505
+ toInternalBalance: false
506
+ });
507
+
508
+ uint256 trancheTokenQuantityBefore = ERC20 (pool.trancheAddress).balanceOf (address (this ));
509
+ quantityReceived = IVault (balancerAddress).swap (
510
+ singleSwap,
511
+ fundManagement,
512
+ inputQuantity, // we won't accept less than 1 output token per input token
513
+ block .timestamp
514
+ );
515
+
516
+ uint256 trancheTokenQuantityAfter = ERC20 (pool.trancheAddress).balanceOf (address (this ));
517
+ // ensure we haven't lost tokens!
518
+ if (trancheTokenQuantityAfter < trancheTokenQuantityBefore) {
519
+ revert INVALID_CHANGE_IN_BALANCE ();
520
+ }
521
+ // change in balance must be >= 0 here
522
+ uint256 changeInBalance = trancheTokenQuantityAfter - trancheTokenQuantityBefore;
523
+ // ensure the change in balance matches that reported to us
524
+ if (changeInBalance != quantityReceived) {
525
+ revert INVALID_TOKEN_BALANCE_RECEIVED ();
526
+ }
527
+ // ensure we received at least the limit we placed
528
+ if (quantityReceived < inputQuantity) {
529
+ revert RECEIVED_LESS_THAN_LIMIT ();
530
+ }
531
+ }
532
+
500
533
/**
501
534
* @dev Function to attempt finalising of as many interactions as possible within the specified gas limit
502
535
* Continue checking for and finalising interactions until we expend the available gas
@@ -510,7 +543,7 @@ contract ElementBridge is IDefiBridge {
510
543
while (gasleft () > gasLoopCondition) {
511
544
// check the heap to see if we can finalise an expired transaction
512
545
// we provide a gas floor to the function which will enable us to leave this function without breaching our gasFloor
513
- (bool expiryAvailable , uint256 nonce ) = checkNextExpiry (ourGasFloor);
546
+ (bool expiryAvailable , uint256 nonce ) = checkForNextInteractionToFinalise (ourGasFloor);
514
547
if (! expiryAvailable) {
515
548
break ;
516
549
}
@@ -570,7 +603,7 @@ contract ElementBridge is IDefiBridge {
570
603
if (trancheAccount.numDeposits == 0 ) {
571
604
// shouldn't be possible, this means we have had no deposits against this tranche
572
605
setInteractionAsFailure (interaction, interactionNonce, 'NO_DEPOSITS_FOR_TRANCHE ' );
573
- popInteraction (interaction, interactionNonce);
606
+ popInteractionFromNonceMapping (interaction, interactionNonce);
574
607
return (0 , 0 , false );
575
608
}
576
609
@@ -586,12 +619,12 @@ contract ElementBridge is IDefiBridge {
586
619
} catch Error (string memory errorMessage ) {
587
620
setInteractionAsFailure (interaction, interactionNonce, errorMessage);
588
621
trancheAccount.redemptionStatus = TrancheRedemptionStatus.REDEMPTION_FAILED;
589
- popInteraction (interaction, interactionNonce);
622
+ popInteractionFromNonceMapping (interaction, interactionNonce);
590
623
return (0 , 0 , false );
591
624
} catch {
592
625
setInteractionAsFailure (interaction, interactionNonce, 'UNKNOWN_ERROR_FROM_TRANCHE_WITHDRAW ' );
593
626
trancheAccount.redemptionStatus = TrancheRedemptionStatus.REDEMPTION_FAILED;
594
- popInteraction (interaction, interactionNonce);
627
+ popInteractionFromNonceMapping (interaction, interactionNonce);
595
628
return (0 , 0 , false );
596
629
}
597
630
}
@@ -606,7 +639,9 @@ contract ElementBridge is IDefiBridge {
606
639
amountToAllocate = trancheAccount.quantityAssetRedeemed / trancheAccount.numDeposits;
607
640
} else {
608
641
// apportion the output asset based on the interaction's holding of the principle token
609
- amountToAllocate = (trancheAccount.quantityAssetRedeemed * interaction.quantityPT) / trancheAccount.quantityTokensHeld;
642
+ // protects against phantom overflow in the operation of
643
+ // amountToAllocate = (trancheAccount.quantityAssetRedeemed * interaction.quantityPT) / trancheAccount.quantityTokensHeld;
644
+ amountToAllocate = FullMath.mulDiv (trancheAccount.quantityAssetRedeemed, interaction.quantityPT, trancheAccount.quantityTokensHeld);
610
645
}
611
646
// numDeposits and numFinalised are uint32 types, so easily within range for an int256
612
647
int256 numRemainingInteractionsForTranche = int256 (uint256 (trancheAccount.numDeposits)) - int256 (uint256 (trancheAccount.numFinalised));
@@ -622,11 +657,11 @@ contract ElementBridge is IDefiBridge {
622
657
// approve the transfer of funds back to the rollup contract
623
658
ERC20 (outputAssetA.erc20Address).approve (rollupProcessor, amountToAllocate);
624
659
interaction.finalised = true ;
625
- popInteraction (interaction, interactionNonce);
660
+ popInteractionFromNonceMapping (interaction, interactionNonce);
626
661
outputValueA = amountToAllocate;
627
662
outputValueB = 0 ;
628
663
interactionCompleted = true ;
629
- emit Finalise (interactionNonce, interactionCompleted, '' );
664
+ emit LogFinalise (interactionNonce, interactionCompleted, '' );
630
665
}
631
666
632
667
/**
@@ -641,7 +676,7 @@ contract ElementBridge is IDefiBridge {
641
676
string memory message
642
677
) internal {
643
678
interaction.failed = true ;
644
- emit Finalise (interactionNonce, false , message);
679
+ emit LogFinalise (interactionNonce, false , message);
645
680
}
646
681
647
682
/**
@@ -650,12 +685,12 @@ contract ElementBridge is IDefiBridge {
650
685
* @param expiry The expiry of the interaction to be added
651
686
* @return expiryAdded Flag specifying whether the interactions expiry was added to the heap
652
687
*/
653
- function addNonceAndExpiry (uint256 nonce , uint64 expiry ) internal returns (bool expiryAdded ) {
688
+ function addNonceAndExpiryToNonceMapping (uint256 nonce , uint64 expiry ) internal returns (bool expiryAdded ) {
654
689
// get the set of nonces already against this expiry
655
- // check for the MAX_INT placeholder nonce that exists to reduce gas costs at this point in the code
690
+ // check for the MAX_UINT placeholder nonce that exists to reduce gas costs at this point in the code
656
691
expiryAdded = false ;
657
692
uint256 [] storage nonces = expiryToNonce[expiry];
658
- if (nonces.length == 1 && nonces[0 ] == MAX_INT ) {
693
+ if (nonces.length == 1 && nonces[0 ] == MAX_UINT ) {
659
694
nonces[0 ] = nonce;
660
695
} else {
661
696
nonces.push (nonce);
@@ -674,7 +709,7 @@ contract ElementBridge is IDefiBridge {
674
709
* @param interactionNonce The nonce of the interaction to be removed
675
710
* @return expiryRemoved Flag specifying whether the interactions expiry was removed from the heap
676
711
*/
677
- function popInteraction (Interaction storage interaction , uint256 interactionNonce ) internal returns (bool expiryRemoved ) {
712
+ function popInteractionFromNonceMapping (Interaction storage interaction , uint256 interactionNonce ) internal returns (bool expiryRemoved ) {
678
713
uint256 [] storage nonces = expiryToNonce[interaction.expiry];
679
714
if (nonces.length == 0 ) {
680
715
return (false );
@@ -706,7 +741,7 @@ contract ElementBridge is IDefiBridge {
706
741
* @return expiryAvailable Flag specifying whether an expiry is available to be finalised
707
742
* @return nonce The next interaction nonce to be finalised
708
743
*/
709
- function checkNextExpiry (uint256 gasFloor )
744
+ function checkForNextInteractionToFinalise (uint256 gasFloor )
710
745
internal
711
746
returns (
712
747
bool expiryAvailable ,
@@ -728,7 +763,7 @@ contract ElementBridge is IDefiBridge {
728
763
uint256 minGasForLoop = (gasFloor + MIN_GAS_FOR_FAILED_INTERACTION);
729
764
while (nonces.length > 0 && gasleft () >= minGasForLoop) {
730
765
uint256 nextNonce = nonces[nonces.length - 1 ];
731
- if (nextNonce == MAX_INT ) {
766
+ if (nextNonce == MAX_UINT ) {
732
767
// this shouldn't happen, this value is the placeholder for reducing gas costs on convert
733
768
// we just need to pop and continue
734
769
nonces.pop ();
0 commit comments