Skip to content

Commit

Permalink
Merge branch 'main' into NOJIRA_UPDATE_SCRIPTS
Browse files Browse the repository at this point in the history
  • Loading branch information
wcgcyx committed Dec 13, 2023
2 parents e90aa43 + c229dd2 commit d2d9273
Show file tree
Hide file tree
Showing 32 changed files with 553 additions and 134 deletions.
49 changes: 39 additions & 10 deletions src/child/ChildAxelarBridgeAdaptor.sol
Original file line number Diff line number Diff line change
Expand Up @@ -49,27 +49,43 @@ contract ChildAxelarBridgeAdaptor is

IAxelarGasService public gasService;

constructor(address _gateway) AxelarExecutable(_gateway) {}
/// @notice Address of the authorized initializer.
address public immutable initializerAddress;

/**
* @notice Constructs the ChildAxelarBridgeAdaptor contract.
* @param _gateway The address of the Axelar gateway contract.
* @param _initializerAddress The address of the authorized initializer.
*/
constructor(address _gateway, address _initializerAddress) AxelarExecutable(_gateway) {
if (_initializerAddress == address(0)) {
revert ZeroAddress();
}
initializerAddress = _initializerAddress;
}

/**
* @notice Initialization function for ChildAxelarBridgeAdaptor.
* @param _roles Struct containing addresses of roles.
* @param _adaptorRoles Struct containing addresses of roles.
* @param _childBridge Address of child bridge contract.
* @param _rootChainId Axelar's string ID for the root chain.
* @param _rootBridgeAdaptor Address of the bridge adaptor on the root chain.
* @param _gasService Address of Axelar Gas Service contract.
*/
function initialize(
InitializationRoles memory _roles,
InitializationRoles memory _adaptorRoles,
address _childBridge,
string memory _rootChainId,
string memory _rootBridgeAdaptor,
address _gasService
) external initializer {
if (msg.sender != initializerAddress) {
revert UnauthorizedInitializer();
}
if (
_childBridge == address(0) || _gasService == address(0) || _roles.defaultAdmin == address(0)
|| _roles.bridgeManager == address(0) || _roles.gasServiceManager == address(0)
|| _roles.targetManager == address(0)
_childBridge == address(0) || _gasService == address(0) || _adaptorRoles.defaultAdmin == address(0)
|| _adaptorRoles.bridgeManager == address(0) || _adaptorRoles.gasServiceManager == address(0)
|| _adaptorRoles.targetManager == address(0)
) {
revert ZeroAddress();
}
Expand All @@ -83,10 +99,10 @@ contract ChildAxelarBridgeAdaptor is
}
__AccessControl_init();

_grantRole(DEFAULT_ADMIN_ROLE, _roles.defaultAdmin);
_grantRole(BRIDGE_MANAGER_ROLE, _roles.bridgeManager);
_grantRole(GAS_SERVICE_MANAGER_ROLE, _roles.gasServiceManager);
_grantRole(TARGET_MANAGER_ROLE, _roles.targetManager);
_grantRole(DEFAULT_ADMIN_ROLE, _adaptorRoles.defaultAdmin);
_grantRole(BRIDGE_MANAGER_ROLE, _adaptorRoles.bridgeManager);
_grantRole(GAS_SERVICE_MANAGER_ROLE, _adaptorRoles.gasServiceManager);
_grantRole(TARGET_MANAGER_ROLE, _adaptorRoles.targetManager);

childBridge = IChildERC20Bridge(_childBridge);
rootChainId = _rootChainId;
Expand Down Expand Up @@ -197,6 +213,19 @@ contract ChildAxelarBridgeAdaptor is
childBridge.onMessageReceive(_payload);
}

/**
* @inheritdoc AxelarExecutable
* @dev This function is called by the parent `AxelarExecutable` contract's `executeWithToken()` function.
* However, this function is not required for the bridge, and thus reverts with an `UnsupportedOperation` error.
*/
function _executeWithToken(string calldata, string calldata, bytes calldata, string calldata, uint256)
internal
pure
override
{
revert UnsupportedOperation();
}

// slither-disable-next-line unused-state,naming-convention
uint256[50] private __gapChildAxelarBridgeAdaptor;
}
55 changes: 47 additions & 8 deletions src/child/ChildERC20Bridge.sol
Original file line number Diff line number Diff line change
Expand Up @@ -35,10 +35,16 @@ import {BridgeRoles} from "../common/BridgeRoles.sol";
* - An account with an UNPAUSER_ROLE can unpause the contract.
* - An account with an ADAPTOR_MANAGER_ROLE can update the root bridge adaptor address.
* - An account with a DEFAULT_ADMIN_ROLE can grant and revoke roles.
* @dev Note:
*
* @dev Caution:
* - When withdrawing ETH (L2 -> L1), it's crucial to make sure that the receiving address on the root chain,
* if it's a contract, has a receive or fallback function that allows it to accept native ETH on the root chain.
* If this isn't the case, the transaction on the root chain could revert, potentially locking the user's funds indefinitely.
* - There is undefined behaviour for bridging non-standard ERC20 tokens (e.g. rebasing tokens). Please approach such cases with great care.
* - This is an upgradeable contract that should be operated behind OpenZeppelin's TransparentUpgradeableProxy.
* - The initialize function is susceptible to front running, so precautions should be taken to account for this scenario.
*
* @dev Note:
* - This is an upgradeable contract that should be operated behind OpenZeppelin's TransparentUpgradeableProxy.
*/
contract ChildERC20Bridge is
BridgeRoles,
Expand Down Expand Up @@ -70,6 +76,8 @@ contract ChildERC20Bridge is
address public childETHToken;
/// @dev The address of the wrapped IMX token on L2.
address public wIMXToken;
/// @dev Address of the authorized initializer.
address public immutable initializerAddress;

/**
* @notice Modifier to ensure that the caller is the registered child bridge adaptor.
Expand All @@ -81,6 +89,17 @@ contract ChildERC20Bridge is
_;
}

/**
* @notice Constructs the ChildERC20Bridge contract.
* @param _initializerAddress The address of the authorized initializer.
*/
constructor(address _initializerAddress) {
if (_initializerAddress == address(0)) {
revert ZeroAddress();
}
initializerAddress = _initializerAddress;
}

/**
* @notice Initialization function for ChildERC20Bridge.
* @param newRoles Struct containing addresses of roles.
Expand All @@ -97,6 +116,9 @@ contract ChildERC20Bridge is
address newRootIMXToken,
address newWIMXToken
) public initializer {
if (msg.sender != initializerAddress) {
revert UnauthorizedInitializer();
}
if (
newBridgeAdaptor == address(0) || newChildTokenTemplate == address(0) || newRootIMXToken == address(0)
|| newRoles.defaultAdmin == address(0) || newRoles.pauser == address(0) || newRoles.unpauser == address(0)
Expand Down Expand Up @@ -238,13 +260,21 @@ contract ChildERC20Bridge is

/**
* @inheritdoc IChildERC20Bridge
* @dev Caution:
* When withdrawing ETH, it's crucial to make sure that the receiving address (`msg.sender`) on the root chain,
* if it's a contract, has a receive or fallback function that allows it to accept native ETH.
* If this isn't the case, the transaction on the root chain could revert, potentially locking the user's funds indefinitely.
*/
function withdrawETH(uint256 amount) external payable {
_withdraw(childETHToken, msg.sender, amount);
}

/**
* @inheritdoc IChildERC20Bridge
* @dev Caution:
* When withdrawing ETH, it's crucial to make sure that the receiving address (`receiver`) on the root chain,
* if it's a contract, has a receive or fallback function that allows it to accept native ETH.
* If this isn't the case, the transaction on the root chain could revert, potentially locking the user's funds indefinitely.
*/
function withdrawETHTo(address receiver, uint256 amount) external payable {
_withdraw(childETHToken, receiver, amount);
Expand Down Expand Up @@ -314,9 +344,12 @@ contract ChildERC20Bridge is
* @notice Private function to handle withdrawal of L1 native ETH.
*/
function _withdrawETH(uint256 amount) private returns (address) {
if (!IChildERC20(childETHToken).burn(msg.sender, amount)) {
try IChildERC20(childETHToken).burn(msg.sender, amount) returns (bool success) {
if (!success) revert BurnFailed();
} catch {
revert BurnFailed();
}

return NATIVE_ETH;
}

Expand All @@ -330,7 +363,9 @@ contract ChildERC20Bridge is
IWIMX wIMX = IWIMX(wIMXToken);

// Transfer to contract
if (!wIMX.transferFrom(msg.sender, address(this), amount)) {
try wIMX.transferFrom(msg.sender, address(this), amount) returns (bool success) {
if (!success) revert TransferWIMXFailed();
} catch {
revert TransferWIMXFailed();
}

Expand Down Expand Up @@ -372,7 +407,9 @@ contract ChildERC20Bridge is
}

// Burn tokens
if (!IChildERC20(childToken).burn(msg.sender, amount)) {
try IChildERC20(childToken).burn(msg.sender, amount) returns (bool success) {
if (!success) revert BurnFailed();
} catch {
revert BurnFailed();
}

Expand Down Expand Up @@ -459,10 +496,10 @@ contract ChildERC20Bridge is
revert ZeroAddress();
}

transferTokensAndEmitEvent(rootToken, rootTokenToChildToken[rootToken], sender, receiver, amount);
_transferTokensAndEmitEvent(rootToken, rootTokenToChildToken[rootToken], sender, receiver, amount);
}

function transferTokensAndEmitEvent(
function _transferTokensAndEmitEvent(
address rootToken,
address childToken,
address sender,
Expand All @@ -486,7 +523,9 @@ contract ChildERC20Bridge is
revert EmptyTokenContract();
}

if (!IChildERC20(childToken).mint(receiver, amount)) {
try IChildERC20(childToken).mint(receiver, amount) returns (bool success) {
if (!success) revert MintFailed();
} catch {
revert MintFailed();
}

Expand Down
4 changes: 2 additions & 2 deletions src/common/AdaptorRoles.sol
Original file line number Diff line number Diff line change
Expand Up @@ -24,14 +24,14 @@ abstract contract AdaptorRoles is AccessControlUpgradeable {
/**
* @notice Function to grant bridge manager role to an address
*/
function grantBridgeManager(address account) external onlyRole(DEFAULT_ADMIN_ROLE) {
function grantBridgeManagerRole(address account) external onlyRole(DEFAULT_ADMIN_ROLE) {
grantRole(BRIDGE_MANAGER_ROLE, account);
}

/**
* @notice Function to grant gas service manager role to an address
*/
function grantGasServiceManager(address account) external onlyRole(DEFAULT_ADMIN_ROLE) {
function grantGasServiceManagerRole(address account) external onlyRole(DEFAULT_ADMIN_ROLE) {
grantRole(GAS_SERVICE_MANAGER_ROLE, account);
}

Expand Down
4 changes: 4 additions & 0 deletions src/interfaces/child/IChildAxelarBridgeAdaptor.sol
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,10 @@ interface IChildAxelarBridgeAdaptorErrors {
error InvalidSourceChain();
/// @notice Error when the source chain's message sender is not a recognised address.
error InvalidSourceAddress();
/// @notice Error when the an unauthorized initializer tries to initialize the contract.
error UnauthorizedInitializer();
/// @notice Error when a function that isn't supported by the adaptor is called.
error UnsupportedOperation();
}

/**
Expand Down
2 changes: 2 additions & 0 deletions src/interfaces/child/IChildERC20Bridge.sol
Original file line number Diff line number Diff line change
Expand Up @@ -196,4 +196,6 @@ interface IChildERC20BridgeErrors {
error NonWrappedNativeTransfer();
/// @notice Error when the bridge doesn't have enough native IMX to support the deposit.
error InsufficientIMX();
/// @notice Error when the an unauthorized initializer tries to initialize the contract.
error UnauthorizedInitializer();
}
4 changes: 4 additions & 0 deletions src/interfaces/root/IRootAxelarBridgeAdaptor.sol
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,10 @@ interface IRootAxelarBridgeAdaptorErrors {
error InvalidSourceAddress();
/// @notice Error when a message received has invalid source chain.
error InvalidSourceChain();
/// @notice Error when the an unauthorized initializer tries to initialize the contract.
error UnauthorizedInitializer();
/// @notice Error when a function that isn't supported by the adaptor is called.
error UnsupportedOperation();
}

/**
Expand Down
21 changes: 16 additions & 5 deletions src/interfaces/root/IRootERC20Bridge.sol
Original file line number Diff line number Diff line change
Expand Up @@ -50,14 +50,21 @@ interface IRootERC20Bridge {
function onMessageReceive(bytes calldata data) external;

/**
* @notice Initiate sending a mapToken message to the child chain.
* This is done when a token hasn't been mapped before.
* @dev Populates a root token => child token mapping on parent chain before
* sending a message telling child chain to do the same.
* @notice Initiates sending a mapToken message to the child chain, if the token hasn't been mapped before.
* This operation requires the `rootToken` to have the following public getter functions: `name()`, `symbol()`, and `decimals()`.
* These functions are optional in the ERC20 standard. If the token does not provide these functions,
* the mapping operation will fail and return a `TokenNotSupported` error.
*
* @dev The function:
* - fails with a `AlreadyMapped` error if the token has already been mapped.
* - populates a root token => child token mapping on the root chain before
* sending a message telling the child chain to do the same.
* - is `payable` because the message passing protocol requires a fee to be paid.
*
* @dev The address of the child chain token is deterministic using CREATE2.
*
* @param rootToken The address of the token on the root chain.
* @return childToken The address of the token to be deployed on the child chain.
* @dev The function is `payable` because the message passing protocol requires a fee to be paid.
*/
function mapToken(IERC20Metadata rootToken) external payable returns (address);

Expand Down Expand Up @@ -189,4 +196,8 @@ interface IRootERC20BridgeErrors {
error ImxDepositLimitTooLow();
/// @notice Error when native transfer is sent to contract from non wrapped-token address.
error NonWrappedNativeTransfer();
/// @notice Error when the an unauthorized initializer tries to initialize the contract.
error UnauthorizedInitializer();
/// @notice Error when attempt to map a ERC20 token that doesn't support name(), symbol() or decimals().
error TokenNotSupported();
}
33 changes: 32 additions & 1 deletion src/lib/EIP712MetaTransaction.sol
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ contract EIP712MetaTransaction is EIP712Upgradeable {
bytes32 private constant META_TRANSACTION_TYPEHASH =
keccak256(bytes("MetaTransaction(uint256 nonce,address from,bytes functionSignature)"));

/// @dev Event emitted when a meta transaction is successfully executed.
event MetaTransactionExecuted(address userAddress, address relayerAddress, bytes functionSignature);

mapping(address => uint256) private nonces;
Expand All @@ -23,6 +24,16 @@ contract EIP712MetaTransaction is EIP712Upgradeable {
bytes functionSignature;
}

/**
* @notice Executes a meta transaction on behalf of the user.
* @dev This function allows a user to sign a transaction off-chain and have it executed by another entity.
* @param userAddress The address of the user initiating the transaction.
* @param functionSignature The signature of the function to be executed.
* @param sigR Part of the signature data.
* @param sigS Part of the signature data.
* @param sigV Recovery byte of the signature.
* @return returnData The bytes returned from the executed function.
*/
function executeMetaTransaction(
address userAddress,
bytes calldata functionSignature,
Expand Down Expand Up @@ -53,12 +64,18 @@ contract EIP712MetaTransaction is EIP712Upgradeable {
}

/**
* @dev Invalidates next "offset" number of nonces for the calling address
* @notice Invalidates next "offset" number of nonces for the calling address
* @param offset The number of nonces, from the current nonce, to invalidate.
*/
function invalidateNext(uint256 offset) external {
nonces[msg.sender] += offset;
}

/**
* @notice Retrieves the current nonce for a user.
* @param user The address of the user.
* @return nonce The current nonce of the user.
*/
function getNonce(address user) external view returns (uint256 nonce) {
nonce = nonces[user];
}
Expand All @@ -79,11 +96,21 @@ contract EIP712MetaTransaction is EIP712Upgradeable {
return sender;
}

/**
* @notice Verifies the signature of a meta transaction.
* @param user The address of the user.
* @param metaTx The meta transaction struct.
* @param sigR Part of the signature data.
* @param sigS Part of the signature data.
* @param sigV Recovery byte of the signature.
* @return True if the signature is valid, false otherwise.
*/
function _verify(address user, MetaTransaction memory metaTx, bytes32 sigR, bytes32 sigS, uint8 sigV)
private
view
returns (bool)
{
// The inclusion of a user specific nonce removes signature malleability concerns
address signer = ecrecover(_hashTypedDataV4(_hashMetaTransaction(metaTx)), sigV, sigR, sigS);
require(signer != address(0), "Invalid signature");
return signer == user;
Expand All @@ -95,12 +122,16 @@ contract EIP712MetaTransaction is EIP712Upgradeable {
);
}

/**
* @dev Extract the first four bytes from `inBytes`
*/
function _convertBytesToBytes4(bytes memory inBytes) private pure returns (bytes4 outBytes4) {
if (inBytes.length == 0) {
return 0x0;
}
// slither-disable-next-line assembly
assembly {
// extract the first 4 bytes from inBytes
// solhint-disable no-inline-assembly
outBytes4 := mload(add(inBytes, 32))
}
Expand Down
2 changes: 1 addition & 1 deletion src/lib/WETH.sol
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ contract WETH is IWETH {
require(balanceOf[msg.sender] >= wad, "Wrapped ETH: Insufficient balance");
balanceOf[msg.sender] -= wad;

Address.sendValue(payable(msg.sender), wad);
payable(msg.sender).transfer(wad);
emit Withdrawal(msg.sender, wad);
}

Expand Down
Loading

0 comments on commit d2d9273

Please sign in to comment.