Skip to content

Commit

Permalink
Merge pull request #22 from immutable/SMR-1814-2-IMX-withdraw-L1
Browse files Browse the repository at this point in the history
Smr 1814 Part 2 imx withdraw on L1
  • Loading branch information
wcgcyx authored Nov 7, 2023
2 parents 33714c2 + fe96c6c commit fabfe75
Show file tree
Hide file tree
Showing 5 changed files with 147 additions and 15 deletions.
12 changes: 9 additions & 3 deletions src/root/RootERC20Bridge.sol
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ contract RootERC20Bridge 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);

IRootERC20BridgeAdaptor public rootBridgeAdaptor;
/// @dev Used to verify source address in messages sent from child chain.
Expand Down Expand Up @@ -354,9 +355,14 @@ contract RootERC20Bridge is
function _withdraw(bytes memory data) private {
(address rootToken, address withdrawer, address receiver, uint256 amount) =
abi.decode(data, (address, address, address, uint256));
address childToken = rootTokenToChildToken[rootToken];
if (childToken == address(0)) {
revert NotMapped();
address childToken;
if (address(rootToken) == rootIMXToken) {
childToken = NATIVE_IMX;
} else {
childToken = rootTokenToChildToken[rootToken];
if (childToken == address(0)) {
revert NotMapped();
}
}
_executeTransfer(rootToken, childToken, withdrawer, receiver, amount);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -102,15 +102,15 @@ contract ChildERC20BridgeWithdrawIMXIntegrationTest is
childBridge.withdrawIMX{value: withdrawFee + withdrawAmount}(withdrawAmount);
}

function test_WithdrawIMX_EmitsAxelarMessageEvent() public {
function test_WithdrawIMX_EmitsAxelarMessageSentEvent() public {
uint256 withdrawFee = 300;
uint256 withdrawAmount = 7 ether;

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

vm.expectEmit(address(axelarAdaptor));
emit AxelarMessage(childBridge.rootChain(), childBridge.rootERC20BridgeAdaptor(), predictedPayload);
emit AxelarMessageSent(childBridge.rootChain(), childBridge.rootERC20BridgeAdaptor(), predictedPayload);

childBridge.withdrawIMX{value: withdrawFee + withdrawAmount}(withdrawAmount);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -163,20 +163,20 @@ contract ChildERC20BridgewithdrawIMXToIntegrationTest is
childBridge.withdrawIMXTo{value: withdrawFee + withdrawAmount}(receiver, withdrawAmount);
}

function test_withdrawIMXTo_EmitsAxelarMessageEvent() public {
function test_withdrawIMXTo_EmitsAxelarMessageSentEvent() public {
uint256 withdrawFee = 300;
uint256 withdrawAmount = 7 ether;

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

vm.expectEmit(address(axelarAdaptor));
emit AxelarMessage(childBridge.rootChain(), childBridge.rootERC20BridgeAdaptor(), predictedPayload);
emit AxelarMessageSent(childBridge.rootChain(), childBridge.rootERC20BridgeAdaptor(), predictedPayload);

childBridge.withdrawIMXTo{value: withdrawFee + withdrawAmount}(address(this), withdrawAmount);
}

function test_withdrawIMXToWithDifferentAccount_EmitsAxelarMessageEvent() public {
function test_withdrawIMXToWithDifferentAccount_EmitsAxelarMessageSentEvent() public {
address receiver = address(0xabcd);
uint256 withdrawFee = 300;
uint256 withdrawAmount = 7 ether;
Expand All @@ -185,7 +185,7 @@ contract ChildERC20BridgewithdrawIMXToIntegrationTest is
abi.encode(WITHDRAW_SIG, ROOT_IMX_TOKEN, address(this), receiver, withdrawAmount);

vm.expectEmit(address(axelarAdaptor));
emit AxelarMessage(childBridge.rootChain(), childBridge.rootERC20BridgeAdaptor(), predictedPayload);
emit AxelarMessageSent(childBridge.rootChain(), childBridge.rootERC20BridgeAdaptor(), predictedPayload);

childBridge.withdrawIMXTo{value: withdrawFee + withdrawAmount}(receiver, withdrawAmount);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ contract RootERC20BridgeWithdrawIntegrationTest is
address constant IMX_TOKEN_ADDRESS = address(0xccc);
address constant NATIVE_ETH = address(0xeee);
address constant WRAPPED_ETH = address(0xddd);
address public constant NATIVE_IMX = address(0xfff);
uint256 constant UNLIMITED_IMX_DEPOSIT_LIMIT = 0;

uint256 constant withdrawAmount = 0.5 ether;
Expand Down Expand Up @@ -59,6 +60,7 @@ contract RootERC20BridgeWithdrawIntegrationTest is
rootBridge.mapToken{value: 1}(token);
// And give the bridge some tokens
token.transfer(address(rootBridge), 100 ether);
imxToken.transfer(address(rootBridge), 100 ether);
}

function test_RevertsIf_WithdrawWithInvalidSourceChain() public {
Expand Down Expand Up @@ -119,6 +121,24 @@ contract RootERC20BridgeWithdrawIntegrationTest is
assertEq(bridgePostBal, bridgePreBal - withdrawAmount, "Incorrect bridge balance after withdraw");
}

function test_withdrawIMX_TransfersIMX() public {
bytes memory data = abi.encode(WITHDRAW_SIG, IMX_TOKEN_ADDRESS, address(this), address(this), withdrawAmount);

bytes32 commandId = bytes32("testCommandId");
string memory sourceAddress = rootBridge.childBridgeAdaptor();

uint256 thisPreBal = imxToken.balanceOf(address(this));
uint256 bridgePreBal = imxToken.balanceOf(address(rootBridge));

axelarAdaptor.execute(commandId, CHILD_CHAIN_NAME, sourceAddress, data);

uint256 thisPostBal = imxToken.balanceOf(address(this));
uint256 bridgePostBal = imxToken.balanceOf(address(rootBridge));

assertEq(thisPostBal, thisPreBal + withdrawAmount, "Incorrect user balance after withdraw");
assertEq(bridgePostBal, bridgePreBal - withdrawAmount, "Incorrect bridge balance after withdraw");
}

function test_withdraw_TransfersTokens_DifferentReceiver() public {
address receiver = address(987654321);
bytes memory data = abi.encode(WITHDRAW_SIG, address(token), address(this), receiver, withdrawAmount);
Expand All @@ -138,6 +158,25 @@ contract RootERC20BridgeWithdrawIntegrationTest is
assertEq(bridgePostBal, bridgePreBal - withdrawAmount, "Incorrect bridge balance after withdraw");
}

function test_withdrawIMX_TransfersIMX_DifferentReceiver() public {
address receiver = address(987654321);
bytes memory data = abi.encode(WITHDRAW_SIG, IMX_TOKEN_ADDRESS, address(this), receiver, withdrawAmount);

bytes32 commandId = bytes32("testCommandId");
string memory sourceAddress = rootBridge.childBridgeAdaptor();

uint256 receiverPreBal = imxToken.balanceOf(receiver);
uint256 bridgePreBal = imxToken.balanceOf(address(rootBridge));

axelarAdaptor.execute(commandId, CHILD_CHAIN_NAME, sourceAddress, data);

uint256 receiverPostBal = imxToken.balanceOf(receiver);
uint256 bridgePostBal = imxToken.balanceOf(address(rootBridge));

assertEq(receiverPostBal, receiverPreBal + withdrawAmount, "Incorrect user balance after withdraw");
assertEq(bridgePostBal, bridgePreBal - withdrawAmount, "Incorrect bridge balance after withdraw");
}

function test_withdraw_EmitsRootChainERC20WithdrawEvent() public {
bytes memory data = abi.encode(WITHDRAW_SIG, address(token), address(this), address(this), withdrawAmount);

Expand All @@ -155,6 +194,17 @@ contract RootERC20BridgeWithdrawIntegrationTest is
axelarAdaptor.execute(commandId, CHILD_CHAIN_NAME, sourceAddress, data);
}

function test_withdrawIMX_EmitsRootChainERC20WithdrawEvent() public {
bytes memory data = abi.encode(WITHDRAW_SIG, IMX_TOKEN_ADDRESS, address(this), address(this), withdrawAmount);

bytes32 commandId = bytes32("testCommandId");
string memory sourceAddress = rootBridge.childBridgeAdaptor();

vm.expectEmit();
emit RootChainERC20Withdraw(address(imxToken), NATIVE_IMX, address(this), address(this), withdrawAmount);
axelarAdaptor.execute(commandId, CHILD_CHAIN_NAME, sourceAddress, data);
}

function test_withdraw_EmitsRootChainERC20WithdrawEvent_DifferentReceiver() public {
address receiver = address(987654321);
bytes memory data = abi.encode(WITHDRAW_SIG, address(token), address(this), receiver, withdrawAmount);
Expand All @@ -168,4 +218,16 @@ contract RootERC20BridgeWithdrawIntegrationTest is
);
axelarAdaptor.execute(commandId, CHILD_CHAIN_NAME, sourceAddress, data);
}

function test_withdrawIMX_EmitsRootChainERC20WithdrawEvent_DifferentReceiver() public {
address receiver = address(987654321);
bytes memory data = abi.encode(WITHDRAW_SIG, IMX_TOKEN_ADDRESS, address(this), receiver, withdrawAmount);

bytes32 commandId = bytes32("testCommandId");
string memory sourceAddress = rootBridge.childBridgeAdaptor();

vm.expectEmit();
emit RootChainERC20Withdraw(address(imxToken), NATIVE_IMX, address(this), receiver, withdrawAmount);
axelarAdaptor.execute(commandId, CHILD_CHAIN_NAME, sourceAddress, data);
}
}
76 changes: 70 additions & 6 deletions test/unit/root/withdrawals/RootERC20BridgeWithdraw.t.sol
Original file line number Diff line number Diff line change
Expand Up @@ -17,13 +17,16 @@ contract RootERC20BridgeWithdrawUnitTest is Test, IRootERC20BridgeEvents, IRootE
address constant CHILD_BRIDGE_ADAPTOR = address(4);
string CHILD_BRIDGE_ADAPTOR_STRING = Strings.toHexString(CHILD_BRIDGE_ADAPTOR);
string constant CHILD_CHAIN_NAME = "test";
address constant UnmappedToken = address(0xbbb);
address constant IMX_TOKEN = address(0xccc);
address constant WRAPPED_ETH = address(0xddd);
address public constant NATIVE_IMX = address(0xfff);
uint256 constant mapTokenFee = 300;
uint256 constant withdrawAmount = 0.5 ether;
uint256 constant UNLIMITED_IMX_DEPOSIT_LIMIT = 0;

ERC20PresetMinterPauser public token;
ERC20PresetMinterPauser public imxToken;
RootERC20Bridge public rootBridge;
MockAdaptor public mockAxelarAdaptor;
MockAxelarGateway public mockAxelarGateway;
Expand All @@ -33,6 +36,8 @@ contract RootERC20BridgeWithdrawUnitTest is Test, IRootERC20BridgeEvents, IRootE
token = new ERC20PresetMinterPauser("Test", "TST");
token.mint(address(this), 100 ether);
deployCodeTo("ERC20PresetMinterPauser.sol", abi.encode("ImmutableX", "IMX"), IMX_TOKEN);
imxToken = ERC20PresetMinterPauser(IMX_TOKEN);
imxToken.mint(address(this), 100 ether);

deployCodeTo("WETH.sol", abi.encode("Wrapped ETH", "WETH"), WRAPPED_ETH);

Expand All @@ -56,22 +61,22 @@ contract RootERC20BridgeWithdrawUnitTest is Test, IRootERC20BridgeEvents, IRootE
}

function test_RevertsIf_WithdrawWithInvalidSender() public {
bytes memory data = abi.encode(WITHDRAW_SIG, IMX_TOKEN, address(this), address(this), withdrawAmount);
bytes memory data = abi.encode(WITHDRAW_SIG, token, address(this), address(this), withdrawAmount);

vm.expectRevert(NotBridgeAdaptor.selector);
rootBridge.onMessageReceive(CHILD_CHAIN_NAME, CHILD_BRIDGE_ADAPTOR_STRING, data);
}

function test_RevertsIf_OnMessageReceiveWithInvalidSourceChain() public {
bytes memory data = abi.encode(WITHDRAW_SIG, IMX_TOKEN, address(this), address(this), withdrawAmount);
bytes memory data = abi.encode(WITHDRAW_SIG, token, address(this), address(this), withdrawAmount);

vm.prank(address(mockAxelarAdaptor));
vm.expectRevert(InvalidSourceChain.selector);
rootBridge.onMessageReceive("ding_dong", CHILD_BRIDGE_ADAPTOR_STRING, data);
}

function test_RevertsIf_OnMessageReceiveWithInvalidSourceAddress() public {
bytes memory data = abi.encode(WITHDRAW_SIG, IMX_TOKEN, address(this), address(this), withdrawAmount);
bytes memory data = abi.encode(WITHDRAW_SIG, token, address(this), address(this), withdrawAmount);

console2.log(CHILD_CHAIN_NAME);
console2.log(rootBridge.childChain());
Expand All @@ -90,15 +95,14 @@ contract RootERC20BridgeWithdrawUnitTest is Test, IRootERC20BridgeEvents, IRootE
}

function test_RevertsIf_OnMessageReceiveWithInvalidSignature() public {
bytes memory data = abi.encode(keccak256("RANDOM"), IMX_TOKEN, address(this), address(this), withdrawAmount);

bytes memory data = abi.encode(keccak256("RANDOM"), token, address(this), address(this), withdrawAmount);
vm.prank(address(mockAxelarAdaptor));
vm.expectRevert(abi.encodeWithSelector(InvalidData.selector, "Unsupported action signature"));
rootBridge.onMessageReceive(CHILD_CHAIN_NAME, CHILD_BRIDGE_ADAPTOR_STRING, data);
}

function test_RevertsIf_OnMessageReceiveWithUnmappedToken() public {
bytes memory data = abi.encode(WITHDRAW_SIG, IMX_TOKEN, address(this), address(this), withdrawAmount);
bytes memory data = abi.encode(WITHDRAW_SIG, UnmappedToken, address(this), address(this), withdrawAmount);
vm.prank(address(mockAxelarAdaptor));
vm.expectRevert(NotMapped.selector);
rootBridge.onMessageReceive(CHILD_CHAIN_NAME, CHILD_BRIDGE_ADAPTOR_STRING, data);
Expand All @@ -123,6 +127,23 @@ contract RootERC20BridgeWithdrawUnitTest is Test, IRootERC20BridgeEvents, IRootE
);
}

function test_onMessageReceive_TransfersIMXTokens() public {
// Give bridge some IMX tokens
imxToken.transfer(address(rootBridge), 100 ether);

uint256 thisPreBal = imxToken.balanceOf(address(this));
uint256 bridgePreBal = imxToken.balanceOf(address(rootBridge));

bytes memory data = abi.encode(WITHDRAW_SIG, imxToken, address(this), address(this), withdrawAmount);
vm.prank(address(mockAxelarAdaptor));
rootBridge.onMessageReceive(CHILD_CHAIN_NAME, CHILD_BRIDGE_ADAPTOR_STRING, data);

assertEq(imxToken.balanceOf(address(this)), thisPreBal + withdrawAmount, "IMX not transferred to receiver");
assertEq(
imxToken.balanceOf(address(rootBridge)), bridgePreBal - withdrawAmount, "IMX not transferred from bridge"
);
}

function test_onMessageReceive_TransfersTokens_DifferentReceiver() public {
address receiver = address(123456);
// Need to first map the token.
Expand All @@ -143,6 +164,26 @@ contract RootERC20BridgeWithdrawUnitTest is Test, IRootERC20BridgeEvents, IRootE
);
}

function test_onMessageReceive_TransfersIMXTokens_DifferentReceiver() public {
address receiver = address(123456);
// Give bridge some IMX tokens
imxToken.transfer(address(rootBridge), 100 ether);

uint256 receiverPreBal = imxToken.balanceOf(address(receiver));
uint256 bridgePreBal = imxToken.balanceOf(address(rootBridge));

bytes memory data = abi.encode(WITHDRAW_SIG, imxToken, address(this), receiver, withdrawAmount);
vm.prank(address(mockAxelarAdaptor));
rootBridge.onMessageReceive(CHILD_CHAIN_NAME, CHILD_BRIDGE_ADAPTOR_STRING, data);

assertEq(
imxToken.balanceOf(address(receiver)), receiverPreBal + withdrawAmount, "IMX not transferred to receiver"
);
assertEq(
imxToken.balanceOf(address(rootBridge)), bridgePreBal - withdrawAmount, "IMX not transferred from bridge"
);
}

function test_onMessageReceive_EmitsRootChainERC20WithdrawEvent() public {
// Need to first map the token.
rootBridge.mapToken(token);
Expand All @@ -162,6 +203,17 @@ contract RootERC20BridgeWithdrawUnitTest is Test, IRootERC20BridgeEvents, IRootE
rootBridge.onMessageReceive(CHILD_CHAIN_NAME, CHILD_BRIDGE_ADAPTOR_STRING, data);
}

function test_onMessageReceive_EmitsRootChainERC20WithdrawEventForIMX() public {
// Give bridge some IMX tokens
imxToken.transfer(address(rootBridge), 100 ether);

bytes memory data = abi.encode(WITHDRAW_SIG, imxToken, address(this), address(this), withdrawAmount);
vm.expectEmit();
emit RootChainERC20Withdraw(address(imxToken), NATIVE_IMX, address(this), address(this), withdrawAmount);
vm.prank(address(mockAxelarAdaptor));
rootBridge.onMessageReceive(CHILD_CHAIN_NAME, CHILD_BRIDGE_ADAPTOR_STRING, data);
}

function test_onMessageReceive_EmitsRootChainERC20WithdrawEvent_DifferentReceiver() public {
address receiver = address(123456);
// Need to first map the token.
Expand All @@ -177,4 +229,16 @@ contract RootERC20BridgeWithdrawUnitTest is Test, IRootERC20BridgeEvents, IRootE
vm.prank(address(mockAxelarAdaptor));
rootBridge.onMessageReceive(CHILD_CHAIN_NAME, CHILD_BRIDGE_ADAPTOR_STRING, data);
}

function test_onMessageReceive_EmitsRootChainERC20WithdrawEventForIMX_DifferentReceiver() public {
address receiver = address(123456);
// Give bridge some IMX tokens
imxToken.transfer(address(rootBridge), 100 ether);

bytes memory data = abi.encode(WITHDRAW_SIG, imxToken, address(this), receiver, withdrawAmount);
vm.expectEmit();
emit RootChainERC20Withdraw(address(imxToken), NATIVE_IMX, address(this), receiver, withdrawAmount);
vm.prank(address(mockAxelarAdaptor));
rootBridge.onMessageReceive(CHILD_CHAIN_NAME, CHILD_BRIDGE_ADAPTOR_STRING, data);
}
}

0 comments on commit fabfe75

Please sign in to comment.