Skip to content

Commit

Permalink
Merge branch 'main' of github.com:immutable/zkevm-bridge-contracts in…
Browse files Browse the repository at this point in the history
…to smr-1915-ub-nonstandard-erc20
  • Loading branch information
Benjimmutable committed Nov 7, 2023
2 parents 0e5264d + fabfe75 commit 8fc4cb1
Show file tree
Hide file tree
Showing 5 changed files with 144 additions and 12 deletions.
12 changes: 9 additions & 3 deletions src/root/RootERC20Bridge.sol
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,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 @@ -378,9 +379,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,7 +102,7 @@ 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;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -163,7 +163,7 @@ 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;

Expand All @@ -176,7 +176,7 @@ contract ChildERC20BridgewithdrawIMXToIntegrationTest is
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 Down
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ contract RootERC20BridgeWithdrawIntegrationTest is
address constant NATIVE_ETH = address(0xeee);
address constant WRAPPED_ETH = address(0xddd);
uint256 constant UNLIMITED_DEPOSIT_LIMIT = 0;
address public constant NATIVE_IMX = address(0xfff);

uint256 constant withdrawAmount = 0.5 ether;

Expand Down Expand Up @@ -66,6 +67,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 @@ -126,6 +128,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 @@ -145,6 +165,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 @@ -162,6 +201,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 @@ -175,4 +225,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 @@ -20,13 +20,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 @@ -36,6 +39,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 Down Expand Up @@ -68,22 +73,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 @@ -102,15 +107,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 @@ -135,6 +139,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 @@ -155,6 +176,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 @@ -174,6 +215,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 @@ -189,4 +241,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 8fc4cb1

Please sign in to comment.