-
Notifications
You must be signed in to change notification settings - Fork 7
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
SMR-1782 ETH Child Bridge #9
Changes from 7 commits
82d032c
ee559c7
877195d
c4c59a4
22a0f27
932ad87
6a06077
fc2eaae
e4de38e
1d5fcac
c597a9f
eb4134e
108abc4
abc5c4f
59e4a8c
56737b4
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -33,38 +33,49 @@ contract ChildERC20Bridge is | |
{ | ||
using SafeERC20 for IERC20Metadata; | ||
|
||
/// @dev leave this as the first param for the integration tests | ||
mapping(address => address) public rootTokenToChildToken; | ||
|
||
bytes32 public constant MAP_TOKEN_SIG = keccak256("MAP_TOKEN"); | ||
bytes32 public constant DEPOSIT_SIG = keccak256("DEPOSIT"); | ||
address public constant NATIVE_ETH = address(0xeee); | ||
|
||
IChildERC20BridgeAdaptor public bridgeAdaptor; | ||
|
||
/// @dev The address that will be sending messages to, and receiving messages from, the child chain. | ||
string public rootERC20BridgeAdaptor; | ||
/// @dev The address of the token template that will be cloned to create tokens. | ||
address public childTokenTemplate; | ||
/// @dev The name of the chain that this bridge is connected to. | ||
string public rootChain; | ||
mapping(address => address) public rootTokenToChildToken; | ||
|
||
bytes32 public constant MAP_TOKEN_SIG = keccak256("MAP_TOKEN"); | ||
bytes32 public constant DEPOSIT_SIG = keccak256("DEPOSIT"); | ||
address public imxToken; | ||
/// @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. | ||
* @param newBridgeAdaptor Address of StateSender to send deposit information to. | ||
* @param newRootERC20BridgeAdaptor Stringified address of root ERC20 bridge adaptor to communicate with. | ||
* @param newChildTokenTemplate Address of child token template to clone. | ||
* @param newRootChain A stringified representation of the chain that this bridge is connected to. Used for validation. | ||
* @param newIMXToken Address of ECR20 IMX on the root chain. | ||
* @param newRootIMXToken Address of ECR20 IMX on the root chain. | ||
* @param newRootETHToken Address used to denote ETH on the root chain. | ||
* @dev Can only be called once. | ||
*/ | ||
function initialize( | ||
address newBridgeAdaptor, | ||
string memory newRootERC20BridgeAdaptor, | ||
address newChildTokenTemplate, | ||
string memory newRootChain, | ||
address newIMXToken | ||
) public initializer { | ||
address newRootIMXToken, | ||
address newRootETHToken) | ||
public initializer | ||
{ | ||
if (newBridgeAdaptor == address(0) | ||
|| newChildTokenTemplate == address(0) | ||
|| newIMXToken == address(0)) { | ||
|| newRootIMXToken == address(0) | ||
|| newRootETHToken == address(0)) { | ||
revert ZeroAddress(); | ||
} | ||
|
||
|
@@ -80,7 +91,12 @@ contract ChildERC20Bridge is | |
childTokenTemplate = newChildTokenTemplate; | ||
bridgeAdaptor = IChildERC20BridgeAdaptor(newBridgeAdaptor); | ||
rootChain = newRootChain; | ||
imxToken = newIMXToken; | ||
rootIMXToken = newRootIMXToken; | ||
|
||
IChildERC20 clonedETHToken = | ||
IChildERC20(Clones.cloneDeterministic(childTokenTemplate, keccak256(abi.encodePacked(newRootETHToken)))); | ||
clonedETHToken.initialize(newRootETHToken, "Ethereum", "ETH", 18); | ||
childETHToken = address(clonedETHToken); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Out of curiosity, is there a reason why we store a reference to the ETH token contract separately from other mapped tokens? I'm wondering if we could apply the same approach here, and map There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. i only really did it like this to mirror the decision to leave the L1 ERC20 IMX -> L2 Native IMX mapping out of the token mapping and handle it separately. I think largely because on the native side of the pair there is technically no token address. Even though at the moment we use a made up address to denote the native token, that's not necessarily the only way we could handle that relationship. I want to look into the other bridge contracts a bit more as a reference on how they handle native mappings. If we stick with using an address to represent the native token and we're confident that won't cause any problems, i believe we could store it in the token mapping instead of separately. Open to suggestions on this. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. it looks like the Polygon bridge does use a mock address and adds it to the token mapping in the initialize function on the root bridge.
https://github.com/0xPolygon/core-contracts/blob/main/contracts/root/RootERC20Predicate.sol And again in the initialize function in the child bridge;
https://github.com/0xPolygon/core-contracts/blob/main/contracts/child/ChildERC20Predicate.sol There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. i think keeping the native tokens out of the mapping is the most secure. I've added checks on the mapToken functions to block the mapping of the hardcoded ETH address as the root token. This means no one can try to map an ERC20 with that reserved address (if it's even possible) and since we don't allow bridging of unmapped tokens it means we can be sure no one can deposit some scam coin on L1 and receive ERC20 ETH on L2. |
||
} | ||
|
||
/** | ||
|
@@ -122,7 +138,7 @@ contract ChildERC20Bridge is | |
revert ZeroAddress(); | ||
} | ||
|
||
if (address(rootToken) == imxToken) { | ||
if (address(rootToken) == rootIMXToken) { | ||
revert CantMapIMX(); | ||
} | ||
|
||
|
@@ -149,19 +165,29 @@ contract ChildERC20Bridge is | |
|
||
address childToken; | ||
|
||
if (address(rootToken) != imxToken) { | ||
childToken = rootTokenToChildToken[address(rootToken)]; | ||
if (childToken == address(0)) { | ||
revert NotMapped(); | ||
if (address(rootToken) != rootIMXToken) { | ||
if (address(rootToken) == NATIVE_ETH) { | ||
childToken = childETHToken; | ||
} else { | ||
childToken = rootTokenToChildToken[address(rootToken)]; | ||
if (childToken == address(0)) { | ||
revert NotMapped(); | ||
} | ||
} | ||
|
||
if (address(childToken).code.length == 0) { | ||
revert EmptyTokenContract(); | ||
} | ||
|
||
if (!IChildERC20(childToken).mint(receiver, amount)) { | ||
revert MintFailed(); | ||
} | ||
emit ERC20Deposit(address(rootToken), childToken, sender, receiver, amount); | ||
|
||
if (address(rootToken) == NATIVE_ETH) { | ||
emit NativeEthDeposit(address(rootToken), childToken, sender, receiver, amount); | ||
} else { | ||
emit ERC20Deposit(address(rootToken), childToken, sender, receiver, amount); | ||
} | ||
} else { | ||
Address.sendValue(payable(receiver), amount); | ||
emit IMXDeposit(address(rootToken), sender, receiver, amount); | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -18,7 +18,8 @@ import {Utils} from "../../utils.t.sol"; | |
contract ChildERC20BridgeIntegrationTest is Test, IChildERC20BridgeEvents, IChildERC20BridgeErrors, Utils { | ||
string public ROOT_ADAPTOR_ADDRESS = Strings.toHexString(address(1)); | ||
string public ROOT_CHAIN_NAME = "ROOT_CHAIN"; | ||
Comment on lines
18
to
20
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think there is some coverage that is missing in the child bridge integration tests now. Namely, making sure that if the root chain's ETH address is used, that the child ETH address is chosen, and that There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. looking at adding these integration tests now. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. ive added more integration tests to the root and child. also removed the need for the childETH address to be passed into the rootBridge initialize method and updated tests + deploy scripts to reflect that. |
||
address constant IMX_TOKEN = address(9); | ||
address constant IMX_TOKEN_ADDRESS = address(0xccc); | ||
address constant CHILD_ETH_TOKEN = address(0xddd); | ||
|
||
ChildERC20Bridge public childERC20Bridge; | ||
ChildERC20 public childERC20; | ||
|
@@ -35,7 +36,7 @@ contract ChildERC20BridgeIntegrationTest is Test, IChildERC20BridgeEvents, IChil | |
new ChildAxelarBridgeAdaptor(address(mockChildAxelarGateway), address(childERC20Bridge)); | ||
|
||
childERC20Bridge.initialize( | ||
address(childAxelarBridgeAdaptor), ROOT_ADAPTOR_ADDRESS, address(childERC20), ROOT_CHAIN_NAME, IMX_TOKEN | ||
address(childAxelarBridgeAdaptor), ROOT_ADAPTOR_ADDRESS, address(childERC20), ROOT_CHAIN_NAME, IMX_TOKEN_ADDRESS, CHILD_ETH_TOKEN | ||
); | ||
} | ||
|
||
|
@@ -246,8 +247,8 @@ contract ChildERC20BridgeIntegrationTest is Test, IChildERC20BridgeEvents, IChil | |
bytes32 depositSig = childERC20Bridge.DEPOSIT_SIG(); | ||
address rootAddress = address(0x123); | ||
{ | ||
// Slot is 6 because of the Ownable, Initializable contracts coming first. | ||
uint256 rootTokenToChildTokenMappingSlot = 6; | ||
// Slot is 2 because of the Ownable, Initializable contracts coming first. | ||
uint256 rootTokenToChildTokenMappingSlot = 2; | ||
address childAddress = address(444444); | ||
bytes32 slot = getMappingStorageSlotFor(rootAddress, rootTokenToChildTokenMappingSlot); | ||
bytes32 data = bytes32(uint256(uint160(childAddress))); | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We use
newRootETHToken
here in the initializer, but we have the storage constantNATIVE_ETH
which is used in other parts of the contract to check the root token address and if it represents ETH. This could be confusingThere was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
i think you're right, we don't need to parse this in on initialization since it wont change between environments/networks unlike the rootIMXToken address. I will remove this from the initializer update the tests and rely on the hardcoded value.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
removed it from the initialiser and updated tests.