From 6df13fc697903a40e0a5ce9368c756a173746666 Mon Sep 17 00:00:00 2001 From: wcgcyx Date: Mon, 9 Oct 2023 15:04:31 +1000 Subject: [PATCH 01/21] Add WIMX --- .vscode/settings.json | 3 + src/WIMX.sol | 64 +++++++++ test/WIMX.t.sol | 321 ++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 388 insertions(+) create mode 100644 .vscode/settings.json create mode 100644 src/WIMX.sol create mode 100644 test/WIMX.t.sol diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 00000000..e4ec924d --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,3 @@ +{ + "solidity.compileUsingRemoteVersion": "v0.8.21+commit.d9974bed" +} \ No newline at end of file diff --git a/src/WIMX.sol b/src/WIMX.sol new file mode 100644 index 00000000..9e5b555c --- /dev/null +++ b/src/WIMX.sol @@ -0,0 +1,64 @@ +// SPDX-License-Identifier: UNKNOWN +pragma solidity ^0.8.21; + +contract WIMX { + string public name = "Wrapped IMX"; + string public symbol = "WIMX"; + uint8 public decimals = 18; + + event Approval(address indexed src, address indexed guy, uint wad); + event Transfer(address indexed src, address indexed dst, uint wad); + event Deposit(address indexed dst, uint wad); + event Withdrawal(address indexed src, uint wad); + + mapping (address => uint) public balanceOf; + mapping (address => mapping (address => uint)) public allowance; + + receive() external payable { + deposit(); + } + function deposit() public payable { + balanceOf[msg.sender] += msg.value; + emit Deposit(msg.sender, msg.value); + } + function withdraw(uint wad) public { + require(balanceOf[msg.sender] >= wad); + balanceOf[msg.sender] -= wad; + + payable(msg.sender).transfer(wad); + emit Withdrawal(msg.sender, wad); + } + + function totalSupply() public view returns (uint) { + return address(this).balance; + } + + function approve(address guy, uint wad) public returns (bool) { + allowance[msg.sender][guy] = wad; + emit Approval(msg.sender, guy, wad); + return true; + } + + function transfer(address dst, uint wad) public returns (bool) { + return transferFrom(msg.sender, dst, wad); + } + + function transferFrom(address src, address dst, uint wad) + public + returns (bool) + { + require(balanceOf[src] >= wad); + + if (src != msg.sender && allowance[src][msg.sender] != type(uint256).max) { + require(allowance[src][msg.sender] >= wad); + allowance[src][msg.sender] -= wad; + } + + balanceOf[src] -= wad; + balanceOf[dst] += wad; + + emit Transfer(src, dst, wad); + + return true; + } +} \ No newline at end of file diff --git a/test/WIMX.t.sol b/test/WIMX.t.sol new file mode 100644 index 00000000..03cda3e0 --- /dev/null +++ b/test/WIMX.t.sol @@ -0,0 +1,321 @@ +// SPDX-License-Identifier: UNKNOWN +pragma solidity ^0.8.21; + +import {Test} from "forge-std/Test.sol"; +import {WIMX} from "../src/WIMX.sol"; + +contract WIMXTest is Test { + string constant DEFAULT_WIMX_NAME = "Wrapped IMX"; + string constant DEFAULT_WIMX_SYMBOL = "WIMX"; + + event Approval(address indexed src, address indexed guy, uint wad); + event Transfer(address indexed src, address indexed dst, uint wad); + event Deposit(address indexed dst, uint wad); + event Withdrawal(address indexed src, uint wad); + + WIMX public wIMX; + + function setUp() public { + wIMX = new WIMX(); + } + + function test_InitialState() public { + assertEq(wIMX.name(), DEFAULT_WIMX_NAME, "Incorrect token name"); + assertEq(wIMX.symbol(), DEFAULT_WIMX_SYMBOL, "Incorrect token symbol"); + assertEq(wIMX.totalSupply(), 0, "Incorrect token supply"); + } + + function test_RevertIf_DepositWithInsufficientBalance() public { + // Create a user and fund it with 1 IMX + address user = address(1234); + vm.deal(user, 1 ether); + + // Before deposit, user should have 0 wIMX + assertEq(user.balance, 1 ether, "User shoud have 1 IMX"); + assertEq(wIMX.balanceOf(user), 0, "User shoud have 0 wIMX"); + + vm.prank(user); + // Deposit should revert because user has only 1 IMX + vm.expectRevert(); + wIMX.deposit{value: 2 ether}(); + + // After deposit, user should have 0 wIMX + assertEq(user.balance, 1 ether, "User shoud have 1 IMX"); + assertEq(wIMX.balanceOf(user), 0, "User shoud have 0 wIMX"); + } + + function test_RevertIf_TransferWithInsufficientBalance() public { + // Create a user and fund it with 1 IMX + address user = address(1234); + vm.deal(user, 1 ether); + + // Before deposit, user should have 0 wIMX + assertEq(user.balance, 1 ether, "User shoud have 1 IMX"); + assertEq(wIMX.balanceOf(user), 0, "User shoud have 0 wIMX"); + + vm.prank(user); + // Deposit should revert because user has only 1 IMX + vm.expectRevert(); + address(wIMX).call{value: 2 ether}(""); + + // After deposit, user should have 0 wIMX + assertEq(user.balance, 1 ether, "User shoud have 1 IMX"); + assertEq(wIMX.balanceOf(user), 0, "User shoud have 0 wIMX"); + } + + function test_SucceedIf_DepositWithSufficientBalance() public { + // Create a user and fund it with 1 IMX + address user = address(1234); + vm.deal(user, 1 ether); + + // Before deposit, user should have 0 wIMX + assertEq(user.balance, 1 ether, "User shoud have 1 IMX"); + assertEq(wIMX.balanceOf(user), 0, "User shoud have 0 wIMX"); + + vm.prank(user); + vm.expectEmit(address(wIMX)); + emit Deposit(user, 0.1 ether); + wIMX.deposit{value: 0.1 ether}(); + + // After deposit, user should have 0.1 wIMX + assertEq(user.balance, 0.9 ether, "User shoud have 0.9 IMX"); + assertEq(wIMX.balanceOf(user), 0.1 ether, "User shoud have 0.1 wIMX"); + } + + function test_SucceedIf_TransferWithSufficientBalance() public { + // Create a user and fund it with 1 IMX + address user = address(1234); + vm.deal(user, 1 ether); + + // Before deposit, user should have 0 wIMX + assertEq(user.balance, 1 ether, "User shoud have 1 IMX"); + assertEq(wIMX.balanceOf(user), 0, "User shoud have 0 wIMX"); + + vm.prank(user); + vm.expectEmit(address(wIMX)); + emit Deposit(user, 0.1 ether); + address(wIMX).call{value: 0.1 ether}(""); + + // After deposit, user should have 0.1 wIMX + assertEq(user.balance, 0.9 ether, "User shoud have 0.9 wIMX"); + assertEq(wIMX.balanceOf(user), 0.1 ether, "User shoud have 0.1 IMX"); + } + + function test_RevertIf_OverWithdraw() public { + // Create a user and fund it with 1 IMX + address user = address(1234); + vm.deal(user, 1 ether); + + // Deposit 0.1 IMX + vm.startPrank(user); + wIMX.deposit{value: 0.1 ether}(); + + // Try withdraw 0.2 wIMX + vm.expectRevert(); + wIMX.withdraw(0.2 ether); + vm.stopPrank(); + + // User should still have 0.1 wIMX and 0.9 IMX + assertEq(user.balance, 0.9 ether, "User shoud have 0.9 IMX"); + assertEq(wIMX.balanceOf(user), 0.1 ether, "User shoud have 0.1 wIMX"); + } + + function test_SucceedIf_WithdrawWithinLimit() public { + // Create a user and fund it with 1 IMX + address user = address(1234); + vm.deal(user, 1 ether); + + // Deposit 0.1 IMX + vm.startPrank(user); + wIMX.deposit{value: 0.1 ether}(); + + // Try withdraw 0.05 wIMX + vm.expectEmit(address(wIMX)); + emit Withdrawal(user, 0.05 ether); + wIMX.withdraw(0.05 ether); + vm.stopPrank(); + + // User should have 0.05 wIMX and 0.95 IMX + assertEq(user.balance, 0.95 ether, "User shoud have 0.95 IMX"); + assertEq(wIMX.balanceOf(user), 0.05 ether, "User shoud have 0.05 wIMX"); + } + + function test_SupplyUpdated_OnDepositAndWithdraw() public { + // Create a user and fund it with 1 IMX + address user = address(1234); + vm.deal(user, 1 ether); + + assertEq(wIMX.totalSupply(), 0, "Token supply should be 0 at start"); + + // Deposit 0.1 IMX + vm.startPrank(user); + wIMX.deposit{value: 0.1 ether}(); + assertEq(wIMX.totalSupply(), 0.1 ether, "Token supply should be 0.1 after deposit"); + + // Withdraw 0.05 IMX + wIMX.withdraw(0.05 ether); + assertEq(wIMX.totalSupply(), 0.05 ether, "Token supply should be 0.05 after withdraw"); + + vm.stopPrank(); + + // Create another user and fund it with 1 IMX + address user2 = address(1235); + vm.deal(user2, 1 ether); + + // Deposit 0.5 IMX + vm.startPrank(user2); + wIMX.deposit{value: 0.5 ether}(); + assertEq(wIMX.totalSupply(), 0.55 ether, "Token supply should be 0.55 after 2nd deposit"); + + // Withdraw 0.5 IMX + vm.startPrank(user2); + wIMX.withdraw(0.5 ether); + assertEq(wIMX.totalSupply(), 0.05 ether, "Token supply should be 0.05 after 2nd withdraw"); + } + + function test_RevertIf_TransferAmountExceedingBalance() public { + // Create a user and fund it with 1 IMX + address user = address(1234); + vm.deal(user, 1 ether); + // Create a recipient + address recipient = address(1235); + + // Deposit 0.1 IMX + vm.startPrank(user); + wIMX.deposit{value: 0.1 ether}(); + + // Transfer 0.2 wIMX to recipient should revert + vm.expectRevert(); + wIMX.transfer(recipient, 0.2 ether); + + vm.stopPrank(); + } + + function test_SucceedIf_TransferAmountWithinBalance() public { + // Create a user and fund it with 1 IMX + address user = address(1234); + vm.deal(user, 1 ether); + // Create a recipient + address recipient = address(1235); + + assertEq(wIMX.balanceOf(user), 0, "User shoud have 0 wIMX"); + assertEq(wIMX.balanceOf(recipient), 0, "Recipient shoud have 0 wIMX"); + + // Deposit 0.1 IMX + vm.startPrank(user); + wIMX.deposit{value: 0.1 ether}(); + + assertEq(wIMX.balanceOf(user), 0.1 ether, "User shoud have 0.1 wIMX"); + assertEq(wIMX.balanceOf(recipient), 0, "Recipient shoud have 0 wIMX"); + + // Transfer 0.05 wIMX to recipient should revert + vm.expectEmit(address(wIMX)); + emit Transfer(user, recipient, 0.05 ether); + wIMX.transfer(recipient, 0.05 ether); + + vm.stopPrank(); + + assertEq(wIMX.balanceOf(user), 0.05 ether, "User shoud have 0.05 wIMX"); + assertEq(wIMX.balanceOf(recipient), 0.05 ether, "Recipient shoud have 0.05 wIMX"); + } + + function test_RevertIf_TransferFromWithNoAllowance() public { + // Create a user and fund it with 1 IMX + address user = address(1234); + vm.deal(user, 1 ether); + // Create a second user + address user2 = address(1235); + + // Deposit 0.1 IMX + vm.startPrank(user); + wIMX.deposit{value: 0.1 ether}(); + + vm.stopPrank(); + vm.startPrank(user2); + + // Second user tries to transfer from + vm.expectRevert(); + wIMX.transferFrom(user, user2, 0.1 ether); + + vm.stopPrank(); + } + + function test_RevertIf_TransferFromWithInsufficientAllowance() public { + // Create a user and fund it with 1 IMX + address user = address(1234); + vm.deal(user, 1 ether); + // Create a second user + address user2 = address(1235); + + // Deposit 0.1 IMX + vm.startPrank(user); + wIMX.deposit{value: 0.1 ether}(); + + // Allow second user + vm.expectEmit(address(wIMX)); + emit Approval(user, user2, 0.05 ether); + wIMX.approve(user2, 0.05 ether); + + vm.stopPrank(); + vm.startPrank(user2); + + // Second user tries to transfer from + vm.expectRevert(); + wIMX.transferFrom(user, user2, 0.1 ether); + + vm.stopPrank(); + } + + function test_RevertIf_TransferFromWithInsufficientBalance() public { + // Create a user and fund it with 1 IMX + address user = address(1234); + vm.deal(user, 1 ether); + // Create a second user + address user2 = address(1235); + + // Deposit 0.1 IMX + vm.startPrank(user); + wIMX.deposit{value: 0.1 ether}(); + + // Allow second user + vm.expectEmit(address(wIMX)); + emit Approval(user, user2, 0.2 ether); + wIMX.approve(user2, 0.2 ether); + + vm.stopPrank(); + vm.startPrank(user2); + + // Second user tries to transfer from + vm.expectRevert(); + wIMX.transferFrom(user, user2, 0.2 ether); + + vm.stopPrank(); + } + + function test_SucceedIf_TransferFromWithinAllowanceAndBalance() public { + // Create a user and fund it with 1 IMX + address user = address(1234); + vm.deal(user, 1 ether); + // Create a second user + address user2 = address(1235); + + // Deposit 0.1 IMX + vm.startPrank(user); + wIMX.deposit{value: 0.1 ether}(); + + // Allow second user + vm.expectEmit(address(wIMX)); + emit Approval(user, user2, 0.1 ether); + wIMX.approve(user2, 0.1 ether); + + vm.stopPrank(); + vm.startPrank(user2); + + // Second user tries to transfer from + vm.expectEmit(address(wIMX)); + emit Transfer(user, user2, 0.1 ether); + wIMX.transferFrom(user, user2, 0.1 ether); + + vm.stopPrank(); + } +} \ No newline at end of file From 83677670a77c60164b7c9e8fade01b7dfe9b8ec4 Mon Sep 17 00:00:00 2001 From: wcgcyx Date: Mon, 9 Oct 2023 16:09:39 +1000 Subject: [PATCH 02/21] Add comments --- src/WIMX.sol | 31 +++++++++++++++++++++++++++---- test/WIMX.t.sol | 2 +- 2 files changed, 28 insertions(+), 5 deletions(-) diff --git a/src/WIMX.sol b/src/WIMX.sol index 9e5b555c..495ecb9a 100644 --- a/src/WIMX.sol +++ b/src/WIMX.sol @@ -1,6 +1,8 @@ -// SPDX-License-Identifier: UNKNOWN +// SPDX-License-Identifier: GPL-3.0 pragma solidity ^0.8.21; +// @notice WIMX is a wrapped IMX contract that allows users to wrap their native IMX. +// @dev This contract is adapted from the official Wrapped ETH contract. contract WIMX { string public name = "Wrapped IMX"; string public symbol = "WIMX"; @@ -14,43 +16,64 @@ contract WIMX { mapping (address => uint) public balanceOf; mapping (address => mapping (address => uint)) public allowance; + // @notice Fallback function on recieving native IMX. receive() external payable { deposit(); } + + // @notice Deposit native IMX in the function call and mint the equal amount of wrapped IMX to msg.sender. function deposit() public payable { balanceOf[msg.sender] += msg.value; emit Deposit(msg.sender, msg.value); } + + // @notice Withdraw given amount of native IMX to msg.sender and burn the equal amount of wrapped IMX. + // @params wad The amount to withdraw. function withdraw(uint wad) public { - require(balanceOf[msg.sender] >= wad); + require(balanceOf[msg.sender] >= wad, "Wrapped IMX: Insufficient balance"); balanceOf[msg.sender] -= wad; payable(msg.sender).transfer(wad); emit Withdrawal(msg.sender, wad); } + // @notice Obtain the current total supply of wrapped IMX. + // @return uint The amount of supplied wrapped IMX. function totalSupply() public view returns (uint) { return address(this).balance; } + // @notice Approve given spender the ability to spend a given amount of msg.sender's tokens. + // @params guy Approved spender. + // @params wad Amount of allowance. + // @return bool Returns true if function call is successful. function approve(address guy, uint wad) public returns (bool) { allowance[msg.sender][guy] = wad; emit Approval(msg.sender, guy, wad); return true; } + // @notice Transfer given amount of tokens from msg.sender to given destination. + // @params dst Destination of this transfer. + // @params wad Amount of this transfer. + // @return bool Returns true if function call is successful. function transfer(address dst, uint wad) public returns (bool) { return transferFrom(msg.sender, dst, wad); } + // @notice Transfer given amount of tokens from given source to given destination. + // @params src Source of this transfer. + // @params dst Destination of this transfer. + // @params wad Amount of this transfer. + // @return bool Returns true if function call is successful. function transferFrom(address src, address dst, uint wad) public returns (bool) { - require(balanceOf[src] >= wad); + require(balanceOf[src] >= wad, "Wrapped IMX: Insufficient balance"); if (src != msg.sender && allowance[src][msg.sender] != type(uint256).max) { - require(allowance[src][msg.sender] >= wad); + require(allowance[src][msg.sender] >= wad, "Wrapped IMX: Insufficient allowance"); allowance[src][msg.sender] -= wad; } diff --git a/test/WIMX.t.sol b/test/WIMX.t.sol index 03cda3e0..eda67e17 100644 --- a/test/WIMX.t.sol +++ b/test/WIMX.t.sol @@ -1,4 +1,4 @@ -// SPDX-License-Identifier: UNKNOWN +// SPDX-License-Identifier: GPL-3.0 pragma solidity ^0.8.21; import {Test} from "forge-std/Test.sol"; From 2e3397bc2b7714c980075ebc5146dff0a34094a3 Mon Sep 17 00:00:00 2001 From: wcgcyx Date: Tue, 10 Oct 2023 11:19:20 +1000 Subject: [PATCH 03/21] Fix typo and natspec comments format --- src/WIMX.sol | 58 +++++++++++++++++++++++++++++++------------------ test/WIMX.t.sol | 52 ++++++++++++++++++++++---------------------- 2 files changed, 63 insertions(+), 47 deletions(-) diff --git a/src/WIMX.sol b/src/WIMX.sol index 495ecb9a..c281035c 100644 --- a/src/WIMX.sol +++ b/src/WIMX.sol @@ -1,8 +1,10 @@ // SPDX-License-Identifier: GPL-3.0 pragma solidity ^0.8.21; -// @notice WIMX is a wrapped IMX contract that allows users to wrap their native IMX. -// @dev This contract is adapted from the official Wrapped ETH contract. +/** + * @notice WIMX is a wrapped IMX contract that allows users to wrap their native IMX. + * @dev This contract is adapted from the official Wrapped ETH contract. + */ contract WIMX { string public name = "Wrapped IMX"; string public symbol = "WIMX"; @@ -16,19 +18,25 @@ contract WIMX { mapping (address => uint) public balanceOf; mapping (address => mapping (address => uint)) public allowance; - // @notice Fallback function on recieving native IMX. + /** + * @notice Fallback function on recieving native IMX. + */ receive() external payable { deposit(); } - // @notice Deposit native IMX in the function call and mint the equal amount of wrapped IMX to msg.sender. + /** + * @notice Deposit native IMX in the function call and mint the equal amount of wrapped IMX to msg.sender. + */ function deposit() public payable { balanceOf[msg.sender] += msg.value; emit Deposit(msg.sender, msg.value); } - // @notice Withdraw given amount of native IMX to msg.sender and burn the equal amount of wrapped IMX. - // @params wad The amount to withdraw. + /** + * @notice Withdraw given amount of native IMX to msg.sender and burn the equal amount of wrapped IMX. + * @param wad The amount to withdraw. + */ function withdraw(uint wad) public { require(balanceOf[msg.sender] >= wad, "Wrapped IMX: Insufficient balance"); balanceOf[msg.sender] -= wad; @@ -37,35 +45,43 @@ contract WIMX { emit Withdrawal(msg.sender, wad); } - // @notice Obtain the current total supply of wrapped IMX. - // @return uint The amount of supplied wrapped IMX. + /** + * @notice Obtain the current total supply of wrapped IMX. + * @return uint The amount of supplied wrapped IMX. + */ function totalSupply() public view returns (uint) { return address(this).balance; } - // @notice Approve given spender the ability to spend a given amount of msg.sender's tokens. - // @params guy Approved spender. - // @params wad Amount of allowance. - // @return bool Returns true if function call is successful. + /** + * @notice Approve given spender the ability to spend a given amount of msg.sender's tokens. + * @param guy Approved spender. + * @param wad Amount of allowance. + * @return bool Returns true if function call is successful. + */ function approve(address guy, uint wad) public returns (bool) { allowance[msg.sender][guy] = wad; emit Approval(msg.sender, guy, wad); return true; } - // @notice Transfer given amount of tokens from msg.sender to given destination. - // @params dst Destination of this transfer. - // @params wad Amount of this transfer. - // @return bool Returns true if function call is successful. + /** + * @notice Transfer given amount of tokens from msg.sender to given destination. + * @param dst Destination of this transfer. + * @param wad Amount of this transfer. + * @return bool Returns true if function call is successful. + */ function transfer(address dst, uint wad) public returns (bool) { return transferFrom(msg.sender, dst, wad); } - // @notice Transfer given amount of tokens from given source to given destination. - // @params src Source of this transfer. - // @params dst Destination of this transfer. - // @params wad Amount of this transfer. - // @return bool Returns true if function call is successful. + /** + * @notice Transfer given amount of tokens from given source to given destination. + * @param src Source of this transfer. + * @param dst Destination of this transfer. + * @param wad Amount of this transfer. + * @return bool Returns true if function call is successful. + */ function transferFrom(address src, address dst, uint wad) public returns (bool) diff --git a/test/WIMX.t.sol b/test/WIMX.t.sol index eda67e17..9a573287 100644 --- a/test/WIMX.t.sol +++ b/test/WIMX.t.sol @@ -31,8 +31,8 @@ contract WIMXTest is Test { vm.deal(user, 1 ether); // Before deposit, user should have 0 wIMX - assertEq(user.balance, 1 ether, "User shoud have 1 IMX"); - assertEq(wIMX.balanceOf(user), 0, "User shoud have 0 wIMX"); + assertEq(user.balance, 1 ether, "User should have 1 IMX"); + assertEq(wIMX.balanceOf(user), 0, "User should have 0 wIMX"); vm.prank(user); // Deposit should revert because user has only 1 IMX @@ -40,8 +40,8 @@ contract WIMXTest is Test { wIMX.deposit{value: 2 ether}(); // After deposit, user should have 0 wIMX - assertEq(user.balance, 1 ether, "User shoud have 1 IMX"); - assertEq(wIMX.balanceOf(user), 0, "User shoud have 0 wIMX"); + assertEq(user.balance, 1 ether, "User should have 1 IMX"); + assertEq(wIMX.balanceOf(user), 0, "User should have 0 wIMX"); } function test_RevertIf_TransferWithInsufficientBalance() public { @@ -50,8 +50,8 @@ contract WIMXTest is Test { vm.deal(user, 1 ether); // Before deposit, user should have 0 wIMX - assertEq(user.balance, 1 ether, "User shoud have 1 IMX"); - assertEq(wIMX.balanceOf(user), 0, "User shoud have 0 wIMX"); + assertEq(user.balance, 1 ether, "User should have 1 IMX"); + assertEq(wIMX.balanceOf(user), 0, "User should have 0 wIMX"); vm.prank(user); // Deposit should revert because user has only 1 IMX @@ -59,8 +59,8 @@ contract WIMXTest is Test { address(wIMX).call{value: 2 ether}(""); // After deposit, user should have 0 wIMX - assertEq(user.balance, 1 ether, "User shoud have 1 IMX"); - assertEq(wIMX.balanceOf(user), 0, "User shoud have 0 wIMX"); + assertEq(user.balance, 1 ether, "User should have 1 IMX"); + assertEq(wIMX.balanceOf(user), 0, "User should have 0 wIMX"); } function test_SucceedIf_DepositWithSufficientBalance() public { @@ -69,8 +69,8 @@ contract WIMXTest is Test { vm.deal(user, 1 ether); // Before deposit, user should have 0 wIMX - assertEq(user.balance, 1 ether, "User shoud have 1 IMX"); - assertEq(wIMX.balanceOf(user), 0, "User shoud have 0 wIMX"); + assertEq(user.balance, 1 ether, "User should have 1 IMX"); + assertEq(wIMX.balanceOf(user), 0, "User should have 0 wIMX"); vm.prank(user); vm.expectEmit(address(wIMX)); @@ -78,8 +78,8 @@ contract WIMXTest is Test { wIMX.deposit{value: 0.1 ether}(); // After deposit, user should have 0.1 wIMX - assertEq(user.balance, 0.9 ether, "User shoud have 0.9 IMX"); - assertEq(wIMX.balanceOf(user), 0.1 ether, "User shoud have 0.1 wIMX"); + assertEq(user.balance, 0.9 ether, "User should have 0.9 IMX"); + assertEq(wIMX.balanceOf(user), 0.1 ether, "User should have 0.1 wIMX"); } function test_SucceedIf_TransferWithSufficientBalance() public { @@ -88,8 +88,8 @@ contract WIMXTest is Test { vm.deal(user, 1 ether); // Before deposit, user should have 0 wIMX - assertEq(user.balance, 1 ether, "User shoud have 1 IMX"); - assertEq(wIMX.balanceOf(user), 0, "User shoud have 0 wIMX"); + assertEq(user.balance, 1 ether, "User should have 1 IMX"); + assertEq(wIMX.balanceOf(user), 0, "User should have 0 wIMX"); vm.prank(user); vm.expectEmit(address(wIMX)); @@ -97,8 +97,8 @@ contract WIMXTest is Test { address(wIMX).call{value: 0.1 ether}(""); // After deposit, user should have 0.1 wIMX - assertEq(user.balance, 0.9 ether, "User shoud have 0.9 wIMX"); - assertEq(wIMX.balanceOf(user), 0.1 ether, "User shoud have 0.1 IMX"); + assertEq(user.balance, 0.9 ether, "User should have 0.9 wIMX"); + assertEq(wIMX.balanceOf(user), 0.1 ether, "User should have 0.1 IMX"); } function test_RevertIf_OverWithdraw() public { @@ -116,8 +116,8 @@ contract WIMXTest is Test { vm.stopPrank(); // User should still have 0.1 wIMX and 0.9 IMX - assertEq(user.balance, 0.9 ether, "User shoud have 0.9 IMX"); - assertEq(wIMX.balanceOf(user), 0.1 ether, "User shoud have 0.1 wIMX"); + assertEq(user.balance, 0.9 ether, "User should have 0.9 IMX"); + assertEq(wIMX.balanceOf(user), 0.1 ether, "User should have 0.1 wIMX"); } function test_SucceedIf_WithdrawWithinLimit() public { @@ -136,8 +136,8 @@ contract WIMXTest is Test { vm.stopPrank(); // User should have 0.05 wIMX and 0.95 IMX - assertEq(user.balance, 0.95 ether, "User shoud have 0.95 IMX"); - assertEq(wIMX.balanceOf(user), 0.05 ether, "User shoud have 0.05 wIMX"); + assertEq(user.balance, 0.95 ether, "User should have 0.95 IMX"); + assertEq(wIMX.balanceOf(user), 0.05 ether, "User should have 0.05 wIMX"); } function test_SupplyUpdated_OnDepositAndWithdraw() public { @@ -198,15 +198,15 @@ contract WIMXTest is Test { // Create a recipient address recipient = address(1235); - assertEq(wIMX.balanceOf(user), 0, "User shoud have 0 wIMX"); - assertEq(wIMX.balanceOf(recipient), 0, "Recipient shoud have 0 wIMX"); + assertEq(wIMX.balanceOf(user), 0, "User should have 0 wIMX"); + assertEq(wIMX.balanceOf(recipient), 0, "Recipient should have 0 wIMX"); // Deposit 0.1 IMX vm.startPrank(user); wIMX.deposit{value: 0.1 ether}(); - assertEq(wIMX.balanceOf(user), 0.1 ether, "User shoud have 0.1 wIMX"); - assertEq(wIMX.balanceOf(recipient), 0, "Recipient shoud have 0 wIMX"); + assertEq(wIMX.balanceOf(user), 0.1 ether, "User should have 0.1 wIMX"); + assertEq(wIMX.balanceOf(recipient), 0, "Recipient should have 0 wIMX"); // Transfer 0.05 wIMX to recipient should revert vm.expectEmit(address(wIMX)); @@ -215,8 +215,8 @@ contract WIMXTest is Test { vm.stopPrank(); - assertEq(wIMX.balanceOf(user), 0.05 ether, "User shoud have 0.05 wIMX"); - assertEq(wIMX.balanceOf(recipient), 0.05 ether, "Recipient shoud have 0.05 wIMX"); + assertEq(wIMX.balanceOf(user), 0.05 ether, "User should have 0.05 wIMX"); + assertEq(wIMX.balanceOf(recipient), 0.05 ether, "Recipient should have 0.05 wIMX"); } function test_RevertIf_TransferFromWithNoAllowance() public { From c39811999caed38be496026792c8c284d5638cbf Mon Sep 17 00:00:00 2001 From: wcgcyx Date: Tue, 10 Oct 2023 12:09:16 +1000 Subject: [PATCH 04/21] Update test --- test/WIMX.t.sol | 149 ++++++++++++++++++++++++++++-------------------- 1 file changed, 88 insertions(+), 61 deletions(-) diff --git a/test/WIMX.t.sol b/test/WIMX.t.sol index 9a573287..c2b4407d 100644 --- a/test/WIMX.t.sol +++ b/test/WIMX.t.sol @@ -28,149 +28,166 @@ contract WIMXTest is Test { function test_RevertIf_DepositWithInsufficientBalance() public { // Create a user and fund it with 1 IMX address user = address(1234); - vm.deal(user, 1 ether); + uint256 imxAmt = 1 ether; + vm.deal(user, imxAmt); // Before deposit, user should have 0 wIMX - assertEq(user.balance, 1 ether, "User should have 1 IMX"); + assertEq(user.balance, imxAmt, "User should have 1 IMX"); assertEq(wIMX.balanceOf(user), 0, "User should have 0 wIMX"); vm.prank(user); // Deposit should revert because user has only 1 IMX vm.expectRevert(); - wIMX.deposit{value: 2 ether}(); + wIMX.deposit{value: imxAmt + 1}(); // After deposit, user should have 0 wIMX - assertEq(user.balance, 1 ether, "User should have 1 IMX"); + assertEq(user.balance, imxAmt, "User should have 1 IMX"); assertEq(wIMX.balanceOf(user), 0, "User should have 0 wIMX"); } function test_RevertIf_TransferWithInsufficientBalance() public { // Create a user and fund it with 1 IMX address user = address(1234); - vm.deal(user, 1 ether); + uint256 imxAmt = 1 ether; + vm.deal(user, imxAmt); // Before deposit, user should have 0 wIMX - assertEq(user.balance, 1 ether, "User should have 1 IMX"); + assertEq(user.balance, imxAmt, "User should have 1 IMX"); assertEq(wIMX.balanceOf(user), 0, "User should have 0 wIMX"); vm.prank(user); // Deposit should revert because user has only 1 IMX vm.expectRevert(); - address(wIMX).call{value: 2 ether}(""); + address(wIMX).call{value: imxAmt + 1}(""); // After deposit, user should have 0 wIMX - assertEq(user.balance, 1 ether, "User should have 1 IMX"); + assertEq(user.balance, imxAmt, "User should have 1 IMX"); assertEq(wIMX.balanceOf(user), 0, "User should have 0 wIMX"); } function test_SucceedIf_DepositWithSufficientBalance() public { // Create a user and fund it with 1 IMX address user = address(1234); - vm.deal(user, 1 ether); + uint256 imxAmt = 1 ether; + vm.deal(user, imxAmt); // Before deposit, user should have 0 wIMX - assertEq(user.balance, 1 ether, "User should have 1 IMX"); + assertEq(user.balance, imxAmt, "User should have 1 IMX"); assertEq(wIMX.balanceOf(user), 0, "User should have 0 wIMX"); + uint256 depositAmt = 0.1 ether; vm.prank(user); vm.expectEmit(address(wIMX)); - emit Deposit(user, 0.1 ether); - wIMX.deposit{value: 0.1 ether}(); + emit Deposit(user, depositAmt); + wIMX.deposit{value: depositAmt}(); // After deposit, user should have 0.1 wIMX - assertEq(user.balance, 0.9 ether, "User should have 0.9 IMX"); - assertEq(wIMX.balanceOf(user), 0.1 ether, "User should have 0.1 wIMX"); + assertEq(user.balance, imxAmt - depositAmt, "User should have 0.9 IMX"); + assertEq(wIMX.balanceOf(user), depositAmt, "User should have 0.1 wIMX"); } function test_SucceedIf_TransferWithSufficientBalance() public { // Create a user and fund it with 1 IMX address user = address(1234); - vm.deal(user, 1 ether); + uint256 imxAmt = 1 ether; + vm.deal(user, imxAmt); // Before deposit, user should have 0 wIMX - assertEq(user.balance, 1 ether, "User should have 1 IMX"); + assertEq(user.balance, imxAmt, "User should have 1 IMX"); assertEq(wIMX.balanceOf(user), 0, "User should have 0 wIMX"); + uint256 depositAmt = 0.1 ether; vm.prank(user); vm.expectEmit(address(wIMX)); - emit Deposit(user, 0.1 ether); - address(wIMX).call{value: 0.1 ether}(""); + emit Deposit(user, depositAmt); + address(wIMX).call{value: depositAmt}(""); // After deposit, user should have 0.1 wIMX - assertEq(user.balance, 0.9 ether, "User should have 0.9 wIMX"); - assertEq(wIMX.balanceOf(user), 0.1 ether, "User should have 0.1 IMX"); + assertEq(user.balance, imxAmt - depositAmt, "User should have 0.9 wIMX"); + assertEq(wIMX.balanceOf(user), depositAmt, "User should have 0.1 IMX"); } function test_RevertIf_OverWithdraw() public { // Create a user and fund it with 1 IMX address user = address(1234); - vm.deal(user, 1 ether); + uint256 imxAmt = 1 ether; + vm.deal(user, imxAmt); // Deposit 0.1 IMX + uint256 depositAmt = 0.1 ether; vm.startPrank(user); - wIMX.deposit{value: 0.1 ether}(); + wIMX.deposit{value: depositAmt}(); // Try withdraw 0.2 wIMX + uint256 withdrawlAmt = 0.2 ether; vm.expectRevert(); - wIMX.withdraw(0.2 ether); + wIMX.withdraw(withdrawlAmt); vm.stopPrank(); // User should still have 0.1 wIMX and 0.9 IMX - assertEq(user.balance, 0.9 ether, "User should have 0.9 IMX"); - assertEq(wIMX.balanceOf(user), 0.1 ether, "User should have 0.1 wIMX"); + assertEq(user.balance, imxAmt - depositAmt, "User should have 0.9 IMX"); + assertEq(wIMX.balanceOf(user), depositAmt, "User should have 0.1 wIMX"); } function test_SucceedIf_WithdrawWithinLimit() public { // Create a user and fund it with 1 IMX address user = address(1234); - vm.deal(user, 1 ether); + uint256 imxAmt = 1 ether; + vm.deal(user, imxAmt); // Deposit 0.1 IMX + uint256 depositAmt = 0.1 ether; vm.startPrank(user); - wIMX.deposit{value: 0.1 ether}(); + wIMX.deposit{value: depositAmt}(); // Try withdraw 0.05 wIMX + uint256 withdrawlAmt = 0.05 ether; vm.expectEmit(address(wIMX)); - emit Withdrawal(user, 0.05 ether); - wIMX.withdraw(0.05 ether); + emit Withdrawal(user, withdrawlAmt); + wIMX.withdraw(withdrawlAmt); vm.stopPrank(); // User should have 0.05 wIMX and 0.95 IMX - assertEq(user.balance, 0.95 ether, "User should have 0.95 IMX"); - assertEq(wIMX.balanceOf(user), 0.05 ether, "User should have 0.05 wIMX"); + assertEq(user.balance, imxAmt - depositAmt + withdrawlAmt, "User should have 0.95 IMX"); + assertEq(wIMX.balanceOf(user), depositAmt - withdrawlAmt, "User should have 0.05 wIMX"); } function test_SupplyUpdated_OnDepositAndWithdraw() public { // Create a user and fund it with 1 IMX address user = address(1234); - vm.deal(user, 1 ether); + uint256 imxAmt = 1 ether; + vm.deal(user, imxAmt); assertEq(wIMX.totalSupply(), 0, "Token supply should be 0 at start"); // Deposit 0.1 IMX + uint256 depositAmt1 = 0.1 ether; vm.startPrank(user); - wIMX.deposit{value: 0.1 ether}(); - assertEq(wIMX.totalSupply(), 0.1 ether, "Token supply should be 0.1 after deposit"); + wIMX.deposit{value: depositAmt1}(); + assertEq(wIMX.totalSupply(), depositAmt1, "Token supply should be 0.1 after deposit"); // Withdraw 0.05 IMX - wIMX.withdraw(0.05 ether); - assertEq(wIMX.totalSupply(), 0.05 ether, "Token supply should be 0.05 after withdraw"); + uint256 withdrawlAmt1 = 0.05 ether; + wIMX.withdraw(withdrawlAmt1); + assertEq(wIMX.totalSupply(), depositAmt1 - withdrawlAmt1, "Token supply should be 0.05 after withdraw"); vm.stopPrank(); // Create another user and fund it with 1 IMX address user2 = address(1235); - vm.deal(user2, 1 ether); + vm.deal(user2, imxAmt); // Deposit 0.5 IMX + uint256 depositAmt2 = 0.5 ether; vm.startPrank(user2); - wIMX.deposit{value: 0.5 ether}(); - assertEq(wIMX.totalSupply(), 0.55 ether, "Token supply should be 0.55 after 2nd deposit"); + wIMX.deposit{value: depositAmt2}(); + assertEq(wIMX.totalSupply(), depositAmt1 - withdrawlAmt1 + depositAmt2, "Token supply should be 0.55 after 2nd deposit"); // Withdraw 0.5 IMX + uint256 withdrawlAmt2 = 0.5 ether; vm.startPrank(user2); - wIMX.withdraw(0.5 ether); - assertEq(wIMX.totalSupply(), 0.05 ether, "Token supply should be 0.05 after 2nd withdraw"); + wIMX.withdraw(withdrawlAmt2); + assertEq(wIMX.totalSupply(), depositAmt1 - withdrawlAmt1 + depositAmt2 - withdrawlAmt2, "Token supply should be 0.05 after 2nd withdraw"); } function test_RevertIf_TransferAmountExceedingBalance() public { @@ -181,12 +198,13 @@ contract WIMXTest is Test { address recipient = address(1235); // Deposit 0.1 IMX + uint256 depositAmt = 0.1 ether; vm.startPrank(user); - wIMX.deposit{value: 0.1 ether}(); + wIMX.deposit{value: depositAmt}(); // Transfer 0.2 wIMX to recipient should revert vm.expectRevert(); - wIMX.transfer(recipient, 0.2 ether); + wIMX.transfer(recipient, depositAmt + 1); vm.stopPrank(); } @@ -202,21 +220,23 @@ contract WIMXTest is Test { assertEq(wIMX.balanceOf(recipient), 0, "Recipient should have 0 wIMX"); // Deposit 0.1 IMX + uint256 depositAmt = 0.1 ether; vm.startPrank(user); - wIMX.deposit{value: 0.1 ether}(); + wIMX.deposit{value: depositAmt}(); - assertEq(wIMX.balanceOf(user), 0.1 ether, "User should have 0.1 wIMX"); + assertEq(wIMX.balanceOf(user), depositAmt, "User should have 0.1 wIMX"); assertEq(wIMX.balanceOf(recipient), 0, "Recipient should have 0 wIMX"); // Transfer 0.05 wIMX to recipient should revert + uint256 transferredAmt = 0.05 ether; vm.expectEmit(address(wIMX)); - emit Transfer(user, recipient, 0.05 ether); - wIMX.transfer(recipient, 0.05 ether); + emit Transfer(user, recipient, transferredAmt); + wIMX.transfer(recipient, transferredAmt); vm.stopPrank(); - assertEq(wIMX.balanceOf(user), 0.05 ether, "User should have 0.05 wIMX"); - assertEq(wIMX.balanceOf(recipient), 0.05 ether, "Recipient should have 0.05 wIMX"); + assertEq(wIMX.balanceOf(user), depositAmt - transferredAmt, "User should have 0.05 wIMX"); + assertEq(wIMX.balanceOf(recipient), transferredAmt, "Recipient should have 0.05 wIMX"); } function test_RevertIf_TransferFromWithNoAllowance() public { @@ -252,16 +272,17 @@ contract WIMXTest is Test { wIMX.deposit{value: 0.1 ether}(); // Allow second user + uint256 approvedAmt = 0.05 ether; vm.expectEmit(address(wIMX)); - emit Approval(user, user2, 0.05 ether); - wIMX.approve(user2, 0.05 ether); + emit Approval(user, user2, approvedAmt); + wIMX.approve(user2, approvedAmt); vm.stopPrank(); vm.startPrank(user2); // Second user tries to transfer from vm.expectRevert(); - wIMX.transferFrom(user, user2, 0.1 ether); + wIMX.transferFrom(user, user2, approvedAmt + 1); vm.stopPrank(); } @@ -274,20 +295,21 @@ contract WIMXTest is Test { address user2 = address(1235); // Deposit 0.1 IMX + uint256 depositAmt = 0.1 ether; vm.startPrank(user); - wIMX.deposit{value: 0.1 ether}(); + wIMX.deposit{value: depositAmt}(); // Allow second user vm.expectEmit(address(wIMX)); - emit Approval(user, user2, 0.2 ether); - wIMX.approve(user2, 0.2 ether); + emit Approval(user, user2, depositAmt + 1); + wIMX.approve(user2, depositAmt + 1); vm.stopPrank(); vm.startPrank(user2); // Second user tries to transfer from vm.expectRevert(); - wIMX.transferFrom(user, user2, 0.2 ether); + wIMX.transferFrom(user, user2, depositAmt + 1); vm.stopPrank(); } @@ -300,22 +322,27 @@ contract WIMXTest is Test { address user2 = address(1235); // Deposit 0.1 IMX + uint256 depositAmt = 0.1 ether; vm.startPrank(user); - wIMX.deposit{value: 0.1 ether}(); + wIMX.deposit{value: depositAmt}(); // Allow second user vm.expectEmit(address(wIMX)); - emit Approval(user, user2, 0.1 ether); - wIMX.approve(user2, 0.1 ether); + emit Approval(user, user2, depositAmt); + wIMX.approve(user2, depositAmt); vm.stopPrank(); vm.startPrank(user2); // Second user tries to transfer from + uint256 transferredAmt = 0.1 ether; vm.expectEmit(address(wIMX)); - emit Transfer(user, user2, 0.1 ether); - wIMX.transferFrom(user, user2, 0.1 ether); + emit Transfer(user, user2, transferredAmt); + wIMX.transferFrom(user, user2, transferredAmt); vm.stopPrank(); + + assertEq(wIMX.balanceOf(user), depositAmt - transferredAmt, "User should have 0 wIMX"); + assertEq(wIMX.balanceOf(user2), transferredAmt, "Recipient should have 0.05 wIMX"); } } \ No newline at end of file From 65d38c16f4bfb85a5c782a37d7a028630183d091 Mon Sep 17 00:00:00 2001 From: wcgcyx Date: Tue, 10 Oct 2023 12:10:04 +1000 Subject: [PATCH 05/21] forge fmt --- src/WIMX.sol | 37 +++++++++++++++++-------------------- test/WIMX.t.sol | 36 ++++++++++++++++++++++-------------- 2 files changed, 39 insertions(+), 34 deletions(-) diff --git a/src/WIMX.sol b/src/WIMX.sol index c281035c..20f2cb62 100644 --- a/src/WIMX.sol +++ b/src/WIMX.sol @@ -6,17 +6,17 @@ pragma solidity ^0.8.21; * @dev This contract is adapted from the official Wrapped ETH contract. */ contract WIMX { - string public name = "Wrapped IMX"; - string public symbol = "WIMX"; - uint8 public decimals = 18; + string public name = "Wrapped IMX"; + string public symbol = "WIMX"; + uint8 public decimals = 18; - event Approval(address indexed src, address indexed guy, uint wad); - event Transfer(address indexed src, address indexed dst, uint wad); - event Deposit(address indexed dst, uint wad); - event Withdrawal(address indexed src, uint wad); + event Approval(address indexed src, address indexed guy, uint256 wad); + event Transfer(address indexed src, address indexed dst, uint256 wad); + event Deposit(address indexed dst, uint256 wad); + event Withdrawal(address indexed src, uint256 wad); - mapping (address => uint) public balanceOf; - mapping (address => mapping (address => uint)) public allowance; + mapping(address => uint256) public balanceOf; + mapping(address => mapping(address => uint256)) public allowance; /** * @notice Fallback function on recieving native IMX. @@ -24,7 +24,7 @@ contract WIMX { receive() external payable { deposit(); } - + /** * @notice Deposit native IMX in the function call and mint the equal amount of wrapped IMX to msg.sender. */ @@ -37,10 +37,10 @@ contract WIMX { * @notice Withdraw given amount of native IMX to msg.sender and burn the equal amount of wrapped IMX. * @param wad The amount to withdraw. */ - function withdraw(uint wad) public { + function withdraw(uint256 wad) public { require(balanceOf[msg.sender] >= wad, "Wrapped IMX: Insufficient balance"); balanceOf[msg.sender] -= wad; - + payable(msg.sender).transfer(wad); emit Withdrawal(msg.sender, wad); } @@ -49,7 +49,7 @@ contract WIMX { * @notice Obtain the current total supply of wrapped IMX. * @return uint The amount of supplied wrapped IMX. */ - function totalSupply() public view returns (uint) { + function totalSupply() public view returns (uint256) { return address(this).balance; } @@ -59,7 +59,7 @@ contract WIMX { * @param wad Amount of allowance. * @return bool Returns true if function call is successful. */ - function approve(address guy, uint wad) public returns (bool) { + function approve(address guy, uint256 wad) public returns (bool) { allowance[msg.sender][guy] = wad; emit Approval(msg.sender, guy, wad); return true; @@ -71,7 +71,7 @@ contract WIMX { * @param wad Amount of this transfer. * @return bool Returns true if function call is successful. */ - function transfer(address dst, uint wad) public returns (bool) { + function transfer(address dst, uint256 wad) public returns (bool) { return transferFrom(msg.sender, dst, wad); } @@ -82,10 +82,7 @@ contract WIMX { * @param wad Amount of this transfer. * @return bool Returns true if function call is successful. */ - function transferFrom(address src, address dst, uint wad) - public - returns (bool) - { + function transferFrom(address src, address dst, uint256 wad) public returns (bool) { require(balanceOf[src] >= wad, "Wrapped IMX: Insufficient balance"); if (src != msg.sender && allowance[src][msg.sender] != type(uint256).max) { @@ -100,4 +97,4 @@ contract WIMX { return true; } -} \ No newline at end of file +} diff --git a/test/WIMX.t.sol b/test/WIMX.t.sol index c2b4407d..e418c97b 100644 --- a/test/WIMX.t.sol +++ b/test/WIMX.t.sol @@ -8,10 +8,10 @@ contract WIMXTest is Test { string constant DEFAULT_WIMX_NAME = "Wrapped IMX"; string constant DEFAULT_WIMX_SYMBOL = "WIMX"; - event Approval(address indexed src, address indexed guy, uint wad); - event Transfer(address indexed src, address indexed dst, uint wad); - event Deposit(address indexed dst, uint wad); - event Withdrawal(address indexed src, uint wad); + event Approval(address indexed src, address indexed guy, uint256 wad); + event Transfer(address indexed src, address indexed dst, uint256 wad); + event Deposit(address indexed dst, uint256 wad); + event Withdrawal(address indexed src, uint256 wad); WIMX public wIMX; @@ -30,7 +30,7 @@ contract WIMXTest is Test { address user = address(1234); uint256 imxAmt = 1 ether; vm.deal(user, imxAmt); - + // Before deposit, user should have 0 wIMX assertEq(user.balance, imxAmt, "User should have 1 IMX"); assertEq(wIMX.balanceOf(user), 0, "User should have 0 wIMX"); @@ -50,7 +50,7 @@ contract WIMXTest is Test { address user = address(1234); uint256 imxAmt = 1 ether; vm.deal(user, imxAmt); - + // Before deposit, user should have 0 wIMX assertEq(user.balance, imxAmt, "User should have 1 IMX"); assertEq(wIMX.balanceOf(user), 0, "User should have 0 wIMX"); @@ -59,7 +59,7 @@ contract WIMXTest is Test { // Deposit should revert because user has only 1 IMX vm.expectRevert(); address(wIMX).call{value: imxAmt + 1}(""); - + // After deposit, user should have 0 wIMX assertEq(user.balance, imxAmt, "User should have 1 IMX"); assertEq(wIMX.balanceOf(user), 0, "User should have 0 wIMX"); @@ -70,7 +70,7 @@ contract WIMXTest is Test { address user = address(1234); uint256 imxAmt = 1 ether; vm.deal(user, imxAmt); - + // Before deposit, user should have 0 wIMX assertEq(user.balance, imxAmt, "User should have 1 IMX"); assertEq(wIMX.balanceOf(user), 0, "User should have 0 wIMX"); @@ -91,7 +91,7 @@ contract WIMXTest is Test { address user = address(1234); uint256 imxAmt = 1 ether; vm.deal(user, imxAmt); - + // Before deposit, user should have 0 wIMX assertEq(user.balance, imxAmt, "User should have 1 IMX"); assertEq(wIMX.balanceOf(user), 0, "User should have 0 wIMX"); @@ -101,7 +101,7 @@ contract WIMXTest is Test { vm.expectEmit(address(wIMX)); emit Deposit(user, depositAmt); address(wIMX).call{value: depositAmt}(""); - + // After deposit, user should have 0.1 wIMX assertEq(user.balance, imxAmt - depositAmt, "User should have 0.9 wIMX"); assertEq(wIMX.balanceOf(user), depositAmt, "User should have 0.1 IMX"); @@ -181,13 +181,21 @@ contract WIMXTest is Test { uint256 depositAmt2 = 0.5 ether; vm.startPrank(user2); wIMX.deposit{value: depositAmt2}(); - assertEq(wIMX.totalSupply(), depositAmt1 - withdrawlAmt1 + depositAmt2, "Token supply should be 0.55 after 2nd deposit"); + assertEq( + wIMX.totalSupply(), + depositAmt1 - withdrawlAmt1 + depositAmt2, + "Token supply should be 0.55 after 2nd deposit" + ); // Withdraw 0.5 IMX uint256 withdrawlAmt2 = 0.5 ether; vm.startPrank(user2); wIMX.withdraw(withdrawlAmt2); - assertEq(wIMX.totalSupply(), depositAmt1 - withdrawlAmt1 + depositAmt2 - withdrawlAmt2, "Token supply should be 0.05 after 2nd withdraw"); + assertEq( + wIMX.totalSupply(), + depositAmt1 - withdrawlAmt1 + depositAmt2 - withdrawlAmt2, + "Token supply should be 0.05 after 2nd withdraw" + ); } function test_RevertIf_TransferAmountExceedingBalance() public { @@ -232,7 +240,7 @@ contract WIMXTest is Test { vm.expectEmit(address(wIMX)); emit Transfer(user, recipient, transferredAmt); wIMX.transfer(recipient, transferredAmt); - + vm.stopPrank(); assertEq(wIMX.balanceOf(user), depositAmt - transferredAmt, "User should have 0.05 wIMX"); @@ -345,4 +353,4 @@ contract WIMXTest is Test { assertEq(wIMX.balanceOf(user), depositAmt - transferredAmt, "User should have 0 wIMX"); assertEq(wIMX.balanceOf(user2), transferredAmt, "Recipient should have 0.05 wIMX"); } -} \ No newline at end of file +} From 2a967ac6a4443393898e6e498a0550f7632643eb Mon Sep 17 00:00:00 2001 From: wcgcyx Date: Thu, 12 Oct 2023 14:41:31 +1000 Subject: [PATCH 06/21] Remove vscode --- .vscode/settings.json | 3 --- 1 file changed, 3 deletions(-) delete mode 100644 .vscode/settings.json diff --git a/.vscode/settings.json b/.vscode/settings.json deleted file mode 100644 index e4ec924d..00000000 --- a/.vscode/settings.json +++ /dev/null @@ -1,3 +0,0 @@ -{ - "solidity.compileUsingRemoteVersion": "v0.8.21+commit.d9974bed" -} \ No newline at end of file From f7cf5206572b644645c782ac32fade897eeed434 Mon Sep 17 00:00:00 2001 From: wcgcyx Date: Thu, 12 Oct 2023 14:42:28 +1000 Subject: [PATCH 07/21] Update .gitignore --- .gitignore | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.gitignore b/.gitignore index 85198aaa..97f29796 100644 --- a/.gitignore +++ b/.gitignore @@ -12,3 +12,6 @@ docs/ # Dotenv file .env + +# IDE settings +.vscode/ \ No newline at end of file From f98e7199c6c32ff98dbf68c348c859a2d0880a26 Mon Sep 17 00:00:00 2001 From: wcgcyx Date: Thu, 12 Oct 2023 14:51:16 +1000 Subject: [PATCH 08/21] Remove test warning --- src/{ => child}/WIMX.sol | 0 test/{ => unit/child}/WIMX.t.sol | 8 +++++--- 2 files changed, 5 insertions(+), 3 deletions(-) rename src/{ => child}/WIMX.sol (100%) rename test/{ => unit/child}/WIMX.t.sol (98%) diff --git a/src/WIMX.sol b/src/child/WIMX.sol similarity index 100% rename from src/WIMX.sol rename to src/child/WIMX.sol diff --git a/test/WIMX.t.sol b/test/unit/child/WIMX.t.sol similarity index 98% rename from test/WIMX.t.sol rename to test/unit/child/WIMX.t.sol index e418c97b..14a6a804 100644 --- a/test/WIMX.t.sol +++ b/test/unit/child/WIMX.t.sol @@ -2,7 +2,7 @@ pragma solidity ^0.8.21; import {Test} from "forge-std/Test.sol"; -import {WIMX} from "../src/WIMX.sol"; +import {WIMX} from "../../../src/child/WIMX.sol"; contract WIMXTest is Test { string constant DEFAULT_WIMX_NAME = "Wrapped IMX"; @@ -58,7 +58,8 @@ contract WIMXTest is Test { vm.prank(user); // Deposit should revert because user has only 1 IMX vm.expectRevert(); - address(wIMX).call{value: imxAmt + 1}(""); + (bool success, ) = address(wIMX).call{value: imxAmt + 1}(""); + require(success); // After deposit, user should have 0 wIMX assertEq(user.balance, imxAmt, "User should have 1 IMX"); @@ -100,7 +101,8 @@ contract WIMXTest is Test { vm.prank(user); vm.expectEmit(address(wIMX)); emit Deposit(user, depositAmt); - address(wIMX).call{value: depositAmt}(""); + (bool success, ) = address(wIMX).call{value: depositAmt}(""); + require(success); // After deposit, user should have 0.1 wIMX assertEq(user.balance, imxAmt - depositAmt, "User should have 0.9 wIMX"); From e54adafc22ae6629ed24c6b258e904621e99d84a Mon Sep 17 00:00:00 2001 From: wcgcyx Date: Fri, 13 Oct 2023 11:01:40 +1000 Subject: [PATCH 09/21] Use interface --- src/child/WIMX.sol | 12 +++++------- src/interfaces/child/IWIMX.sol | 30 ++++++++++++++++++++++++++++++ test/unit/child/WIMX.t.sol | 4 ++-- 3 files changed, 37 insertions(+), 9 deletions(-) create mode 100644 src/interfaces/child/IWIMX.sol diff --git a/src/child/WIMX.sol b/src/child/WIMX.sol index 20f2cb62..1e778eaa 100644 --- a/src/child/WIMX.sol +++ b/src/child/WIMX.sol @@ -1,20 +1,18 @@ // SPDX-License-Identifier: GPL-3.0 pragma solidity ^0.8.21; +import {IWIMX} from "../interfaces/child/IWIMX.sol"; +import {Address} from "@openzeppelin/contracts/utils/Address.sol"; + /** * @notice WIMX is a wrapped IMX contract that allows users to wrap their native IMX. * @dev This contract is adapted from the official Wrapped ETH contract. */ -contract WIMX { +contract WIMX is IWIMX { string public name = "Wrapped IMX"; string public symbol = "WIMX"; uint8 public decimals = 18; - event Approval(address indexed src, address indexed guy, uint256 wad); - event Transfer(address indexed src, address indexed dst, uint256 wad); - event Deposit(address indexed dst, uint256 wad); - event Withdrawal(address indexed src, uint256 wad); - mapping(address => uint256) public balanceOf; mapping(address => mapping(address => uint256)) public allowance; @@ -41,7 +39,7 @@ contract WIMX { require(balanceOf[msg.sender] >= wad, "Wrapped IMX: Insufficient balance"); balanceOf[msg.sender] -= wad; - payable(msg.sender).transfer(wad); + Address.sendValue(payable(msg.sender), wad); emit Withdrawal(msg.sender, wad); } diff --git a/src/interfaces/child/IWIMX.sol b/src/interfaces/child/IWIMX.sol new file mode 100644 index 00000000..c6c427c6 --- /dev/null +++ b/src/interfaces/child/IWIMX.sol @@ -0,0 +1,30 @@ +// SPDX-License-Identifier: Apache 2.0 +pragma solidity ^0.8.21; + +import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; + +/** + * @dev Interface of Wrapped IMX. + */ +interface IWIMX is IERC20 { + /** + * @dev Emitted when `value` native IMX are deposited from `account`. + */ + event Deposit(address indexed account, uint256 value); + + /** + * @dev Emitted when `value` wIMX tokens are withdrawn to `account`. + */ + event Withdrawal(address indexed account, uint256 value); + + /** + * @notice Deposit native IMX in the function call and mint the equal amount of wrapped IMX to msg.sender. + */ + function deposit() external payable; + + /** + * @notice Withdraw given amount of native IMX to msg.sender and burn the equal amount of wrapped IMX. + * @param value The amount to withdraw. + */ + function withdraw(uint256 value) external; +} diff --git a/test/unit/child/WIMX.t.sol b/test/unit/child/WIMX.t.sol index 14a6a804..570740f7 100644 --- a/test/unit/child/WIMX.t.sol +++ b/test/unit/child/WIMX.t.sol @@ -58,7 +58,7 @@ contract WIMXTest is Test { vm.prank(user); // Deposit should revert because user has only 1 IMX vm.expectRevert(); - (bool success, ) = address(wIMX).call{value: imxAmt + 1}(""); + (bool success,) = address(wIMX).call{value: imxAmt + 1}(""); require(success); // After deposit, user should have 0 wIMX @@ -101,7 +101,7 @@ contract WIMXTest is Test { vm.prank(user); vm.expectEmit(address(wIMX)); emit Deposit(user, depositAmt); - (bool success, ) = address(wIMX).call{value: depositAmt}(""); + (bool success,) = address(wIMX).call{value: depositAmt}(""); require(success); // After deposit, user should have 0.1 wIMX From 743f3a9a8942e9a02a6987e2a9054c0a235a12de Mon Sep 17 00:00:00 2001 From: wcgcyx Date: Fri, 13 Oct 2023 11:54:12 +1000 Subject: [PATCH 10/21] Add CI and slither --- .github/workflows/static-analysis.yml | 10 ++++++++++ .github/workflows/test.yml | 4 ++-- 2 files changed, 12 insertions(+), 2 deletions(-) create mode 100644 .github/workflows/static-analysis.yml diff --git a/.github/workflows/static-analysis.yml b/.github/workflows/static-analysis.yml new file mode 100644 index 00000000..556162f3 --- /dev/null +++ b/.github/workflows/static-analysis.yml @@ -0,0 +1,10 @@ +name: Slither Analysis + +on: [push] + +jobs: + analyze: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v3 + - uses: crytic/slither-action@v0.3.0 \ No newline at end of file diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 09880b1d..921bdba7 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -1,6 +1,6 @@ -name: test +name: Build and Test -on: workflow_dispatch +on: [push] env: FOUNDRY_PROFILE: ci From beca9eafcfab6b20c4641989fd7ca684c8983840 Mon Sep 17 00:00:00 2001 From: wcgcyx Date: Mon, 16 Oct 2023 11:17:04 +1000 Subject: [PATCH 11/21] Exclude lib and test folder --- .github/workflows/static-analysis.yml | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/.github/workflows/static-analysis.yml b/.github/workflows/static-analysis.yml index 556162f3..8198ebf5 100644 --- a/.github/workflows/static-analysis.yml +++ b/.github/workflows/static-analysis.yml @@ -7,4 +7,7 @@ jobs: runs-on: ubuntu-latest steps: - uses: actions/checkout@v3 - - uses: crytic/slither-action@v0.3.0 \ No newline at end of file + - uses: crytic/slither-action@v0.3.0 + with: + fail-on: high + filter-paths: lib,test \ No newline at end of file From e6fc7361d1b38210d02bacf73f054f063de0ec1a Mon Sep 17 00:00:00 2001 From: wcgcyx Date: Mon, 16 Oct 2023 12:35:30 +1000 Subject: [PATCH 12/21] Update static-analysis.yml --- .github/workflows/static-analysis.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/static-analysis.yml b/.github/workflows/static-analysis.yml index 8198ebf5..9be10507 100644 --- a/.github/workflows/static-analysis.yml +++ b/.github/workflows/static-analysis.yml @@ -10,4 +10,4 @@ jobs: - uses: crytic/slither-action@v0.3.0 with: fail-on: high - filter-paths: lib,test \ No newline at end of file + slither-args: --filter-paths "./lib|./test" \ No newline at end of file From 015e32b0aca0e22e8a18c114cc6bf19ea9925ca2 Mon Sep 17 00:00:00 2001 From: wcgcyx Date: Mon, 16 Oct 2023 14:52:19 +1000 Subject: [PATCH 13/21] Update DeployChildContracts.s.sol --- script/DeployChildContracts.s.sol | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/script/DeployChildContracts.s.sol b/script/DeployChildContracts.s.sol index 6ec7ab47..9bf20df8 100644 --- a/script/DeployChildContracts.s.sol +++ b/script/DeployChildContracts.s.sol @@ -6,6 +6,7 @@ import {Script, console2} from "forge-std/Script.sol"; import {ChildERC20Bridge} from "../src/child/ChildERC20Bridge.sol"; import {ChildAxelarBridgeAdaptor} from "../src/child/ChildAxelarBridgeAdaptor.sol"; import {ChildERC20} from "../src/child/ChildERC20.sol"; +import {WIMX} from "../src/child/WIMX.sol"; // TODO update private key usage to be more secure: https://book.getfoundry.sh/reference/forge/forge-script#wallet-options---raw @@ -29,11 +30,14 @@ contract DeployChildContracts is Script { address(childBridge) // child bridge ); + WIMX wrappedIMX = new WIMX(); + vm.stopBroadcast(); console2.log("====ADDRESSES===="); console2.log("Child ERC20 Bridge: %s", address(childBridge)); console2.log("Child Axelar Bridge Adaptor: %s", address(childBridgeAdaptor)); console2.log("childTokenTemplate: %s", address(childTokenTemplate)); + console2.log("Wrapped IMX: %s", address(wrappedIMX)); } } From cf8ca982a2f69f6fa6ed492c07035b9e33a3fc48 Mon Sep 17 00:00:00 2001 From: Craig M Date: Mon, 16 Oct 2023 19:24:21 +1300 Subject: [PATCH 14/21] WIP --- .env.sample | 3 +- README.md | 2 + script/InitializeRootContracts.s.sol | 5 +- src/root/RootERC20Bridge.sol | 60 ++++++++++++++++++--- test/integration/root/RootERC20Bridge.t.sol | 5 +- test/unit/root/RootERC20Bridge.t.sol | 50 ++++++++++++++--- test/utils.t.sol | 22 ++++++-- 7 files changed, 123 insertions(+), 24 deletions(-) diff --git a/.env.sample b/.env.sample index 957e611b..f1854b16 100644 --- a/.env.sample +++ b/.env.sample @@ -10,4 +10,5 @@ ROOT_GAS_SERVICE_ADDRESS= CHILD_GAS_SERVICE_ADDRESS= ROOT_CHAIN_NAME= CHILD_CHAIN_NAME= -ROOT_IMX_ADDRESS= \ No newline at end of file +ROOT_IMX_ADDRESS= +CHILD_ETH_ADDRESS= \ No newline at end of file diff --git a/README.md b/README.md index 46a57755..4814d790 100644 --- a/README.md +++ b/README.md @@ -82,6 +82,8 @@ ROOT_GAS_SERVICE_ADDRESS= CHILD_GAS_SERVICE_ADDRESS= ROOT_CHAIN_NAME="ROOT" CHILD_CHAIN_NAME="CHILD" +ROOT_IMX_ADDRESS= +CHILD_ETH_ADDRESS= ``` where `{ROOT,CHILD}_{GATEWAY,GAS_SERVICE}_ADDRESS` refers to the gateway and gas service addresses used by Axelar. diff --git a/script/InitializeRootContracts.s.sol b/script/InitializeRootContracts.s.sol index b28902e9..66a53115 100644 --- a/script/InitializeRootContracts.s.sol +++ b/script/InitializeRootContracts.s.sol @@ -22,7 +22,7 @@ contract InitializeRootContracts is Script { string memory rootRpcUrl = vm.envString("ROOT_RPC_URL"); uint256 rootPrivateKey = vm.envUint("ROOT_PRIVATE_KEY"); address rootIMXToken = vm.envAddress("ROOT_IMX_ADDRESS"); - + address childETHToken = vm.envAddress("CHILD_ETH_ADDRESS"); /** * INITIALIZE ROOT CHAIN CONTRACTS @@ -35,7 +35,8 @@ contract InitializeRootContracts is Script { childERC20Bridge, childBridgeAdaptor, rootChainChildTokenTemplate, - rootIMXToken + rootIMXToken, + childETHToken ); rootBridgeAdaptor.setChildBridgeAdaptor(); diff --git a/src/root/RootERC20Bridge.sol b/src/root/RootERC20Bridge.sol index 6b08e6c6..dd0dd42f 100644 --- a/src/root/RootERC20Bridge.sol +++ b/src/root/RootERC20Bridge.sol @@ -11,6 +11,7 @@ import {IAxelarGateway} from "@axelar-cgp-solidity/contracts/interfaces/IAxelarG import {IRootERC20Bridge, IERC20Metadata} from "../interfaces/root/IRootERC20Bridge.sol"; import {IRootERC20BridgeEvents, IRootERC20BridgeErrors} from "../interfaces/root/IRootERC20Bridge.sol"; import {IRootERC20BridgeAdaptor} from "../interfaces/root/IRootERC20BridgeAdaptor.sol"; +import {console2} from "forge-std/Test.sol"; /** * @notice RootERC20Bridge is a bridge that allows ERC20 tokens to be transferred from the root chain to the child chain. @@ -42,8 +43,10 @@ contract RootERC20Bridge is /// @dev The address of the token template that will be cloned to create tokens on the child chain. address public childTokenTemplate; mapping(address => address) public rootTokenToChildToken; - + /// @dev The address of the IMX ERC20 token on L1. address public rootIMXToken; + /// @dev The address of the ETH ERC20 token on L2. + address public childETHToken; /** * @notice Initilization function for RootERC20Bridge. @@ -52,6 +55,7 @@ contract RootERC20Bridge is * @param newChildBridgeAdaptor Address of child bridge adaptor to communicate with. * @param newChildTokenTemplate Address of child token template to clone. * @param newRootIMXToken Address of ECR20 IMX on the root chain. + * @param newChildETHToken Address of ECR20 ETH on the child chain. * @dev Can only be called once. */ function initialize( @@ -59,7 +63,8 @@ contract RootERC20Bridge is address newChildERC20Bridge, address newChildBridgeAdaptor, address newChildTokenTemplate, - address newRootIMXToken) + address newRootIMXToken, + address newChildETHToken) public initializer { @@ -67,13 +72,15 @@ contract RootERC20Bridge is || newChildERC20Bridge == address(0) || newChildTokenTemplate == address(0) || newChildBridgeAdaptor == address(0) - || newRootIMXToken == address(0)) + || newRootIMXToken == address(0) + || newChildETHToken == address(0)) { revert ZeroAddress(); } childERC20Bridge = newChildERC20Bridge; childTokenTemplate = newChildTokenTemplate; rootIMXToken = newRootIMXToken; + childETHToken = newChildETHToken; rootBridgeAdaptor = IRootERC20BridgeAdaptor(newRootBridgeAdaptor); childBridgeAdaptor = Strings.toHexString(newChildBridgeAdaptor); } @@ -89,6 +96,30 @@ contract RootERC20Bridge is return _mapToken(rootToken); } + function depositETH() external payable { //override removed? + _depositETH(msg.sender, msg.value); + } + + function depositToETH(address receiver) external payable { //override removed? + _depositETH(receiver, msg.value); + } + + function _depositETH(address receiver, uint256 amount) private { + console2.log('start balance'); + console2.logUint(address(this).balance); + _deposit(IERC20Metadata(NATIVE_TOKEN), receiver, amount); + //@TODO can we do an invariant check here? + console2.log('end balance'); + + console2.logUint(address(this).balance); + + + // invariant check to ensure that the root native balance has increased by the amount deposited + // if (address(msg.sender).balance != expectedBalance) { + // revert BalanceInvariantCheckFailed(address(this).balance, expectedBalance); + // } + } + /** * @inheritdoc IRootERC20Bridge */ @@ -141,6 +172,8 @@ contract RootERC20Bridge is } function _deposit(IERC20Metadata rootToken, address receiver, uint256 amount) private { + console2.log("_deposit ---------------"); + if (receiver == address(0) || address(rootToken) == address(0)) { revert ZeroAddress(); } @@ -152,27 +185,40 @@ contract RootERC20Bridge is // TODO We can call _mapToken here, but ordering in the GMP is not guaranteed. // Therefore, we need to decide how to handle this and it may be a UI decision to wait until map token message is executed on child chain. // Discuss this, and add this decision to the design doc. - // TODO NATIVE TOKEN BRIDGING NOT YET SUPPORTED if (address(rootToken) != NATIVE_TOKEN) { + console2.log("!NATIVE_TOKEN"); + + if (address(rootToken) != rootIMXToken) { childToken = rootTokenToChildToken[address(rootToken)]; if (childToken == address(0)) { revert NotMapped(); } - } + } // ERC20 must be transferred explicitly rootToken.safeTransferFrom(msg.sender, address(this), amount); } + + console2.logBytes32(DEPOSIT_SIG); + console2.logAddress(address(rootToken)); + console2.logAddress(msg.sender); + console2.logAddress(address(receiver)); + console2.logUint(amount); + // Deposit sig, root token address, depositor, receiver, amount bytes memory payload = abi.encode(DEPOSIT_SIG, rootToken, msg.sender, receiver, amount); // TODO investigate using delegatecall to keep the axelar message sender as the bridge contract, since adaptor can change. + + console2.logBytes(payload); + + //@TODO need to minus the bridge amount from the gas otherwise we're sending the whole amount to axelar rootBridgeAdaptor.sendMessage{value: msg.value}(payload, msg.sender); if (address(rootToken) == NATIVE_TOKEN) { - // not used yet - emit NativeDeposit(address(rootToken), childToken, msg.sender, receiver, amount); + console2.log("emit NativeDeposit"); + emit NativeDeposit(address(rootToken), childETHToken, msg.sender, receiver, amount); } else if (address(rootToken) == rootIMXToken) { emit IMXDeposit(address(rootToken), msg.sender, receiver, amount); } else { diff --git a/test/integration/root/RootERC20Bridge.t.sol b/test/integration/root/RootERC20Bridge.t.sol index f4269f14..44eda30b 100644 --- a/test/integration/root/RootERC20Bridge.t.sol +++ b/test/integration/root/RootERC20Bridge.t.sol @@ -16,7 +16,8 @@ contract RootERC20BridgeIntegrationTest is Test, IRootERC20BridgeEvents, IRootAx address constant CHILD_BRIDGE_ADAPTOR = address(4); string constant CHILD_CHAIN_NAME = "test"; bytes32 public constant MAP_TOKEN_SIG = keccak256("MAP_TOKEN"); - address constant IMX_TOKEN_ADDRESS = address(9); + address constant IMX_TOKEN_ADDRESS = address(99); + address constant ETH_TOKEN_ADDRESS = address(88); ERC20PresetMinterPauser public token; @@ -27,7 +28,7 @@ contract RootERC20BridgeIntegrationTest is Test, IRootERC20BridgeEvents, IRootAx function setUp() public { (token, rootBridge, axelarAdaptor, mockAxelarGateway, axelarGasService) = - integrationSetup(CHILD_BRIDGE, CHILD_BRIDGE_ADAPTOR, CHILD_CHAIN_NAME, IMX_TOKEN_ADDRESS); + integrationSetup(CHILD_BRIDGE, CHILD_BRIDGE_ADAPTOR, CHILD_CHAIN_NAME, IMX_TOKEN_ADDRESS, ETH_TOKEN_ADDRESS); } /** diff --git a/test/unit/root/RootERC20Bridge.t.sol b/test/unit/root/RootERC20Bridge.t.sol index 60d09a98..9e8eea8f 100644 --- a/test/unit/root/RootERC20Bridge.t.sol +++ b/test/unit/root/RootERC20Bridge.t.sol @@ -21,6 +21,7 @@ contract RootERC20BridgeUnitTest is Test, IRootERC20BridgeEvents, IRootERC20Brid address constant CHILD_BRIDGE_ADAPTOR = address(4); string constant CHILD_CHAIN_NAME = "test"; address constant IMX_TOKEN = address(99); + address constant ETH_TOKEN = address(0xeee); ERC20PresetMinterPauser public token; RootERC20Bridge public rootBridge; @@ -39,7 +40,7 @@ contract RootERC20BridgeUnitTest is Test, IRootERC20BridgeEvents, IRootERC20Brid mockAxelarAdaptor = new MockAdaptor(); // The specific ERC20 token template does not matter for these unit tests - rootBridge.initialize(address(mockAxelarAdaptor), CHILD_BRIDGE, CHILD_BRIDGE_ADAPTOR, address(token), IMX_TOKEN); + rootBridge.initialize(address(mockAxelarAdaptor), CHILD_BRIDGE, CHILD_BRIDGE_ADAPTOR, address(token), IMX_TOKEN, ETH_TOKEN); } /** @@ -54,43 +55,49 @@ contract RootERC20BridgeUnitTest is Test, IRootERC20BridgeEvents, IRootERC20Brid function test_RevertIfInitializeTwice() public { vm.expectRevert("Initializable: contract is already initialized"); - rootBridge.initialize(address(mockAxelarAdaptor), CHILD_BRIDGE, CHILD_BRIDGE_ADAPTOR, address(token), IMX_TOKEN); + rootBridge.initialize(address(mockAxelarAdaptor), CHILD_BRIDGE, CHILD_BRIDGE_ADAPTOR, address(token), IMX_TOKEN, ETH_TOKEN); } function test_RevertIf_InitializeWithAZeroAddressRootAdapter() public { RootERC20Bridge bridge = new RootERC20Bridge(); vm.expectRevert(ZeroAddress.selector); - bridge.initialize(address(0), address(1), address(1), address(1), address(1)); + bridge.initialize(address(0), address(1), address(1), address(1), address(1), address(1)); } function test_RevertIf_InitializeWithAZeroAddressChildBridge() public { RootERC20Bridge bridge = new RootERC20Bridge(); vm.expectRevert(ZeroAddress.selector); - bridge.initialize(address(1), address(0), address(1), address(1), address(1)); + bridge.initialize(address(1), address(0), address(1), address(1), address(1), address(1)); } function test_RevertIf_InitializeWithAZeroAddressChildAdapter() public { RootERC20Bridge bridge = new RootERC20Bridge(); vm.expectRevert(ZeroAddress.selector); - bridge.initialize(address(1), address(1), address(0), address(1), address(1)); + bridge.initialize(address(1), address(1), address(0), address(1), address(1), address(1)); } function test_RevertIf_InitializeWithAZeroAddressTokenTemplate() public { RootERC20Bridge bridge = new RootERC20Bridge(); vm.expectRevert(ZeroAddress.selector); - bridge.initialize(address(1), address(1), address(1), address(0), address(1)); + bridge.initialize(address(1), address(1), address(1), address(0), address(1), address(1)); } function test_RevertIf_InitializeWithAZeroAddressIMXToken() public { RootERC20Bridge bridge = new RootERC20Bridge(); vm.expectRevert(ZeroAddress.selector); - bridge.initialize(address(1), address(1), address(1), address(1), address(0)); + bridge.initialize(address(1), address(1), address(1), address(1), address(0), address(1)); + } + + function test_RevertIf_InitializeWithAZeroAddressETHToken() public { + RootERC20Bridge bridge = new RootERC20Bridge(); + vm.expectRevert(ZeroAddress.selector); + bridge.initialize(address(1), address(1), address(1), address(1), address(1), address(0)); } function test_RevertIf_InitializeWithAZeroAddressAll() public { RootERC20Bridge bridge = new RootERC20Bridge(); vm.expectRevert(ZeroAddress.selector); - bridge.initialize(address(0), address(0), address(0), address(0), address(0)); + bridge.initialize(address(0), address(0), address(0), address(0), address(0), address(0)); } /** @@ -188,6 +195,33 @@ contract RootERC20BridgeUnitTest is Test, IRootERC20BridgeEvents, IRootERC20Brid rootBridge.updateRootBridgeAdaptor(address(0)); } + /** + * DEPOSIT ETH + */ + + function test_depositETHCallsSendMessage() public { + console2.log('test_depositETHCallsSendMessage'); + uint256 amount = 100; + (, bytes memory predictedPayload) = setupDeposit(ERC20PresetMinterPauser(ETH_TOKEN), rootBridge, 0, amount, false); + + vm.expectCall( + address(mockAxelarAdaptor), + 0, + abi.encodeWithSelector(mockAxelarAdaptor.sendMessage.selector, predictedPayload, address(this)) + ); + + rootBridge.depositETH{value: amount}(); + } + + function test_depositEmitsNativeDepositEvent() public { + uint256 amount = 100; + (address childToken,) = setupDeposit(ERC20PresetMinterPauser(ETH_TOKEN), rootBridge, 0, amount, false); + + vm.expectEmit(); + emit NativeDeposit(address(token), childToken, address(this), address(this), amount); + rootBridge.depositETH{value: amount}(); + } + /** * DEPOSIT TOKEN */ diff --git a/test/utils.t.sol b/test/utils.t.sol index c7cae0d3..8b6d9b81 100644 --- a/test/utils.t.sol +++ b/test/utils.t.sol @@ -12,11 +12,13 @@ import {IChildERC20, ChildERC20} from "../src/child/ChildERC20.sol"; import {RootAxelarBridgeAdaptor} from "../src/root/RootAxelarBridgeAdaptor.sol"; contract Utils is Test { + function integrationSetup( address childBridge, address childBridgeAdaptor, string memory childBridgeName, - address imxTokenAddress) + address imxTokenAddress, + address ethTokenAddress) public returns ( ERC20PresetMinterPauser token, @@ -40,7 +42,7 @@ contract Utils is Test { address(axelarGasService) ); - rootBridge.initialize(address(axelarAdaptor), childBridge, childBridgeAdaptor, address(token), imxTokenAddress); + rootBridge.initialize(address(axelarAdaptor), childBridge, childBridgeAdaptor, address(token), imxTokenAddress, ethTokenAddress); axelarAdaptor.setChildBridgeAdaptor(); } @@ -73,12 +75,24 @@ contract Utils is Test { address to, bool saveTokenMapping ) public returns (address childToken, bytes memory predictedPayload) { + console2.log("_setupDeposit ------"); + console2.logBytes32(rootBridge.DEPOSIT_SIG()); + console2.logAddress(address(token)); + console2.logAddress(address(this)); + console2.logAddress(to); + console2.logUint(tokenAmount); predictedPayload = abi.encode(rootBridge.DEPOSIT_SIG(), address(token), address(this), to, tokenAmount); + console2.logBytes(predictedPayload); if (saveTokenMapping) { childToken = rootBridge.mapToken{value: gasPrice}(token); } - token.mint(address(this), tokenAmount); - token.approve(address(rootBridge), tokenAmount); + if (address(token) == address(0xeee)) { + vm.deal(to, tokenAmount); + } else { + token.mint(address(this), tokenAmount); + token.approve(address(rootBridge), tokenAmount); + } + return (childToken, predictedPayload); } From 917b829dcc8c09b47aea2dbe3d519d5b824846e5 Mon Sep 17 00:00:00 2001 From: Craig M Date: Tue, 17 Oct 2023 11:24:31 +1300 Subject: [PATCH 15/21] WIP --- src/interfaces/root/IRootERC20Bridge.sol | 2 ++ src/root/RootERC20Bridge.sol | 26 ++++++++++++++++++------ 2 files changed, 22 insertions(+), 6 deletions(-) diff --git a/src/interfaces/root/IRootERC20Bridge.sol b/src/interfaces/root/IRootERC20Bridge.sol index 22131f12..7f0c104f 100644 --- a/src/interfaces/root/IRootERC20Bridge.sol +++ b/src/interfaces/root/IRootERC20Bridge.sol @@ -61,6 +61,8 @@ interface IRootERC20BridgeEvents { } interface IRootERC20BridgeErrors { + /// @notice Error when no gas payment has been received. + error NoGas(); /// @notice Error when a zero address is given when not valid. error ZeroAddress(); /// @notice Error when a token is already mapped. diff --git a/src/root/RootERC20Bridge.sol b/src/root/RootERC20Bridge.sol index dd0dd42f..b4c52f8b 100644 --- a/src/root/RootERC20Bridge.sol +++ b/src/root/RootERC20Bridge.sol @@ -96,18 +96,18 @@ contract RootERC20Bridge is return _mapToken(rootToken); } - function depositETH() external payable { //override removed? + function depositETH(uint256 gasAmount) external payable { //override removed? _depositETH(msg.sender, msg.value); } - function depositToETH(address receiver) external payable { //override removed? + function depositToETH(address receiver, uint256 gasAmount) external payable { //override removed? _depositETH(receiver, msg.value); } - function _depositETH(address receiver, uint256 amount) private { + function _depositETH(address receiver, uint256 amount, uint256 gasAmount) private { console2.log('start balance'); console2.logUint(address(this).balance); - _deposit(IERC20Metadata(NATIVE_TOKEN), receiver, amount); + _deposit(IERC20Metadata(NATIVE_TOKEN), receiver, amount, gasAmount); //@TODO can we do an invariant check here? console2.log('end balance'); @@ -145,6 +145,9 @@ contract RootERC20Bridge is } function _mapToken(IERC20Metadata rootToken) private returns (address) { + if(msg.value == 0) { + revert NoGas(); + } if (address(rootToken) == address(0)) { revert ZeroAddress(); } @@ -173,7 +176,10 @@ contract RootERC20Bridge is function _deposit(IERC20Metadata rootToken, address receiver, uint256 amount) private { console2.log("_deposit ---------------"); - + if(msg.value == 0) { + revert NoGas(); + } + if (receiver == address(0) || address(rootToken) == address(0)) { revert ZeroAddress(); } @@ -210,9 +216,17 @@ contract RootERC20Bridge is // Deposit sig, root token address, depositor, receiver, amount bytes memory payload = abi.encode(DEPOSIT_SIG, rootToken, msg.sender, receiver, amount); // TODO investigate using delegatecall to keep the axelar message sender as the bridge contract, since adaptor can change. - + console2.logBytes(payload); + gasService.payNativeGasForContractCall{ value: msg.value }( + address(this), + destinationChain, + destinationAddress, + payload, + msg.sender + ); + //@TODO need to minus the bridge amount from the gas otherwise we're sending the whole amount to axelar rootBridgeAdaptor.sendMessage{value: msg.value}(payload, msg.sender); From a76bff180e5440c17d897705a570e77c4a853745 Mon Sep 17 00:00:00 2001 From: Craig M Date: Tue, 17 Oct 2023 15:42:53 +1300 Subject: [PATCH 16/21] Update .gitignore --- .gitignore | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.gitignore b/.gitignore index 15101c3c..5680b282 100644 --- a/.gitignore +++ b/.gitignore @@ -5,7 +5,9 @@ out/ # Ignores development broadcast logs !/broadcast /broadcast/*/31337/ +/broadcast/*/2501/ /broadcast/*/31338/ +/broadcast/*/2500/ /broadcast/**/dry-run/ # Docs From ea14d4bf6b11279bb9ceafd65d6c203d9139e951 Mon Sep 17 00:00:00 2001 From: Craig M Date: Tue, 17 Oct 2023 17:10:55 +1300 Subject: [PATCH 17/21] eth deposit working --- src/interfaces/root/IRootERC20Bridge.sol | 8 +- src/root/RootERC20Bridge.sol | 63 +++++----- test/integration/root/RootERC20Bridge.t.sol | 36 +++--- test/unit/root/RootERC20Bridge.t.sol | 121 ++++++++++---------- test/utils.t.sol | 24 ++-- 5 files changed, 120 insertions(+), 132 deletions(-) diff --git a/src/interfaces/root/IRootERC20Bridge.sol b/src/interfaces/root/IRootERC20Bridge.sol index 7f0c104f..b37a333f 100644 --- a/src/interfaces/root/IRootERC20Bridge.sol +++ b/src/interfaces/root/IRootERC20Bridge.sol @@ -61,8 +61,12 @@ interface IRootERC20BridgeEvents { } interface IRootERC20BridgeErrors { - /// @notice Error when no gas payment has been received. - error NoGas(); + /// @notice Error when the amount requested is less than the value sent. + error InsufficientValue(); + /// @notice Error when there is no gas payment received. + error NoGasReceived(); + /// @notice Error when the amount is zero. + error ZeroAmount(); /// @notice Error when a zero address is given when not valid. error ZeroAddress(); /// @notice Error when a token is already mapped. diff --git a/src/root/RootERC20Bridge.sol b/src/root/RootERC20Bridge.sol index b4c52f8b..b26ed996 100644 --- a/src/root/RootERC20Bridge.sol +++ b/src/root/RootERC20Bridge.sol @@ -96,24 +96,31 @@ contract RootERC20Bridge is return _mapToken(rootToken); } - function depositETH(uint256 gasAmount) external payable { //override removed? - _depositETH(msg.sender, msg.value); + function depositETH(uint256 amount) external payable { //override removed? + _depositETH(msg.sender, amount); } - function depositToETH(address receiver, uint256 gasAmount) external payable { //override removed? - _depositETH(receiver, msg.value); + function depositToETH(address receiver, uint256 amount) external payable { //override removed? + _depositETH(receiver, amount); } - function _depositETH(address receiver, uint256 amount, uint256 gasAmount) private { + function _depositETH(address receiver, uint256 amount) private { + if (msg.value < amount) { + revert InsufficientValue(); + } + + if (amount == msg.value) { + revert NoGasReceived(); + } + console2.log('start balance'); console2.logUint(address(this).balance); - _deposit(IERC20Metadata(NATIVE_TOKEN), receiver, amount, gasAmount); + _deposit(IERC20Metadata(NATIVE_TOKEN), receiver, amount); //@TODO can we do an invariant check here? console2.log('end balance'); console2.logUint(address(this).balance); - // invariant check to ensure that the root native balance has increased by the amount deposited // if (address(msg.sender).balance != expectedBalance) { // revert BalanceInvariantCheckFailed(address(this).balance, expectedBalance); @@ -135,6 +142,9 @@ contract RootERC20Bridge is } function _depositERC20(IERC20Metadata rootToken, address receiver, uint256 amount) private { + if (msg.value == 0) { + revert NoGasReceived(); + } uint256 expectedBalance = rootToken.balanceOf(address(this)) + amount; _deposit(rootToken, receiver, amount); // invariant check to ensure that the root token balance has increased by the amount deposited @@ -146,7 +156,7 @@ contract RootERC20Bridge is function _mapToken(IERC20Metadata rootToken) private returns (address) { if(msg.value == 0) { - revert NoGas(); + revert NoGasReceived(); } if (address(rootToken) == address(0)) { revert ZeroAddress(); @@ -176,15 +186,16 @@ contract RootERC20Bridge is function _deposit(IERC20Metadata rootToken, address receiver, uint256 amount) private { console2.log("_deposit ---------------"); - if(msg.value == 0) { - revert NoGas(); - } - if (receiver == address(0) || address(rootToken) == address(0)) { revert ZeroAddress(); } + if (amount == 0) { + revert ZeroAmount(); + } + address childToken; + uint256 feeAmount; // The native token does not need to be mapped since it should have been mapped on initialization // The native token also cannot be transferred since it was received in the payable function call @@ -192,46 +203,26 @@ contract RootERC20Bridge is // Therefore, we need to decide how to handle this and it may be a UI decision to wait until map token message is executed on child chain. // Discuss this, and add this decision to the design doc. if (address(rootToken) != NATIVE_TOKEN) { - - console2.log("!NATIVE_TOKEN"); - - if (address(rootToken) != rootIMXToken) { childToken = rootTokenToChildToken[address(rootToken)]; if (childToken == address(0)) { revert NotMapped(); } } - // ERC20 must be transferred explicitly rootToken.safeTransferFrom(msg.sender, address(this), amount); + feeAmount = msg.value; + } else { + feeAmount = msg.value - amount; } - - console2.logBytes32(DEPOSIT_SIG); - console2.logAddress(address(rootToken)); - console2.logAddress(msg.sender); - console2.logAddress(address(receiver)); - console2.logUint(amount); // Deposit sig, root token address, depositor, receiver, amount bytes memory payload = abi.encode(DEPOSIT_SIG, rootToken, msg.sender, receiver, amount); // TODO investigate using delegatecall to keep the axelar message sender as the bridge contract, since adaptor can change. - console2.logBytes(payload); - - gasService.payNativeGasForContractCall{ value: msg.value }( - address(this), - destinationChain, - destinationAddress, - payload, - msg.sender - ); - - //@TODO need to minus the bridge amount from the gas otherwise we're sending the whole amount to axelar - rootBridgeAdaptor.sendMessage{value: msg.value}(payload, msg.sender); + rootBridgeAdaptor.sendMessage{value: feeAmount}(payload, msg.sender); if (address(rootToken) == NATIVE_TOKEN) { - console2.log("emit NativeDeposit"); emit NativeDeposit(address(rootToken), childETHToken, msg.sender, receiver, amount); } else if (address(rootToken) == rootIMXToken) { emit IMXDeposit(address(rootToken), msg.sender, receiver, amount); diff --git a/test/integration/root/RootERC20Bridge.t.sol b/test/integration/root/RootERC20Bridge.t.sol index 44eda30b..d6bfd4de 100644 --- a/test/integration/root/RootERC20Bridge.t.sol +++ b/test/integration/root/RootERC20Bridge.t.sol @@ -16,9 +16,10 @@ contract RootERC20BridgeIntegrationTest is Test, IRootERC20BridgeEvents, IRootAx address constant CHILD_BRIDGE_ADAPTOR = address(4); string constant CHILD_CHAIN_NAME = "test"; bytes32 public constant MAP_TOKEN_SIG = keccak256("MAP_TOKEN"); - address constant IMX_TOKEN_ADDRESS = address(99); - address constant ETH_TOKEN_ADDRESS = address(88); - + address constant IMX_TOKEN_ADDRESS = address(0xccc); + address constant CHILD_ETH_TOKEN = address(0xddd); + uint256 constant mapTokenFee = 300; + uint256 constant depositFee = 200; ERC20PresetMinterPauser public token; RootERC20Bridge public rootBridge; @@ -28,7 +29,7 @@ contract RootERC20BridgeIntegrationTest is Test, IRootERC20BridgeEvents, IRootAx function setUp() public { (token, rootBridge, axelarAdaptor, mockAxelarGateway, axelarGasService) = - integrationSetup(CHILD_BRIDGE, CHILD_BRIDGE_ADAPTOR, CHILD_CHAIN_NAME, IMX_TOKEN_ADDRESS, ETH_TOKEN_ADDRESS); + integrationSetup(CHILD_BRIDGE, CHILD_BRIDGE_ADAPTOR, CHILD_CHAIN_NAME, IMX_TOKEN_ADDRESS, CHILD_ETH_TOKEN); } /** @@ -38,7 +39,6 @@ contract RootERC20BridgeIntegrationTest is Test, IRootERC20BridgeEvents, IRootAx */ function test_mapToken() public { // TODO split this up into multiple tests. - uint256 mapTokenFee = 300; address childToken = Clones.predictDeterministicAddress(address(token), keccak256(abi.encodePacked(token)), CHILD_BRIDGE); @@ -98,9 +98,8 @@ contract RootERC20BridgeIntegrationTest is Test, IRootERC20BridgeEvents, IRootAx // TODO split into multiple tests function test_depositToken() public { uint256 tokenAmount = 300; - uint256 gasPrice = 100; string memory childBridgeAdaptorString = Strings.toHexString(CHILD_BRIDGE_ADAPTOR); - (address childToken, bytes memory predictedPayload) = setupDeposit(token, rootBridge, gasPrice, tokenAmount, true); + (address childToken, bytes memory predictedPayload) = setupDeposit(token, rootBridge, mapTokenFee, depositFee, tokenAmount, true); vm.expectEmit(address(axelarAdaptor)); emit MapTokenAxelarMessage(CHILD_CHAIN_NAME, childBridgeAdaptorString, predictedPayload); @@ -109,13 +108,13 @@ contract RootERC20BridgeIntegrationTest is Test, IRootERC20BridgeEvents, IRootAx vm.expectCall( address(axelarAdaptor), - gasPrice, + depositFee, abi.encodeWithSelector(axelarAdaptor.sendMessage.selector, predictedPayload, address(this)) ); vm.expectCall( address(axelarGasService), - gasPrice, + depositFee, abi.encodeWithSelector( axelarGasService.payNativeGasForContractCall.selector, address(axelarAdaptor), @@ -140,24 +139,23 @@ contract RootERC20BridgeIntegrationTest is Test, IRootERC20BridgeEvents, IRootAx uint256 thisNativePreBal = address(this).balance; uint256 gasServiceNativePreBal = address(axelarGasService).balance; - rootBridge.deposit{value: gasPrice}(token, tokenAmount); + rootBridge.deposit{value: depositFee}(token, tokenAmount); // Check that tokens are transferred assertEq(thisPreBal - tokenAmount, token.balanceOf(address(this)), "Tokens not transferred from user"); assertEq(bridgePreBal + tokenAmount, token.balanceOf(address(rootBridge)), "Tokens not transferred to bridge"); // Check that native asset transferred to gas service - assertEq(thisNativePreBal - gasPrice, address(this).balance, "ETH not paid from user"); - assertEq(gasServiceNativePreBal + gasPrice, address(axelarGasService).balance, "ETH not paid to adaptor"); + assertEq(thisNativePreBal - depositFee, address(this).balance, "ETH not paid from user"); + assertEq(gasServiceNativePreBal + depositFee, address(axelarGasService).balance, "ETH not paid to adaptor"); } // TODO split into multiple tests function test_depositTo() public { uint256 tokenAmount = 300; - uint256 gasPrice = 100; address recipient = address(9876); string memory childBridgeAdaptorString = Strings.toHexString(CHILD_BRIDGE_ADAPTOR); (address childToken, bytes memory predictedPayload) = - setupDepositTo(token, rootBridge, gasPrice, tokenAmount, recipient, true); + setupDepositTo(token, rootBridge, mapTokenFee, depositFee, tokenAmount, recipient, true); vm.expectEmit(address(axelarAdaptor)); emit MapTokenAxelarMessage(CHILD_CHAIN_NAME, childBridgeAdaptorString, predictedPayload); @@ -166,13 +164,13 @@ contract RootERC20BridgeIntegrationTest is Test, IRootERC20BridgeEvents, IRootAx vm.expectCall( address(axelarAdaptor), - gasPrice, + depositFee, abi.encodeWithSelector(axelarAdaptor.sendMessage.selector, predictedPayload, address(this)) ); vm.expectCall( address(axelarGasService), - gasPrice, + depositFee, abi.encodeWithSelector( axelarGasService.payNativeGasForContractCall.selector, address(axelarAdaptor), @@ -197,13 +195,13 @@ contract RootERC20BridgeIntegrationTest is Test, IRootERC20BridgeEvents, IRootAx uint256 thisNativePreBal = address(this).balance; uint256 gasServiceNativePreBal = address(axelarGasService).balance; - rootBridge.depositTo{value: gasPrice}(token, recipient, tokenAmount); + rootBridge.depositTo{value: depositFee}(token, recipient, tokenAmount); // Check that tokens are transferred assertEq(thisPreBal - tokenAmount, token.balanceOf(address(this)), "Tokens not transferred from user"); assertEq(bridgePreBal + tokenAmount, token.balanceOf(address(rootBridge)), "Tokens not transferred to bridge"); // Check that native asset transferred to gas service - assertEq(thisNativePreBal - gasPrice, address(this).balance, "ETH not paid from user"); - assertEq(gasServiceNativePreBal + gasPrice, address(axelarGasService).balance, "ETH not paid to adaptor"); + assertEq(thisNativePreBal - depositFee, address(this).balance, "ETH not paid from user"); + assertEq(gasServiceNativePreBal + depositFee, address(axelarGasService).balance, "ETH not paid to adaptor"); } } diff --git a/test/unit/root/RootERC20Bridge.t.sol b/test/unit/root/RootERC20Bridge.t.sol index 9e8eea8f..2e3fc3de 100644 --- a/test/unit/root/RootERC20Bridge.t.sol +++ b/test/unit/root/RootERC20Bridge.t.sol @@ -21,7 +21,10 @@ contract RootERC20BridgeUnitTest is Test, IRootERC20BridgeEvents, IRootERC20Brid address constant CHILD_BRIDGE_ADAPTOR = address(4); string constant CHILD_CHAIN_NAME = "test"; address constant IMX_TOKEN = address(99); - address constant ETH_TOKEN = address(0xeee); + address constant CHILD_ETH_TOKEN = address(0xddd); + address constant NATIVE_TOKEN = address(0xeee); + uint256 constant mapTokenFee = 300; + uint256 constant depositFee = 200; ERC20PresetMinterPauser public token; RootERC20Bridge public rootBridge; @@ -40,7 +43,7 @@ contract RootERC20BridgeUnitTest is Test, IRootERC20BridgeEvents, IRootERC20Brid mockAxelarAdaptor = new MockAdaptor(); // The specific ERC20 token template does not matter for these unit tests - rootBridge.initialize(address(mockAxelarAdaptor), CHILD_BRIDGE, CHILD_BRIDGE_ADAPTOR, address(token), IMX_TOKEN, ETH_TOKEN); + rootBridge.initialize(address(mockAxelarAdaptor), CHILD_BRIDGE, CHILD_BRIDGE_ADAPTOR, address(token), IMX_TOKEN, CHILD_ETH_TOKEN); } /** @@ -55,7 +58,7 @@ contract RootERC20BridgeUnitTest is Test, IRootERC20BridgeEvents, IRootERC20Brid function test_RevertIfInitializeTwice() public { vm.expectRevert("Initializable: contract is already initialized"); - rootBridge.initialize(address(mockAxelarAdaptor), CHILD_BRIDGE, CHILD_BRIDGE_ADAPTOR, address(token), IMX_TOKEN, ETH_TOKEN); + rootBridge.initialize(address(mockAxelarAdaptor), CHILD_BRIDGE, CHILD_BRIDGE_ADAPTOR, address(token), IMX_TOKEN, CHILD_ETH_TOKEN); } function test_RevertIf_InitializeWithAZeroAddressRootAdapter() public { @@ -105,7 +108,6 @@ contract RootERC20BridgeUnitTest is Test, IRootERC20BridgeEvents, IRootERC20Brid */ function test_mapToken_EmitsTokenMappedEvent() public { - uint256 mapTokenFee = 300; address childToken = Clones.predictDeterministicAddress(address(token), keccak256(abi.encodePacked(token)), CHILD_BRIDGE); @@ -116,7 +118,6 @@ contract RootERC20BridgeUnitTest is Test, IRootERC20BridgeEvents, IRootERC20Brid } function test_mapToken_CallsAdaptor() public { - uint256 mapTokenFee = 300; bytes memory payload = abi.encode(rootBridge.MAP_TOKEN_SIG(), token, token.name(), token.symbol(), token.decimals()); @@ -131,7 +132,6 @@ contract RootERC20BridgeUnitTest is Test, IRootERC20BridgeEvents, IRootERC20Brid } function test_mapToken_SetsTokenMapping() public { - uint256 mapTokenFee = 300; address childToken = Clones.predictDeterministicAddress(address(token), keccak256(abi.encodePacked(token)), CHILD_BRIDGE); @@ -140,14 +140,14 @@ contract RootERC20BridgeUnitTest is Test, IRootERC20BridgeEvents, IRootERC20Brid assertEq(rootBridge.rootTokenToChildToken(address(token)), childToken, "rootTokenToChildToken mapping not set"); } - function testFuzz_mapToken_UpdatesEthBalance(uint256 mapTokenFee) public { - vm.assume(mapTokenFee < address(this).balance); - vm.assume(mapTokenFee > 0); + function testFuzz_mapToken_UpdatesEthBalance(uint256 _mapTokenFee) public { + vm.assume(_mapTokenFee < address(this).balance); + vm.assume(_mapTokenFee > 0); uint256 thisPreBal = address(this).balance; uint256 rootBridgePreBal = address(rootBridge).balance; uint256 adaptorPreBal = address(mockAxelarAdaptor).balance; - rootBridge.mapToken{value: mapTokenFee}(token); + rootBridge.mapToken{value: _mapTokenFee}(token); /* * Because this is a unit test, the adaptor is mocked. This adaptor would typically @@ -155,8 +155,8 @@ contract RootERC20BridgeUnitTest is Test, IRootERC20BridgeEvents, IRootERC20Brid */ // User pays - assertEq(address(this).balance, thisPreBal - mapTokenFee, "ETH balance not decreased"); - assertEq(address(mockAxelarAdaptor).balance, adaptorPreBal + mapTokenFee, "ETH not paid to adaptor"); + assertEq(address(this).balance, thisPreBal - _mapTokenFee, "ETH balance not decreased"); + assertEq(address(mockAxelarAdaptor).balance, adaptorPreBal + _mapTokenFee, "ETH not paid to adaptor"); assertEq(address(rootBridge).balance, rootBridgePreBal, "ETH balance not increased"); } @@ -195,31 +195,32 @@ contract RootERC20BridgeUnitTest is Test, IRootERC20BridgeEvents, IRootERC20Brid rootBridge.updateRootBridgeAdaptor(address(0)); } + // @TODO add tests for no gas received; mapToken, deposit, depositTo, depositETH, depositToETH + /** * DEPOSIT ETH */ function test_depositETHCallsSendMessage() public { - console2.log('test_depositETHCallsSendMessage'); - uint256 amount = 100; - (, bytes memory predictedPayload) = setupDeposit(ERC20PresetMinterPauser(ETH_TOKEN), rootBridge, 0, amount, false); + uint256 amount = 1000; + (, bytes memory predictedPayload) = setupDeposit(ERC20PresetMinterPauser(NATIVE_TOKEN), rootBridge, mapTokenFee, depositFee, amount, false); vm.expectCall( address(mockAxelarAdaptor), - 0, + depositFee, abi.encodeWithSelector(mockAxelarAdaptor.sendMessage.selector, predictedPayload, address(this)) ); - rootBridge.depositETH{value: amount}(); + rootBridge.depositETH{value: amount+depositFee}(amount); } function test_depositEmitsNativeDepositEvent() public { - uint256 amount = 100; - (address childToken,) = setupDeposit(ERC20PresetMinterPauser(ETH_TOKEN), rootBridge, 0, amount, false); + uint256 amount = 1000; + (address childToken,) = setupDeposit(ERC20PresetMinterPauser(NATIVE_TOKEN), rootBridge, mapTokenFee, depositFee, amount, false); vm.expectEmit(); - emit NativeDeposit(address(token), childToken, address(this), address(this), amount); - rootBridge.depositETH{value: amount}(); + emit NativeDeposit(NATIVE_TOKEN, CHILD_ETH_TOKEN, address(this), address(this), amount); + rootBridge.depositETH{value: amount+depositFee}(amount); } /** @@ -228,45 +229,45 @@ contract RootERC20BridgeUnitTest is Test, IRootERC20BridgeEvents, IRootERC20Brid function test_depositCallsSendMessage() public { uint256 amount = 100; - (, bytes memory predictedPayload) = setupDeposit(token, rootBridge, 0, amount, true); + (, bytes memory predictedPayload) = setupDeposit(token, rootBridge, mapTokenFee, depositFee, amount, true); vm.expectCall( address(mockAxelarAdaptor), - 0, + depositFee, abi.encodeWithSelector(mockAxelarAdaptor.sendMessage.selector, predictedPayload, address(this)) ); - rootBridge.deposit(token, amount); + rootBridge.deposit{value: depositFee}(token, amount); } function test_depositEmitsERC20DepositEvent() public { uint256 amount = 100; - (address childToken,) = setupDeposit(token, rootBridge, 0, amount, true); + (address childToken,) = setupDeposit(token, rootBridge, mapTokenFee, depositFee, amount, true); vm.expectEmit(); emit ERC20Deposit(address(token), childToken, address(this), address(this), amount); - rootBridge.deposit(token, amount); + rootBridge.deposit{value: depositFee}(token, amount); } function test_depositIMXEmitsIMXDepositEvent() public { uint256 amount = 100; - setupDeposit(ERC20PresetMinterPauser(IMX_TOKEN), rootBridge, 0, amount, false); + setupDeposit(ERC20PresetMinterPauser(IMX_TOKEN), rootBridge, mapTokenFee, depositFee, amount, false); vm.expectEmit(); emit IMXDeposit(IMX_TOKEN, address(this), address(this), amount); - rootBridge.deposit(IERC20Metadata(IMX_TOKEN), amount); + rootBridge.deposit{value: depositFee}(IERC20Metadata(IMX_TOKEN), amount); } function test_depositTransfersTokens() public { uint256 amount = 100; - setupDeposit(token, rootBridge, 0, amount, true); + setupDeposit(token, rootBridge, mapTokenFee, depositFee, amount, true); uint256 thisPreBal = token.balanceOf(address(this)); uint256 bridgePreBal = token.balanceOf(address(rootBridge)); - rootBridge.deposit(token, amount); + rootBridge.deposit{value: depositFee}(token, amount); // Check that tokens are transferred assertEq(thisPreBal - amount, token.balanceOf(address(this)), "Tokens not transferred from user"); @@ -274,47 +275,46 @@ contract RootERC20BridgeUnitTest is Test, IRootERC20BridgeEvents, IRootERC20Brid } function test_depositTransfersNativeAsset() public { - uint256 gasPrice = 300; uint256 amount = 100; - setupDeposit(token, rootBridge, 0, amount, true); + setupDeposit(token, rootBridge, mapTokenFee, depositFee, amount, true); uint256 thisNativePreBal = address(this).balance; uint256 adaptorNativePreBal = address(mockAxelarAdaptor).balance; - rootBridge.deposit{value: gasPrice}(token, amount); + rootBridge.deposit{value: depositFee}(token, amount); // Check that native asset transferred to adaptor // In this case, because the adaptor is mocked, gas payment goes to the adaptor. - assertEq(thisNativePreBal - gasPrice, address(this).balance, "ETH not paid from user"); - assertEq(adaptorNativePreBal + gasPrice, address(mockAxelarAdaptor).balance, "ETH not paid to adaptor"); + assertEq(thisNativePreBal - depositFee, address(this).balance, "ETH not paid from user"); + assertEq(adaptorNativePreBal + depositFee, address(mockAxelarAdaptor).balance, "ETH not paid to adaptor"); } function test_RevertIf_depositCalledWithZeroAddress() public { uint256 amount = 100; - setupDeposit(token, rootBridge, 0, amount, true); + setupDeposit(token, rootBridge, mapTokenFee, depositFee, amount, true); // Will fail when it tries to call balanceOf vm.expectRevert(); - rootBridge.deposit(IERC20Metadata(address(0)), 100); + rootBridge.deposit{value: depositFee}(IERC20Metadata(address(0)), amount); } function test_RevertIf_depositCalledWithUnmappedToken() public { uint256 amount = 100; - setupDeposit(token, rootBridge, 0, amount, true); + setupDeposit(token, rootBridge, mapTokenFee, depositFee, amount, true); ERC20PresetMinterPauser newToken = new ERC20PresetMinterPauser("Test", "TST"); vm.expectRevert(NotMapped.selector); - rootBridge.deposit(newToken, 100); + rootBridge.deposit{value: depositFee}(newToken, amount); } // We want to ensure that messages don't get sent when they are not supposed to function test_RevertIf_depositCalledWhenTokenApprovalNotProvided() public { uint256 amount = 100; - setupDeposit(token, rootBridge, 0, amount, true); + setupDeposit(token, rootBridge, mapTokenFee, depositFee, amount, true); vm.expectRevert(); - rootBridge.deposit(token, amount * 2); + rootBridge.deposit{value: depositFee}(token, amount * 2); } /** @@ -325,26 +325,26 @@ contract RootERC20BridgeUnitTest is Test, IRootERC20BridgeEvents, IRootERC20Brid uint256 amount = 100; address receiver = address(12345); - (, bytes memory predictedPayload) = setupDepositTo(token, rootBridge, 0, amount, receiver, true); + (, bytes memory predictedPayload) = setupDepositTo(token, rootBridge, mapTokenFee, depositFee, amount, receiver, true); vm.expectCall( address(mockAxelarAdaptor), - 0, + depositFee, abi.encodeWithSelector(mockAxelarAdaptor.sendMessage.selector, predictedPayload, address(this)) ); - rootBridge.depositTo(token, receiver, amount); + rootBridge.depositTo{value: depositFee}(token, receiver, amount); } function test_depositToEmitsERC20DepositEvent() public { uint256 amount = 100; address receiver = address(12345); - (address childToken,) = setupDepositTo(token, rootBridge, 0, amount, receiver, true); + (address childToken,) = setupDepositTo(token, rootBridge, mapTokenFee, depositFee, amount, receiver, true); vm.expectEmit(); emit ERC20Deposit(address(token), childToken, address(this), receiver, amount); - rootBridge.depositTo(token, receiver, amount); + rootBridge.depositTo{value: depositFee}(token, receiver, amount); } function test_depositToIMXEmitsIMXDepositEvent() public { @@ -352,23 +352,23 @@ contract RootERC20BridgeUnitTest is Test, IRootERC20BridgeEvents, IRootERC20Brid address receiver = address(12345); - setupDepositTo(ERC20PresetMinterPauser(IMX_TOKEN), rootBridge, 0, amount, receiver, false); + setupDepositTo(ERC20PresetMinterPauser(IMX_TOKEN), rootBridge, mapTokenFee, depositFee, amount, receiver, false); vm.expectEmit(); emit IMXDeposit(IMX_TOKEN, address(this), receiver, amount); - rootBridge.depositTo(IERC20Metadata(IMX_TOKEN), receiver, amount); + rootBridge.depositTo{value: depositFee}(IERC20Metadata(IMX_TOKEN), receiver, amount); } function test_depositToTransfersTokens() public { uint256 amount = 100; address receiver = address(12345); - setupDepositTo(token, rootBridge, 0, amount, receiver, true); + setupDepositTo(token, rootBridge, mapTokenFee, depositFee, amount, receiver, true); uint256 thisPreBal = token.balanceOf(address(this)); uint256 bridgePreBal = token.balanceOf(address(rootBridge)); - rootBridge.depositTo(token, receiver, amount); + rootBridge.depositTo{value: depositFee}(token, receiver, amount); // Check that tokens are transferred assertEq(thisPreBal - amount, token.balanceOf(address(this)), "Tokens not transferred from user"); @@ -376,53 +376,52 @@ contract RootERC20BridgeUnitTest is Test, IRootERC20BridgeEvents, IRootERC20Brid } function test_depositToTransfersNativeAsset() public { - uint256 gasPrice = 300; uint256 amount = 100; address receiver = address(12345); - setupDepositTo(token, rootBridge, gasPrice, amount, receiver, true); + setupDepositTo(token, rootBridge, mapTokenFee, depositFee, amount, receiver, true); uint256 thisNativePreBal = address(this).balance; uint256 adaptorNativePreBal = address(mockAxelarAdaptor).balance; - rootBridge.depositTo{value: gasPrice}(token, receiver, amount); + rootBridge.depositTo{value: depositFee}(token, receiver, amount); // Check that native asset transferred to adaptor // In this case, because the adaptor is mocked, gas payment goes to the adaptor. - assertEq(thisNativePreBal - gasPrice, address(this).balance, "ETH not paid from user"); - assertEq(adaptorNativePreBal + gasPrice, address(mockAxelarAdaptor).balance, "ETH not paid to adaptor"); + assertEq(thisNativePreBal - depositFee, address(this).balance, "ETH not paid from user"); + assertEq(adaptorNativePreBal + depositFee, address(mockAxelarAdaptor).balance, "ETH not paid to adaptor"); } // We want to ensure that messages don't get sent when they are not supposed to function test_RevertIf_depositToCalledWhenTokenApprovalNotProvided() public { uint256 amount = 100; address receiver = address(12345); - setupDepositTo(token, rootBridge, 0, amount, receiver, true); + setupDepositTo(token, rootBridge, mapTokenFee, depositFee, amount, receiver, true); vm.expectRevert(); - rootBridge.depositTo(token, receiver, amount * 2); + rootBridge.depositTo{value: depositFee}(token, receiver, amount * 2); } function test_RevertIf_depositToCalledWithZeroAddress() public { uint256 amount = 100; address receiver = address(12345); - setupDepositTo(token, rootBridge, 0, amount, receiver, true); + setupDepositTo(token, rootBridge, mapTokenFee, depositFee, amount, receiver, true); // Will fail when it tries to call balanceOf vm.expectRevert(); - rootBridge.depositTo(IERC20Metadata(address(0)), receiver, 100); + rootBridge.depositTo{value: depositFee}(IERC20Metadata(address(0)), receiver, amount); } function test_RevertIf_depositToCalledWithUnmappedToken() public { uint256 amount = 100; address receiver = address(12345); - setupDepositTo(token, rootBridge, 0, amount, receiver, true); + setupDepositTo(token, rootBridge, mapTokenFee, depositFee, amount, receiver, true); ERC20PresetMinterPauser newToken = new ERC20PresetMinterPauser("Test", "TST"); vm.expectRevert(NotMapped.selector); - rootBridge.depositTo(newToken, receiver, 100); + rootBridge.depositTo{value: depositFee}(newToken, receiver, amount); } } diff --git a/test/utils.t.sol b/test/utils.t.sol index 8b6d9b81..5da0769f 100644 --- a/test/utils.t.sol +++ b/test/utils.t.sol @@ -49,45 +49,41 @@ contract Utils is Test { function setupDeposit( ERC20PresetMinterPauser token, RootERC20Bridge rootBridge, - uint256 gasPrice, + uint256 mapTokenFee, + uint256 depositFee, uint256 tokenAmount, bool saveTokenMapping ) public returns (address childToken, bytes memory predictedPayload) { - return _setupDeposit(token, rootBridge, gasPrice, tokenAmount, address(this), saveTokenMapping); + return _setupDeposit(token, rootBridge, mapTokenFee, depositFee, tokenAmount, address(this), saveTokenMapping); } function setupDepositTo( ERC20PresetMinterPauser token, RootERC20Bridge rootBridge, - uint256 gasPrice, + uint256 mapTokenFee, + uint256 depositFee, uint256 tokenAmount, address to, bool saveTokenMapping ) public returns (address childToken, bytes memory predictedPayload) { - return _setupDeposit(token, rootBridge, gasPrice, tokenAmount, to, saveTokenMapping); + return _setupDeposit(token, rootBridge, mapTokenFee, depositFee, tokenAmount, to, saveTokenMapping); } function _setupDeposit( ERC20PresetMinterPauser token, RootERC20Bridge rootBridge, - uint256 gasPrice, + uint256 mapTokenFee, + uint256 depositFee, uint256 tokenAmount, address to, bool saveTokenMapping ) public returns (address childToken, bytes memory predictedPayload) { - console2.log("_setupDeposit ------"); - console2.logBytes32(rootBridge.DEPOSIT_SIG()); - console2.logAddress(address(token)); - console2.logAddress(address(this)); - console2.logAddress(to); - console2.logUint(tokenAmount); predictedPayload = abi.encode(rootBridge.DEPOSIT_SIG(), address(token), address(this), to, tokenAmount); - console2.logBytes(predictedPayload); if (saveTokenMapping) { - childToken = rootBridge.mapToken{value: gasPrice}(token); + childToken = rootBridge.mapToken{value: mapTokenFee}(token); } if (address(token) == address(0xeee)) { - vm.deal(to, tokenAmount); + vm.deal(to, tokenAmount+depositFee); } else { token.mint(address(this), tokenAmount); token.approve(address(rootBridge), tokenAmount); From 0a7ffb1f1eb88aa4ce7f19facf2cb259059ab864 Mon Sep 17 00:00:00 2001 From: Craig M Date: Tue, 17 Oct 2023 18:58:32 +1300 Subject: [PATCH 18/21] more tests --- src/interfaces/root/IRootERC20Bridge.sol | 2 - src/root/RootERC20Bridge.sol | 25 +++-------- test/unit/root/RootERC20Bridge.t.sol | 53 ++++++++++++++++++++++-- 3 files changed, 54 insertions(+), 26 deletions(-) diff --git a/src/interfaces/root/IRootERC20Bridge.sol b/src/interfaces/root/IRootERC20Bridge.sol index b37a333f..0752b2cb 100644 --- a/src/interfaces/root/IRootERC20Bridge.sol +++ b/src/interfaces/root/IRootERC20Bridge.sol @@ -64,8 +64,6 @@ interface IRootERC20BridgeErrors { /// @notice Error when the amount requested is less than the value sent. error InsufficientValue(); /// @notice Error when there is no gas payment received. - error NoGasReceived(); - /// @notice Error when the amount is zero. error ZeroAmount(); /// @notice Error when a zero address is given when not valid. error ZeroAddress(); diff --git a/src/root/RootERC20Bridge.sol b/src/root/RootERC20Bridge.sol index b26ed996..f5d35a1a 100644 --- a/src/root/RootERC20Bridge.sol +++ b/src/root/RootERC20Bridge.sol @@ -109,22 +109,14 @@ contract RootERC20Bridge is revert InsufficientValue(); } - if (amount == msg.value) { - revert NoGasReceived(); - } - - console2.log('start balance'); - console2.logUint(address(this).balance); + uint256 expectedBalance = address(this).balance - (msg.value - amount); + _deposit(IERC20Metadata(NATIVE_TOKEN), receiver, amount); - //@TODO can we do an invariant check here? - console2.log('end balance'); - - console2.logUint(address(this).balance); // invariant check to ensure that the root native balance has increased by the amount deposited - // if (address(msg.sender).balance != expectedBalance) { - // revert BalanceInvariantCheckFailed(address(this).balance, expectedBalance); - // } + if (address(this).balance != expectedBalance) { + revert BalanceInvariantCheckFailed(address(this).balance, expectedBalance); + } } /** @@ -142,9 +134,6 @@ contract RootERC20Bridge is } function _depositERC20(IERC20Metadata rootToken, address receiver, uint256 amount) private { - if (msg.value == 0) { - revert NoGasReceived(); - } uint256 expectedBalance = rootToken.balanceOf(address(this)) + amount; _deposit(rootToken, receiver, amount); // invariant check to ensure that the root token balance has increased by the amount deposited @@ -155,9 +144,6 @@ contract RootERC20Bridge is } function _mapToken(IERC20Metadata rootToken) private returns (address) { - if(msg.value == 0) { - revert NoGasReceived(); - } if (address(rootToken) == address(0)) { revert ZeroAddress(); } @@ -185,7 +171,6 @@ contract RootERC20Bridge is } function _deposit(IERC20Metadata rootToken, address receiver, uint256 amount) private { - console2.log("_deposit ---------------"); if (receiver == address(0) || address(rootToken) == address(0)) { revert ZeroAddress(); } diff --git a/test/unit/root/RootERC20Bridge.t.sol b/test/unit/root/RootERC20Bridge.t.sol index 2e3fc3de..578f4696 100644 --- a/test/unit/root/RootERC20Bridge.t.sol +++ b/test/unit/root/RootERC20Bridge.t.sol @@ -195,8 +195,6 @@ contract RootERC20BridgeUnitTest is Test, IRootERC20BridgeEvents, IRootERC20Brid rootBridge.updateRootBridgeAdaptor(address(0)); } - // @TODO add tests for no gas received; mapToken, deposit, depositTo, depositETH, depositToETH - /** * DEPOSIT ETH */ @@ -214,15 +212,62 @@ contract RootERC20BridgeUnitTest is Test, IRootERC20BridgeEvents, IRootERC20Brid rootBridge.depositETH{value: amount+depositFee}(amount); } - function test_depositEmitsNativeDepositEvent() public { + function test_depositETHEmitsNativeDepositEvent() public { uint256 amount = 1000; - (address childToken,) = setupDeposit(ERC20PresetMinterPauser(NATIVE_TOKEN), rootBridge, mapTokenFee, depositFee, amount, false); + setupDeposit(ERC20PresetMinterPauser(NATIVE_TOKEN), rootBridge, mapTokenFee, depositFee, amount, false); vm.expectEmit(); emit NativeDeposit(NATIVE_TOKEN, CHILD_ETH_TOKEN, address(this), address(this), amount); rootBridge.depositETH{value: amount+depositFee}(amount); } + function test_RevertIf_depositETHInsufficientValue() public { + uint256 amount = 1000; + setupDeposit(ERC20PresetMinterPauser(NATIVE_TOKEN), rootBridge, mapTokenFee, depositFee, amount, false); + + vm.expectRevert(InsufficientValue.selector); + rootBridge.depositETH{value: (amount/2)+depositFee}(amount); + } + + /** + * ZERO AMOUNT + */ + + function test_RevertIf_depositETHAmountIsZero() public { + uint256 amount = 0; + setupDeposit(ERC20PresetMinterPauser(NATIVE_TOKEN), rootBridge, mapTokenFee, depositFee, amount, false); + + vm.expectRevert(ZeroAmount.selector); + rootBridge.depositETH{value: amount+depositFee}(amount); + } + + function test_RevertIf_depositToETHAmountIsZero() public { + uint256 amount = 0; + address receiver = address(12345); + + setupDeposit(ERC20PresetMinterPauser(NATIVE_TOKEN), rootBridge, mapTokenFee, depositFee, amount, false); + + vm.expectRevert(ZeroAmount.selector); + rootBridge.depositToETH{value: amount+depositFee}(receiver, amount); + } + + function test_RevertIf_depositAmountIsZero() public { + uint256 amount = 0; + setupDeposit(ERC20PresetMinterPauser(NATIVE_TOKEN), rootBridge, mapTokenFee, depositFee, amount, false); + + vm.expectRevert(ZeroAmount.selector); + rootBridge.deposit{value: depositFee}(token, amount); + } + + function test_RevertIf_depositToAmountIsZero() public { + uint256 amount = 0; + address receiver = address(12345); + setupDeposit(ERC20PresetMinterPauser(NATIVE_TOKEN), rootBridge, mapTokenFee, depositFee, amount, false); + + vm.expectRevert(ZeroAmount.selector); + rootBridge.depositTo{value: depositFee}(token, receiver, amount); + } + /** * DEPOSIT TOKEN */ From 04414caee58060e77e41186ff88b35bf79d66bb5 Mon Sep 17 00:00:00 2001 From: Craig M Date: Tue, 17 Oct 2023 19:06:40 +1300 Subject: [PATCH 19/21] Update RootERC20Bridge.sol --- src/root/RootERC20Bridge.sol | 1 - 1 file changed, 1 deletion(-) diff --git a/src/root/RootERC20Bridge.sol b/src/root/RootERC20Bridge.sol index f5d35a1a..be0add99 100644 --- a/src/root/RootERC20Bridge.sol +++ b/src/root/RootERC20Bridge.sol @@ -11,7 +11,6 @@ import {IAxelarGateway} from "@axelar-cgp-solidity/contracts/interfaces/IAxelarG import {IRootERC20Bridge, IERC20Metadata} from "../interfaces/root/IRootERC20Bridge.sol"; import {IRootERC20BridgeEvents, IRootERC20BridgeErrors} from "../interfaces/root/IRootERC20Bridge.sol"; import {IRootERC20BridgeAdaptor} from "../interfaces/root/IRootERC20BridgeAdaptor.sol"; -import {console2} from "forge-std/Test.sol"; /** * @notice RootERC20Bridge is a bridge that allows ERC20 tokens to be transferred from the root chain to the child chain. From a2fb994b127cd4d7aa5d53110808726b8532ed3e Mon Sep 17 00:00:00 2001 From: Craig M Date: Wed, 18 Oct 2023 18:30:49 +1300 Subject: [PATCH 20/21] depositToETH tests added --- test/unit/root/RootERC20Bridge.t.sol | 36 ++++++++++++++++++++++++++++ 1 file changed, 36 insertions(+) diff --git a/test/unit/root/RootERC20Bridge.t.sol b/test/unit/root/RootERC20Bridge.t.sol index 578f4696..90c5c5a3 100644 --- a/test/unit/root/RootERC20Bridge.t.sol +++ b/test/unit/root/RootERC20Bridge.t.sol @@ -229,6 +229,42 @@ contract RootERC20BridgeUnitTest is Test, IRootERC20BridgeEvents, IRootERC20Brid rootBridge.depositETH{value: (amount/2)+depositFee}(amount); } + /** + * DEPOSIT TO ETH + */ + + function test_depositToETHCallsSendMessage() public { + uint256 amount = 1000; + address receiver = address(12345); + (, bytes memory predictedPayload) = setupDepositTo(ERC20PresetMinterPauser(NATIVE_TOKEN), rootBridge, mapTokenFee, depositFee, amount, receiver, false); + vm.expectCall( + address(mockAxelarAdaptor), + depositFee, + abi.encodeWithSelector(mockAxelarAdaptor.sendMessage.selector, predictedPayload, address(this)) + ); + + rootBridge.depositToETH{value: amount+depositFee}(receiver, amount); + } + + function test_depositToETHEmitsNativeDepositEvent() public { + uint256 amount = 1000; + address receiver = address(12345); + setupDepositTo(ERC20PresetMinterPauser(NATIVE_TOKEN), rootBridge, mapTokenFee, depositFee, amount, receiver, false); + + vm.expectEmit(); + emit NativeDeposit(NATIVE_TOKEN, CHILD_ETH_TOKEN, address(this), receiver, amount); + rootBridge.depositToETH{value: amount+depositFee}(receiver, amount); + } + + function test_RevertIf_depositToETHInsufficientValue() public { + uint256 amount = 1000; + address receiver = address(12345); + setupDepositTo(ERC20PresetMinterPauser(NATIVE_TOKEN), rootBridge, mapTokenFee, depositFee, amount, receiver, false); + + vm.expectRevert(InsufficientValue.selector); + rootBridge.depositToETH{value: (amount/2)+depositFee}(receiver, amount); + } + /** * ZERO AMOUNT */ From 1e552350e5c216deaac3c3baab9e96e4f62d9012 Mon Sep 17 00:00:00 2001 From: Craig M Date: Wed, 18 Oct 2023 18:33:07 +1300 Subject: [PATCH 21/21] typo fix --- src/root/RootERC20Bridge.sol | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/root/RootERC20Bridge.sol b/src/root/RootERC20Bridge.sol index be0add99..ddb8f287 100644 --- a/src/root/RootERC20Bridge.sol +++ b/src/root/RootERC20Bridge.sol @@ -53,8 +53,8 @@ contract RootERC20Bridge is * @param newChildERC20Bridge Address of child ERC20 bridge to communicate with. * @param newChildBridgeAdaptor Address of child bridge adaptor to communicate with. * @param newChildTokenTemplate Address of child token template to clone. - * @param newRootIMXToken Address of ECR20 IMX on the root chain. - * @param newChildETHToken Address of ECR20 ETH on the child chain. + * @param newRootIMXToken Address of ERC20 IMX on the root chain. + * @param newChildETHToken Address of ERC20 ETH on the child chain. * @dev Can only be called once. */ function initialize(