Skip to content

Commit

Permalink
Merge branch 'main' into SMR-1814-2-IMX-withdraw-L1
Browse files Browse the repository at this point in the history
  • Loading branch information
wcgcyx committed Nov 7, 2023
2 parents 2bff7a9 + 33714c2 commit 12a12d8
Show file tree
Hide file tree
Showing 23 changed files with 731 additions and 58 deletions.
3 changes: 2 additions & 1 deletion src/child/ChildAxelarBridgeAdaptor.sol
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,7 @@ contract ChildAxelarBridgeAdaptor is
);

gateway.callContract(_rootChain, _rootBridgeAdaptor, payload);
emit AxelarMessage(_rootChain, _rootBridgeAdaptor, payload);
emit AxelarMessageSent(_rootChain, _rootBridgeAdaptor, payload);
}

/**
Expand All @@ -71,6 +71,7 @@ contract ChildAxelarBridgeAdaptor is
internal
override
{
emit AdaptorExecute(sourceChain_, sourceAddress_, payload_);
childBridge.onMessageReceive(sourceChain_, sourceAddress_, payload_);
}
}
85 changes: 64 additions & 21 deletions src/child/ChildERC20Bridge.sol
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ contract ChildERC20Bridge is
bytes32 public constant DEPOSIT_SIG = keccak256("DEPOSIT");
bytes32 public constant WITHDRAW_SIG = keccak256("WITHDRAW");
address public constant NATIVE_ETH = address(0xeee);
address public constant NATIVE_IMX = address(0xfff);

IChildERC20BridgeAdaptor public bridgeAdaptor;

Expand Down Expand Up @@ -109,20 +110,21 @@ contract ChildERC20Bridge is
if (!Strings.equal(messageSourceChain, rootChain)) {
revert InvalidSourceChain();
}

if (!Strings.equal(sourceAddress, rootERC20BridgeAdaptor)) {
revert InvalidSourceAddress();
}
if (data.length == 0) {
revert InvalidData();
if (data.length <= 32) {
// Data must always be greater than 32.
// 32 bytes for the signature, and at least some information for the payload
revert InvalidData("Data too short");
}

if (bytes32(data[:32]) == MAP_TOKEN_SIG) {
_mapToken(data);
} else if (bytes32(data[:32]) == DEPOSIT_SIG) {
_deposit(data[32:]);
} else {
revert InvalidData();
revert InvalidData("Unsupported action signature");
}
}

Expand All @@ -134,29 +136,65 @@ contract ChildERC20Bridge is
_withdraw(childToken, receiver, amount);
}

function _withdraw(IChildERC20 childToken, address receiver, uint256 amount) private {
if (address(childToken).code.length == 0) {
revert EmptyTokenContract();
}
function withdrawIMX(uint256 amount) external payable {
_withdrawIMX(msg.sender, amount);
}

address rootToken = childToken.rootToken();
function withdrawIMXTo(address receiver, uint256 amount) external payable {
_withdrawIMX(receiver, amount);
}

if (rootTokenToChildToken[rootToken] != address(childToken)) {
revert NotMapped();
function _withdrawIMX(address receiver, uint256 amount) private {
if (msg.value < amount) {
revert InsufficientValue();
}

// A mapped token should never have root token unset
if (rootToken == address(0)) {
revert ZeroAddressRootToken();
uint256 expectedBalance = address(this).balance - (msg.value - amount);

_withdraw(IChildERC20(NATIVE_IMX), receiver, amount);

if (address(this).balance != expectedBalance) {
revert BalanceInvariantCheckFailed(address(this).balance, expectedBalance);
}
}

// A mapped token should never have the bridge unset
if (childToken.bridge() != address(this)) {
revert BridgeNotSet();
function _withdraw(IChildERC20 childToken, address receiver, uint256 amount) private {
if (address(childToken) == address(0)) {
revert ZeroAddress();
}
if (amount == 0) {
revert ZeroAmount();
}

address rootToken;
uint256 feeAmount = msg.value;

if (address(childToken) == NATIVE_IMX) {
feeAmount = msg.value - amount;
rootToken = rootIMXToken;
} else {
if (address(childToken).code.length == 0) {
revert EmptyTokenContract();
}
rootToken = childToken.rootToken();

if (rootTokenToChildToken[rootToken] != address(childToken)) {
revert NotMapped();
}

// A mapped token should never have root token unset
if (rootToken == address(0)) {
revert ZeroAddressRootToken();
}

// A mapped token should never have the bridge unset
if (childToken.bridge() != address(this)) {
revert IncorrectBridgeAddress();
}

if (!childToken.burn(msg.sender, amount)) {
revert BurnFailed();
if (!childToken.burn(msg.sender, amount)) {
revert BurnFailed();
}
}

// TODO Should we enforce receiver != 0? old poly contracts don't
Expand All @@ -165,9 +203,14 @@ contract ChildERC20Bridge is
bytes memory payload = abi.encode(WITHDRAW_SIG, rootToken, msg.sender, receiver, amount);

// Send the message to the bridge adaptor and up to root chain
bridgeAdaptor.sendMessage{value: msg.value}(payload, msg.sender);

emit ChildChainERC20Withdraw(rootToken, address(childToken), msg.sender, receiver, amount);
bridgeAdaptor.sendMessage{value: feeAmount}(payload, msg.sender);

if (address(childToken) == NATIVE_IMX) {
emit ChildChainNativeIMXWithdraw(rootToken, msg.sender, receiver, amount);
} else {
emit ChildChainERC20Withdraw(rootToken, address(childToken), msg.sender, receiver, amount);
}
}

function _mapToken(bytes calldata data) private {
Expand Down
4 changes: 3 additions & 1 deletion src/interfaces/child/IChildAxelarBridgeAdaptor.sol
Original file line number Diff line number Diff line change
Expand Up @@ -12,5 +12,7 @@ interface IChildAxelarBridgeAdaptorErrors {

interface IChildAxelarBridgeAdaptorEvents {
/// @notice Emitted when an Axelar message is sent to the root chain.
event AxelarMessage(string indexed rootChain, string indexed rootBridgeAdaptor, bytes indexed payload);
event AxelarMessageSent(string indexed rootChain, string indexed rootBridgeAdaptor, bytes indexed payload);
/// @notice Emitted when an Axelar message is received from the root chain.
event AdaptorExecute(string sourceChain, string sourceAddress_, bytes payload_);
}
4 changes: 2 additions & 2 deletions src/interfaces/child/IChildERC20.sol
Original file line number Diff line number Diff line change
Expand Up @@ -25,8 +25,8 @@ interface IChildERC20 is IERC20MetadataUpgradeable {
function bridge() external view returns (address);

/**
* @notice Returns bridge address controlling the child token
* @return address Returns the address of the Bridge
* @notice Returns the address of the mapped token on the root chain
* @return address Returns the address of the root token
*/
function rootToken() external view returns (address);

Expand Down
13 changes: 12 additions & 1 deletion src/interfaces/child/IChildERC20Bridge.sol
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,9 @@ interface IChildERC20BridgeEvents {
address indexed receiver,
uint256 amount
);
event ChildChainNativeIMXWithdraw(
address indexed rootToken, address depositor, address indexed receiver, uint256 amount
);

event ChildChainERC20Deposit(
address indexed rootToken,
Expand All @@ -52,6 +55,10 @@ interface IChildERC20BridgeEvents {

// TODO add parameters to errors if it makes sense
interface IChildERC20BridgeErrors {
/// @notice Error when the amount requested is less than the value sent.
error InsufficientValue();
/// @notice Error when the withdrawal amount is zero
error ZeroAmount();
/// @notice Error when the contract to mint had no bytecode.
error EmptyTokenContract();
/// @notice Error when the mint operation failed.
Expand All @@ -73,7 +80,7 @@ interface IChildERC20BridgeErrors {
/// @notice Error when a message is given to the bridge from an address not the designated bridge adaptor.
error NotBridgeAdaptor();
/// @notice Error when the message's payload is not valid.
error InvalidData();
error InvalidData(string reason);
/// @notice Error when the message's source chain is not valid.
error InvalidSourceChain();
/// @notice Error when the source chain's message sender is not a recognised address.
Expand All @@ -82,6 +89,10 @@ interface IChildERC20BridgeErrors {
error ZeroAddressRootToken();
/// @notice Error when a given child token's bridge address is not set.
error BridgeNotSet();
/// @notice Error when a given child token's bridge address is incorrect.
error IncorrectBridgeAddress();
/// @notice Error when a call to the given child token's `burn` function fails.
error BurnFailed();
/// @notice Error when token balance invariant check fails.
error BalanceInvariantCheckFailed(uint256 actualBalance, uint256 expectedBalance);
}
4 changes: 3 additions & 1 deletion src/interfaces/root/IRootAxelarBridgeAdaptor.sol
Original file line number Diff line number Diff line change
Expand Up @@ -14,5 +14,7 @@ interface IRootAxelarBridgeAdaptorErrors {

interface IRootAxelarBridgeAdaptorEvents {
/// @notice Emitted when an Axelar message is sent to the child chain.
event AxelarMessage(string indexed childChain, string indexed childBridgeAdaptor, bytes indexed payload);
event AxelarMessageSent(string indexed childChain, string indexed childBridgeAdaptor, bytes indexed payload);
/// @notice Emitted when an Axelar message is received from the child chain.
event AdaptorExecute(string sourceChain, string sourceAddress_, bytes payload_);
}
2 changes: 1 addition & 1 deletion src/interfaces/root/IRootERC20Bridge.sol
Original file line number Diff line number Diff line change
Expand Up @@ -106,7 +106,7 @@ interface IRootERC20BridgeErrors {
/// @notice Error when the given child chain bridge adaptor is invalid.
error InvalidChildERC20BridgeAdaptor();
/// @notice Error when a message received has invalid data.
error InvalidData();
error InvalidData(string reason);
/// @notice Error when a message received has invalid source address.
error InvalidSourceAddress();
/// @notice Error when a message received has invalid source chain.
Expand Down
3 changes: 2 additions & 1 deletion src/root/RootAxelarBridgeAdaptor.sol
Original file line number Diff line number Diff line change
Expand Up @@ -78,7 +78,7 @@ contract RootAxelarBridgeAdaptor is
);

gateway.callContract(_childChain, _childBridgeAdaptor, payload);
emit AxelarMessage(_childChain, _childBridgeAdaptor, payload);
emit AxelarMessageSent(_childChain, _childBridgeAdaptor, payload);
}

/**
Expand All @@ -88,6 +88,7 @@ contract RootAxelarBridgeAdaptor is
internal
override
{
emit AdaptorExecute(sourceChain_, sourceAddress_, payload_);
rootBridge.onMessageReceive(sourceChain_, sourceAddress_, payload_);
}
}
10 changes: 6 additions & 4 deletions src/root/RootERC20Bridge.sol
Original file line number Diff line number Diff line change
Expand Up @@ -165,14 +165,16 @@ contract RootERC20Bridge is
if (!Strings.equal(sourceAddress, childBridgeAdaptor)) {
revert InvalidSourceAddress();
}
if (data.length == 0) {
revert InvalidData();
if (data.length <= 32) {
// Data must always be greater than 32.
// 32 bytes for the signature, and at least some information for the payload
revert InvalidData("Data too short");
}

if (bytes32(data[:32]) == WITHDRAW_SIG) {
_withdraw(data[32:]);
} else {
revert InvalidData();
revert InvalidData("Unsupported action signature");
}
}

Expand Down Expand Up @@ -380,6 +382,6 @@ contract RootERC20Bridge is
IERC20Metadata(rootToken).safeTransfer(receiver, amount);
}
// slither-disable-next-line reentrancy-events
emit RootChainERC20Withdraw(address(rootToken), childToken, withdrawer, receiver, amount);
emit RootChainERC20Withdraw(rootToken, childToken, withdrawer, receiver, amount);
}
}
4 changes: 2 additions & 2 deletions test/integration/child/ChildAxelarBridge.t.sol
Original file line number Diff line number Diff line change
Expand Up @@ -83,7 +83,7 @@ contract ChildERC20BridgeIntegrationTest is Test, IChildERC20BridgeEvents, IChil
bytes32 commandId = bytes32("testCommandId");
bytes memory payload = abi.encode("invalid payload");

vm.expectRevert(InvalidData.selector);
vm.expectRevert(abi.encodeWithSelector(InvalidData.selector, "Unsupported action signature"));
childAxelarBridgeAdaptor.execute(commandId, ROOT_CHAIN_NAME, ROOT_ADAPTOR_ADDRESS, payload);
}

Expand All @@ -109,7 +109,7 @@ contract ChildERC20BridgeIntegrationTest is Test, IChildERC20BridgeEvents, IChil
bytes32 commandId = bytes32("testCommandId");
bytes memory payload = "";

vm.expectRevert(InvalidData.selector);
vm.expectRevert(abi.encodeWithSelector(InvalidData.selector, "Data too short"));
childAxelarBridgeAdaptor.execute(commandId, ROOT_CHAIN_NAME, ROOT_ADAPTOR_ADDRESS, payload);
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -106,14 +106,14 @@ contract ChildERC20BridgeWithdrawIntegrationTest is
childBridge.withdraw{value: withdrawFee}(childToken, withdrawAmount);
}

function test_withdraw_emits_AxelarMessageEvent() public {
function test_withdraw_emits_AxelarMessageSentEvent() public {
ChildERC20 childToken = ChildERC20(childBridge.rootTokenToChildToken(rootToken));

bytes memory predictedPayload =
abi.encode(WITHDRAW_SIG, rootToken, address(this), address(this), withdrawAmount);

vm.expectEmit(address(axelarAdaptor));
emit AxelarMessage(childBridge.rootChain(), childBridge.rootERC20BridgeAdaptor(), predictedPayload);
emit AxelarMessageSent(childBridge.rootChain(), childBridge.rootERC20BridgeAdaptor(), predictedPayload);
childBridge.withdraw{value: withdrawFee}(childToken, withdrawAmount);
}

Expand Down
Loading

0 comments on commit 12a12d8

Please sign in to comment.