Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feat v3.5 #52

Merged
merged 51 commits into from
Sep 18, 2024
Merged
Show file tree
Hide file tree
Changes from 37 commits
Commits
Show all changes
51 commits
Select commit Hold shift + click to select a range
382c867
Add BridgeProxy contract and state variables
cosmatudor Aug 21, 2024
266aad9
add deposit endpoint
cosmatudor Aug 22, 2024
2e08dd9
add logic for deposit funds in Bridge contract
cosmatudor Aug 22, 2024
c0aa688
moved structs to SharedStructs
cosmatudor Aug 22, 2024
b004524
Merge branch 'CII-56-pending-transactions-management' into CII-53-dep…
cosmatudor Aug 22, 2024
cb71bf9
add counter for transactions
cosmatudor Aug 22, 2024
cfb9263
add execute endpoint
cosmatudor Aug 22, 2024
4ed8787
fixed BridgeMock bug
cosmatudor Aug 22, 2024
36075e9
Merge branch 'CII-53-deposit-endpoint' into CII-54-execute-endpoint
cosmatudor Aug 22, 2024
5f51c43
add executeCallback private method
cosmatudor Aug 22, 2024
c8478bd
add finishExecuteGracefully private method
cosmatudor Aug 22, 2024
873c584
Merge pull request #42 from multiversx/CII-56-pending-transactions-ma…
dragos-rebegea Aug 22, 2024
bf7feab
add refund method and view functions
cosmatudor Aug 23, 2024
35509e2
removed token payments mapping and refactored
cosmatudor Aug 23, 2024
77d9018
Merge branch 'CII-53-deposit-endpoint' into CII-54-execute-endpoint
cosmatudor Aug 23, 2024
dc3b320
Merge branch 'CII-54-execute-endpoint' into CII-55-finish-execute-gra…
cosmatudor Aug 23, 2024
152b651
removed token payment
cosmatudor Aug 23, 2024
2f95346
Merge branch 'CII-55-finish-execute-gracefully' into CII-58-refund-wo…
cosmatudor Aug 23, 2024
2bf24e6
fixed logic after removing token payments
cosmatudor Aug 23, 2024
093e45c
Merge branch 'CII-54-execute-endpoint' into CII-55-finish-execute-gra…
cosmatudor Aug 23, 2024
ea91fda
Merge branch 'CII-55-finish-execute-gracefully' into CII-58-refund-wo…
cosmatudor Aug 23, 2024
5d704da
add setter for Bridge address
cosmatudor Aug 23, 2024
7257749
refactored tests for Bridge, MintBurnERC20 and ERC20Safe contracts
cosmatudor Aug 23, 2024
25faae2
refactored deposit endpoint
cosmatudor Aug 23, 2024
07721ef
removed state variable bridgeAddress
cosmatudor Aug 23, 2024
554ec5f
Merge pull request #45 from multiversx/CII-53-deposit-endpoint
dragos-rebegea Aug 26, 2024
b7a6f53
Merge pull request #47 from multiversx/CII-54-execute-endpoint
dragos-rebegea Aug 26, 2024
b1084e7
Merge pull request #48 from multiversx/CII-55-finish-execute-gracefully
dragos-rebegea Aug 26, 2024
d50a18a
Merge pull request #51 from multiversx/CII-58-refund-workflow
dragos-rebegea Aug 26, 2024
8b28c00
small gas optimization for getPendingTransaction view function
cosmatudor Aug 26, 2024
f05e4da
lowestIndexId and calldata encoding fixes
cosmatudor Aug 26, 2024
79529b5
add unit tests for BridgeProxy contract
cosmatudor Aug 26, 2024
bbc701f
refactored execute endpoint
cosmatudor Aug 26, 2024
1578588
Merge pull request #53 from multiversx/CII-57-bridge-proxy-testing
dragos-rebegea Aug 26, 2024
5d776a4
add logic for deleting pending txns
cosmatudor Aug 27, 2024
82678b5
add test for txns with endpoints with no args
cosmatudor Aug 27, 2024
f6bcaf2
Merge branch 'CII-57-bridge-proxy-testing' into feat-v3.5
cosmatudor Aug 27, 2024
084a44e
Merge branch 'feat/v3' into feat-v3.5
cosmatudor Aug 28, 2024
886c783
Merge remote-tracking branch 'origin/feat/v3' into feat-v3.5
cosmatudor Aug 28, 2024
2b0440d
made BridgeProxy contract upgradable
cosmatudor Aug 28, 2024
3beeb5e
test fixes after merge
cosmatudor Aug 28, 2024
21e4633
add test for BridgeProxy contract upgrade
cosmatudor Aug 28, 2024
f8a2e46
Merge branch 'feat/v3.5' into feat-v3.5
dragos-rebegea Aug 29, 2024
0152098
add isPendingTransaction private function
cosmatudor Aug 29, 2024
1f99ceb
Merge branch 'feat-v3.5' of https://github.com/multiversx/mx-bridge-e…
cosmatudor Aug 29, 2024
4707f2a
temporar fixes
cosmatudor Aug 30, 2024
47cf9bd
fixes after review
cosmatudor Aug 30, 2024
16a2c21
renamed BridgeProxy to BridgeExecutor
cosmatudor Sep 16, 2024
72e586a
more fixes
cosmatudor Sep 16, 2024
7c953fe
refactor execute function
cosmatudor Sep 18, 2024
76999c4
renamed variables
cosmatudor Sep 18, 2024
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
66 changes: 44 additions & 22 deletions contracts/Bridge.sol
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import "./SharedStructs.sol";
import "./ERC20Safe.sol";
import "./access/RelayerRole.sol";
import "./lib/Pausable.sol";
import "./BridgeProxy.sol";

/**
@title Bridge
Expand Down Expand Up @@ -35,6 +36,7 @@ contract Bridge is RelayerRole, Pausable {

uint256 public quorum;
ERC20Safe internal immutable safe;
BridgeProxy internal bridgeProxy;

mapping(uint256 => bool) public executedBatches;
mapping(uint256 => CrossTransferStatus) public crossTransferStatuses;
Expand All @@ -48,14 +50,15 @@ contract Bridge is RelayerRole, Pausable {
* - add/remove relayers
* - add/remove tokens that can be bridged
*/
constructor(address[] memory board, uint256 initialQuorum, ERC20Safe erc20Safe) {
constructor(address[] memory board, uint256 initialQuorum, ERC20Safe erc20Safe, BridgeProxy _bridgeProxy) {
require(initialQuorum >= minimumQuorum, "Quorum is too low.");
require(board.length >= initialQuorum, "The board should be at least the quorum size.");

_addRelayers(board);

quorum = initialQuorum;
safe = erc20Safe;
bridgeProxy = _bridgeProxy;
}

/**
Expand Down Expand Up @@ -97,20 +100,21 @@ contract Bridge is RelayerRole, Pausable {
}

/**
@notice Executes transfers that were signed by the relayers.
@dev This is for the MultiversX to Ethereum flow
@dev Arrays here try to mimmick the structure of a batch. A batch represents the values from the same index in all the arrays.
@param tokens Array containing all the token addresses that the batch interacts with. Can even contain duplicates.
@param recipients Array containing all the destinations from the batch. Can be duplicates.
@param amounts Array containing all the amounts that will be transfered.
@param batchNonceMvx Nonce for the batch. This identifies a batch created on the MultiversX chain that bridges tokens from MultiversX to Ethereum
@param signatures Signatures from all the relayers for the execution. This mimics a delegated multisig contract. For the execution to take place, there must be enough valid signatures to achieve quorum.
@notice Executes a batch of transfers
@param mvxTransactions List of transactions from MultiversX side. Each transaction consists of:
- token address
- sender
- recipient
- amount
- deposit nonce
- call data
- true, if recipient a smart contract
false, otherwise
@param batchNonceMvx Nonce for the batch
@param signatures List of signatures from the relayers
*/
function executeTransfer(
address[] calldata tokens,
address[] calldata recipients,
uint256[] calldata amounts,
uint256[] calldata depositNonces,
MvxTransaction[] calldata mvxTransactions,
uint256 batchNonceMvx,
bytes[] calldata signatures
) public whenNotPaused onlyRelayer {
Expand All @@ -120,16 +124,12 @@ contract Bridge is RelayerRole, Pausable {

_validateQuorum(
signatures,
_getHashedDepositData(
abi.encode(recipients, tokens, amounts, depositNonces, batchNonceMvx, executeTransferAction)
)
_getHashedDepositData(abi.encode(mvxTransactions, batchNonceMvx, executeTransferAction))
);

DepositStatus[] memory statuses = new DepositStatus[](tokens.length);
for (uint256 j = 0; j < tokens.length; j++) {
statuses[j] = safe.transfer(tokens[j], amounts[j], recipients[j])
? DepositStatus.Executed
: DepositStatus.Rejected;
DepositStatus[] memory statuses = new DepositStatus[](mvxTransactions.length);
for (uint256 j = 0; j < mvxTransactions.length; j++) {
statuses[j] = _processDeposit(mvxTransactions[j]);
}

CrossTransferStatus storage crossStatus = crossTransferStatuses[batchNonceMvx];
Expand All @@ -142,7 +142,9 @@ contract Bridge is RelayerRole, Pausable {
@param batchNonceMvx Nonce for the batch
@return a list of statuses for each transfer in the batch provided
*/
function getStatusesAfterExecution(uint256 batchNonceMvx) external view returns (DepositStatus[] memory, bool isFinal) {
function getStatusesAfterExecution(
uint256 batchNonceMvx
) external view returns (DepositStatus[] memory, bool isFinal) {
CrossTransferStatus memory crossStatus = crossTransferStatuses[batchNonceMvx];
return (crossStatus.statuses, _isMvxBatchFinal(crossStatus.createdBlockNumber));
}
Expand All @@ -164,6 +166,26 @@ contract Bridge is RelayerRole, Pausable {
return keccak256(abi.encodePacked(prefix, keccak256(encodedData)));
}

function _processDeposit(MvxTransaction calldata mvxTransaction) private returns (DepositStatus) {
address recipient;
if (mvxTransaction.isScRecipient) {
recipient = address(bridgeProxy);
} else {
recipient = mvxTransaction.recipient;
}

DepositStatus status = safe.transfer(mvxTransaction.token, mvxTransaction.amount, recipient)
? DepositStatus.Executed
: DepositStatus.Rejected;

// If the recipient is a smart contract, deposit the funds in the bridgeProxy
if (mvxTransaction.isScRecipient && status == DepositStatus.Executed) {
bridgeProxy.deposit(mvxTransaction);
}

return status;
}

function _validateQuorum(bytes[] memory signatures, bytes32 data) private view {
uint256 signersCount;
address[] memory validSigners = new address[](signatures.length);
Expand Down
113 changes: 113 additions & 0 deletions contracts/BridgeProxy.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,113 @@
//SPDX-License-Identifier: UNLICENSED

pragma solidity ^0.8.20;

import "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import "./lib/Pausable.sol";
import "./SharedStructs.sol";
import "./lib/BoolTokenTransfer.sol";
import "./access/AdminRole.sol";
import "./access/BridgeRole.sol";

contract BridgeProxy is Pausable, BridgeRole {
using BoolTokenTransfer for IERC20;

uint256 public constant MIN_GAS_LIMIT_FOR_SC_CALL = 10_000_000;
cosmatudor marked this conversation as resolved.
Show resolved Hide resolved
uint256 public constant DEFAULT_GAS_LIMIT_FOR_REFUND_CALLBACK = 20_000_000;

mapping(uint256 => MvxTransaction) private pendingTransactions;
uint256 private lowestTxId;
cosmatudor marked this conversation as resolved.
Show resolved Hide resolved
uint256 private currentTxId;

constructor() Pausable() {
lowestTxId = 0;
cosmatudor marked this conversation as resolved.
Show resolved Hide resolved
}

function deposit(MvxTransaction calldata txn) external payable whenNotPaused onlyBridge {
pendingTransactions[currentTxId] = txn;
cosmatudor marked this conversation as resolved.
Show resolved Hide resolved
currentTxId++;
}

function execute(uint256 txId) external whenNotPaused {
require(txId < currentTxId, "BridgeProxy: Invalid transaction ID");
MvxTransaction memory txn = pendingTransactions[txId];

require(txn.amount != 0, "BridgeProxy: No amount bridged");
cosmatudor marked this conversation as resolved.
Show resolved Hide resolved

if (txn.callData.length > 0) {
Copy link
Collaborator

@ccorcoveanu ccorcoveanu Aug 29, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

would it be cleaner to do?:

if (txn.callData.length == 0) {
    _finishExecuteGracefully(txId, true);
    return;
}

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@cosmatudor I guess this still applies, wouldn't it read better with an early exit?

(bytes memory selector, uint256 gasLimit, bytes memory args) = abi.decode(
txn.callData,
(bytes, uint256, bytes)
);

if (selector.length == 0 || gasLimit == 0 || gasLimit < MIN_GAS_LIMIT_FOR_SC_CALL) {
_finishExecuteGracefully(txId, true);
return;
}

bytes memory data;
if (args.length > 0) {
data = abi.encodePacked(selector, args);
} else {
data = selector;
}

(bool success, ) = txn.recipient.call{ gas: gasLimit }(data);
ccorcoveanu marked this conversation as resolved.
Show resolved Hide resolved

bool isRefund = !success;
_finishExecuteGracefully(txId, isRefund);
} else {
_finishExecuteGracefully(txId, true);
}
}

function _finishExecuteGracefully(uint256 txId, bool isRefund) private {
if (isRefund) {
_refundTransaction(txId);
}
_updateLowestTxId();
delete pendingTransactions[txId];
}

function _refundTransaction(uint256 txId) private {
MvxTransaction memory txn = pendingTransactions[txId];

IERC20 erc20 = IERC20(txn.token);
bool transferExecuted = erc20.boolTransfer(this.bridge(), txn.amount);
require(transferExecuted, "BridgeProxy: Refund failed");
}

function _updateLowestTxId() private {
uint256 newLowestTxId = lowestTxId;

while (newLowestTxId < currentTxId && pendingTransactions[newLowestTxId].amount == 0) {
newLowestTxId++;
}

lowestTxId = newLowestTxId;
}

function getPendingTransactionById(uint256 txId) public view returns (MvxTransaction memory) {
return pendingTransactions[txId];
}

function getPendingTransaction() public view returns (MvxTransaction[] memory) {
uint256 pendingTransactionsCount = currentTxId - lowestTxId;
MvxTransaction[] memory txns = new MvxTransaction[](pendingTransactionsCount);
uint256 index = 0;

for (uint256 i = lowestTxId; i < currentTxId; i++) {
if (pendingTransactions[i].amount != 0) {
cosmatudor marked this conversation as resolved.
Show resolved Hide resolved
txns[index] = pendingTransactions[i];
index++;
}
}

// Resize the array to the actual number of pending transactions
assembly {
mstore(txns, index)
}

return txns;
}
}
10 changes: 10 additions & 0 deletions contracts/SharedStructs.sol
Original file line number Diff line number Diff line change
Expand Up @@ -34,3 +34,13 @@ struct Batch {
struct DepositSCExtension {
string depositData;
}

struct MvxTransaction {
address token;
bytes32 sender;
address recipient;
uint256 amount;
uint256 depositNonce;
bytes callData;
bool isScRecipient;
cosmatudor marked this conversation as resolved.
Show resolved Hide resolved
}
5 changes: 3 additions & 2 deletions contracts/test/BridgeMock.sol
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,9 @@ contract BridgeMock is Bridge {
constructor(
address[] memory board,
uint256 initialQuorum,
ERC20Safe erc20Safe
) Bridge(board, initialQuorum, erc20Safe) {}
ERC20Safe erc20Safe,
BridgeProxy bridgeProxy
) Bridge(board, initialQuorum, erc20Safe, bridgeProxy) {}

function proxyTransfer(address tokenAddress, uint256 amount, address recipientAddress) external returns (bool) {
return safe.transfer(tokenAddress, amount, recipientAddress);
Expand Down
15 changes: 15 additions & 0 deletions contracts/test/Counter.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
//SPDX-License-Identifier: UNLICENSED

pragma solidity ^0.8.20;

contract Counter {
uint256 public count;

constructor() {
count = 0;
}

function increment() public {
count += 1;
}
}
Loading
Loading