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

Upgrade the bridge to work with chain signatures #248

Draft
wants to merge 3 commits into
base: master
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
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
36 changes: 36 additions & 0 deletions erc20-bridge-token/contracts/Borsh.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
// SPDX-License-Identifier: GPL-3.0-or-later
pragma solidity ^0.8.24;

library Borsh {
function encodeUint32(uint32 val) internal pure returns (bytes memory) {
return abi.encodePacked(swapBytes4(val));
}

function encodeUint128(uint128 val) internal pure returns (bytes memory) {
return abi.encodePacked(swapBytes16(val));
}

function encodeString(string memory val) internal pure returns (bytes memory) {
bytes memory b = bytes(val);
return bytes.concat(
encodeUint32(uint32(b.length)),
bytes(val)
);
}

function encodeAddress(address val) internal pure returns (bytes20) {
return bytes20(val);
}

function swapBytes4(uint32 v) internal pure returns (uint32) {
v = ((v & 0x00ff00ff) << 8) | ((v & 0xff00ff00) >> 8);
return (v << 16) | (v >> 16);
}

function swapBytes16(uint128 v) internal pure returns (uint128) {
v = ((v & 0x00ff00ff00ff00ff00ff00ff00ff00ff) << 8) | ((v & 0xff00ff00ff00ff00ff00ff00ff00ff00) >> 8);
v = ((v & 0x0000ffff0000ffff0000ffff0000ffff) << 16) | ((v & 0xffff0000ffff0000ffff0000ffff0000) >> 16);
v = ((v & 0x00000000ffffffff00000000ffffffff) << 32) | ((v & 0xffffffff00000000ffffffff00000000) >> 32);
return (v << 64) | (v >> 64);
}
}
122 changes: 74 additions & 48 deletions erc20-bridge-token/contracts/BridgeTokenFactory.sol
Original file line number Diff line number Diff line change
Expand Up @@ -4,17 +4,13 @@ pragma solidity ^0.8.24;
import {AccessControlUpgradeable} from "@openzeppelin/contracts-upgradeable/access/AccessControlUpgradeable.sol";
import {ERC1967Proxy} from "@openzeppelin/contracts/proxy/ERC1967/ERC1967Proxy.sol";
import {UUPSUpgradeable} from "@openzeppelin/contracts-upgradeable/proxy/utils/UUPSUpgradeable.sol";
import {ECDSA} from "@openzeppelin/contracts/utils/cryptography/ECDSA.sol";

import "rainbow-bridge-sol/nearprover/contracts/INearProver.sol";
import "rainbow-bridge-sol/nearprover/contracts/ProofDecoder.sol";

import "./ProofConsumer.sol";
import "./BridgeToken.sol";
import "./ResultsDecoder.sol";
import "./SelectivePausableUpgradable.sol";
import "./Borsh.sol";

contract BridgeTokenFactory is
ProofConsumer,
UUPSUpgradeable,
AccessControlUpgradeable,
SelectivePausableUpgradable
Expand All @@ -26,6 +22,10 @@ contract BridgeTokenFactory is
CheckAccountAndToken
}

// We removed ProofConsumer from the list of parent contracts and added this gap
// to preserve storage layout when upgrading to the new contract version.
uint256[54] private __gap;

mapping(address => string) private _ethToNearToken;
mapping(string => address) private _nearToEthToken;
mapping(address => bool) private _isBridgeToken;
Expand All @@ -35,12 +35,30 @@ contract BridgeTokenFactory is
bool private _isWhitelistModeEnabled;

address public tokenImplementationAddress;
address public nearBridgeDerivedAddress;

mapping(uint128 => bool) private _completedTransfers;
Copy link
Collaborator

Choose a reason for hiding this comment

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

Let's make it public so the front can check if it is completed or not.


bytes32 public constant PAUSABLE_ADMIN_ROLE = keccak256("PAUSABLE_ADMIN_ROLE");
uint constant UNPAUSED_ALL = 0;
uint constant PAUSED_WITHDRAW = 1 << 0;
uint constant PAUSED_DEPOSIT = 1 << 1;

struct BridgeDeposit {
uint128 nonce;
string token;
uint128 amount;
address recipient;
address relayer;
}

struct MetadataPayload {
string token;
string name;
string symbol;
uint8 decimals;
}

// Event when funds are withdrawn from Ethereum back to NEAR.
event Withdraw(
string token,
Expand All @@ -60,21 +78,17 @@ contract BridgeTokenFactory is
uint8 decimals
);

error InvalidSignature();
error NonceAlreadyUsed();

// BridgeTokenFactory is linked to the bridge token factory on NEAR side.
// It also links to the prover that it uses to unlock the tokens.
function initialize(
address _tokenImplementationAddress,
bytes memory _nearTokenLocker,
INearProver _prover,
uint64 _minBlockAcceptanceHeight
address _nearBridgeDerivedAddress
) external initializer {
require(_nearTokenLocker.length > 0, "Invalid Near Token Locker address");
require(address(_prover) != address(0), "Invalid Near prover address");

nearTokenLocker = _nearTokenLocker;
prover = _prover;
minBlockAcceptanceHeight = _minBlockAcceptanceHeight;
tokenImplementationAddress = _tokenImplementationAddress;
nearBridgeDerivedAddress = _nearBridgeDerivedAddress;

__UUPSUpgradeable_init();
__AccessControl_init();
Expand All @@ -97,43 +111,44 @@ contract BridgeTokenFactory is
return _nearToEthToken[nearTokenId];
}

function newBridgeToken(
bytes memory proofData,
uint64 proofBlockHeight
) external returns (address) {
ProofDecoder.ExecutionStatus memory status = _parseAndConsumeProof(
proofData,
proofBlockHeight
);
ResultsDecoder.MetadataResult memory result = ResultsDecoder.decodeMetadataResult(
status.successValue
function newBridgeToken(bytes calldata signatureData, MetadataPayload calldata metadata) external returns (address) {
bytes memory borshEncoded = bytes.concat(
Borsh.encodeString(metadata.token),
Borsh.encodeString(metadata.name),
Borsh.encodeString(metadata.symbol),
bytes1(metadata.decimals)
);
bytes32 hashed = keccak256(borshEncoded);

if (ECDSA.recover(hashed, signatureData) != nearBridgeDerivedAddress) {
revert InvalidSignature();
}

require(!_isBridgeToken[_nearToEthToken[result.token]], "ERR_TOKEN_EXIST");
require(!_isBridgeToken[_nearToEthToken[metadata.token]], "ERR_TOKEN_EXIST");

address bridgeTokenProxy = address(
new ERC1967Proxy(
tokenImplementationAddress,
abi.encodeWithSelector(
BridgeToken.initialize.selector,
result.name,
result.symbol,
result.decimals
metadata.name,
metadata.symbol,
metadata.decimals
)
)
);

emit SetMetadata(
bridgeTokenProxy,
result.token,
result.name,
result.symbol,
result.decimals
metadata.token,
metadata.name,
metadata.symbol,
metadata.decimals
);

_isBridgeToken[address(bridgeTokenProxy)] = true;
_ethToNearToken[address(bridgeTokenProxy)] = result.token;
_nearToEthToken[result.token] = address(bridgeTokenProxy);
_ethToNearToken[address(bridgeTokenProxy)] = metadata.token;
_nearToEthToken[metadata.token] = address(bridgeTokenProxy);

return bridgeTokenProxy;
}
Expand All @@ -158,22 +173,33 @@ contract BridgeTokenFactory is
);
}

function deposit(
bytes memory proofData,
uint64 proofBlockHeight
) external whenNotPaused(PAUSED_DEPOSIT) {
ProofDecoder.ExecutionStatus memory status = _parseAndConsumeProof(
proofData,
proofBlockHeight
);
ResultsDecoder.LockResult memory result = ResultsDecoder.decodeLockResult(
status.successValue
function deposit(bytes calldata signatureData, BridgeDeposit calldata bridgeDeposit) external whenNotPaused(PAUSED_DEPOSIT) {
if (_completedTransfers[bridgeDeposit.nonce]) {
revert NonceAlreadyUsed();
}

bytes memory borshEncoded = bytes.concat(
Borsh.encodeUint128(bridgeDeposit.nonce),
Borsh.encodeString(bridgeDeposit.token),
Borsh.encodeUint128(bridgeDeposit.amount),
bytes1(0x00), // variant 1 in rust enum
Borsh.encodeAddress(bridgeDeposit.recipient),
bridgeDeposit.relayer == address(0) // None or Some(Address) in rust
? bytes("\x00")
: bytes.concat(bytes("\x01"), Borsh.encodeAddress(bridgeDeposit.relayer))
);
bytes32 hashed = keccak256(borshEncoded);

if (ECDSA.recover(hashed, signatureData) != nearBridgeDerivedAddress) {
revert InvalidSignature();
}

require(_isBridgeToken[_nearToEthToken[bridgeDeposit.token]], "ERR_NOT_BRIDGE_TOKEN");
BridgeToken(_nearToEthToken[bridgeDeposit.token]).mint(bridgeDeposit.recipient, bridgeDeposit.amount);

require(_isBridgeToken[_nearToEthToken[result.token]], "ERR_NOT_BRIDGE_TOKEN");
BridgeToken(_nearToEthToken[result.token]).mint(result.recipient, result.amount);
_completedTransfers[bridgeDeposit.nonce] = true;

emit Deposit(result.token, result.amount, result.recipient);
emit Deposit(bridgeDeposit.token, bridgeDeposit.amount, bridgeDeposit.recipient);
}

function withdraw(
Expand Down
72 changes: 0 additions & 72 deletions erc20-bridge-token/contracts/ProofConsumer.sol

This file was deleted.

51 changes: 0 additions & 51 deletions erc20-bridge-token/contracts/ResultsDecoder.sol

This file was deleted.

13 changes: 0 additions & 13 deletions erc20-bridge-token/contracts/test/NearProverMock.sol

This file was deleted.

Loading
Loading