Skip to content

Commit

Permalink
Merge branch 'main' of github.com:LiskHQ/lisk-contracts into 115-impl…
Browse files Browse the repository at this point in the history
…ement-claim
  • Loading branch information
Phanco committed Jan 11, 2024
2 parents 677f613 + 18250a6 commit 931c71d
Show file tree
Hide file tree
Showing 4 changed files with 99 additions and 29 deletions.
4 changes: 2 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ git submodule update --init --recursive
**NOTE**: To successfully deploy all smart contracts and execute the required transactions, the deployer (specified by `PRIVATE_KEY` in the `.env` file) must have funds available in its address on the respective networks. For a private test network, you can use a any private key from the list provided by `anvil` when the network is created, or choose another private key with sufficient funds on both forked networks.

Private L1 and L2 test networks are established using the `anvil` tool, and the smart contracts are deployed using the `forge script` tool. To run private networks and deploy the smart contracts, follow these steps:
1. Create `.env` file and set the vars `PRIVATE_KEY`, `L1_RPC_URL`, `L1_FORK_RPC_URL`, `L2_RPC_URL`, `L2_FORK_RPC_URL`, `L1_STANDARD_BRIDGE_ADDR` and `TEST_NETWORK_MNEMONIC`. You can copy and rename the `.env.example` file if the default values provided in `.env.example` are satisfactory. `L1_RPC_URL` should be set to `http://127.0.0.1:8545` and `L2_RPC_URL` should be set to `http://127.0.0.1:8546` if no changes are made in the `./runL1TestNetwork.sh` or `./runL2TestNetwork.sh` script files.
1. Create `.env` file and set the vars `PRIVATE_KEY`, `L2_TOKEN_SALT`, `L1_RPC_URL`, `L1_FORK_RPC_URL`, `L2_RPC_URL`, `L2_FORK_RPC_URL`, `L1_STANDARD_BRIDGE_ADDR` and `TEST_NETWORK_MNEMONIC`. You can copy and rename the `.env.example` file if the default values provided in `.env.example` are satisfactory. `L1_RPC_URL` should be set to `http://127.0.0.1:8545` and `L2_RPC_URL` should be set to `http://127.0.0.1:8546` if no changes are made in the `./runL1TestNetwork.sh` or `./runL2TestNetwork.sh` script files.
2. Navigate to the `script` directory.
3. To create and launch a private test L1 network, execute the script: `./runL1TestNetwork.sh`
4. To create and launch a private test L2 network, execute the script: `./runL2TestNetwork.sh`
Expand All @@ -55,7 +55,7 @@ Private L1 and L2 test networks are established using the `anvil` tool, and the
**NOTE**: To successfully deploy all smart contracts and execute the required transactions, the deployer (specified by `PRIVATE_KEY` in the `.env` file) must have funds available in its address. This implies that a private key with a sufficient balance on both public test networks is required.

To deploy smart contracts on both L1 and L2 public networks, you will need to provide for each network an URL for a public node from a RPC provider, such as Alchemy or Infura. Additionally, in order to verify smart contracts during the deployment process, it is necessary to provide an Etherscan API key. Follow these steps to deploy the smart contracts:
1. Create `.env` file and set the vars `PRIVATE_KEY`, `L1_RPC_URL`, `L2_RPC_URL`, `L1_ETHERSCAN_API_KEY`, `L2_ETHERSCAN_API_KEY` and `L1_STANDARD_BRIDGE_ADDR`. You can copy and rename the `.env.example` file if the default values provided in `.env.example` are satisfactory. `L1_ETHERSCAN_API_KEY` and `L2_ETHERSCAN_API_KEY` may be empty to skip smart contracts verification process.
1. Create `.env` file and set the vars `PRIVATE_KEY`, `L2_TOKEN_SALT`, `L1_RPC_URL`, `L2_RPC_URL`, `L1_ETHERSCAN_API_KEY`, `L2_ETHERSCAN_API_KEY` and `L1_STANDARD_BRIDGE_ADDR`. You can copy and rename the `.env.example` file if the default values provided in `.env.example` are satisfactory. `L1_ETHERSCAN_API_KEY` and `L2_ETHERSCAN_API_KEY` may be empty to skip smart contracts verification process.
2. Navigate to the `script` directory.
3. To deploy all smart contracts, execute the script: `./deployContracts.sh`

Expand Down
7 changes: 3 additions & 4 deletions script/L2LiskToken.s.sol
Original file line number Diff line number Diff line change
Expand Up @@ -36,15 +36,14 @@ contract L2LiskTokenScript is Script {

// calculate L2LiskToken contract address
address l2LiskTokenAddressCalculated = computeCreate2Address(
salt,
hashInitCode(type(L2LiskToken).creationCode, abi.encode(L2_STANDARD_BRIDGE, l1AddressesConfig.L1LiskToken)),
CREATE2_FACTORY
salt, hashInitCode(type(L2LiskToken).creationCode, abi.encode(l1AddressesConfig.L1LiskToken))
);
console2.log("Simulation: Calculated L2 Lisk token address: %s", l2LiskTokenAddressCalculated);

// deploy L2LiskToken contract
vm.startBroadcast(deployerPrivateKey);
L2LiskToken l2LiskToken = new L2LiskToken{ salt: salt }(L2_STANDARD_BRIDGE, l1AddressesConfig.L1LiskToken);
L2LiskToken l2LiskToken = new L2LiskToken{ salt: salt }(l1AddressesConfig.L1LiskToken);
l2LiskToken.initialize(L2_STANDARD_BRIDGE);
vm.stopBroadcast();

assert(address(l2LiskToken) == l2LiskTokenAddressCalculated);
Expand Down
22 changes: 19 additions & 3 deletions src/L2/L2LiskToken.sol
Original file line number Diff line number Diff line change
Expand Up @@ -27,11 +27,20 @@ contract L2LiskToken is IOptimismMintableERC20, ERC20, ERC20Permit {
/// @notice Symbol of the token.
string private constant SYMBOL = "LSK";

/// @notice Address which deployed this contract. Only this address is able to call initialize() function.
/// Using Foundry's forge script when deploying a contract with CREATE2 opcode, the address of the
/// deployer is a proxy contract address to have a deterministic deployer address. This offers a
/// flexibility that a deployed contract address is calculated only by the hash of the contract's bytecode
/// and a salt. Because initialize() function needs to be called by the actual deployer (EOA), we need to
/// store the address of the original caller of the constructor (tx.origin) and not msg.sender which is the
/// proxy contract address.
address private immutable initializer;

/// @notice Address of the corresponding version of this token on the remote chain (on L1).
address public immutable REMOTE_TOKEN;

/// @notice Address of the StandardBridge on this (deployed) network.
address public immutable BRIDGE;
address public BRIDGE;

/// @notice Emitted whenever tokens are minted for an account.
/// @param account Address of the account tokens are being minted for.
Expand All @@ -50,10 +59,17 @@ contract L2LiskToken is IOptimismMintableERC20, ERC20, ERC20Permit {
}

/// @notice Constructs the L2LiskToken contract.
/// @param bridgeAddr Address of the L2 standard bridge.
/// @param remoteTokenAddr Address of the corresponding L1LiskToken.
constructor(address bridgeAddr, address remoteTokenAddr) ERC20(NAME, SYMBOL) ERC20Permit(NAME) {
constructor(address remoteTokenAddr) ERC20(NAME, SYMBOL) ERC20Permit(NAME) {
REMOTE_TOKEN = remoteTokenAddr;
initializer = tx.origin;
}

/// @notice Initializes the L2LiskToken contract.
/// @param bridgeAddr Address of the L2 standard bridge.
function initialize(address bridgeAddr) public {
require(msg.sender == initializer, "L2LiskToken: only initializer can initialize this contract");
require(BRIDGE == address(0), "L2LiskToken: already initialized");
BRIDGE = bridgeAddr;
}

Expand Down
95 changes: 75 additions & 20 deletions test/L2/L2LiskToken.t.sol
Original file line number Diff line number Diff line change
Expand Up @@ -22,9 +22,15 @@ contract L2LiskTokenTest is Test {
bytes32 public salt;

function setUp() public {
bridge = vm.addr(1);
remoteToken = vm.addr(2);
l2LiskToken = new L2LiskToken(bridge, remoteToken);
bridge = vm.addr(uint256(bytes32("bridge")));
remoteToken = vm.addr(uint256(bytes32("remoteToken")));

// msg.sender and tx.origin needs to be the same for the contract to be able to call initialize()
vm.prank(address(this), address(this));
l2LiskToken = new L2LiskToken(remoteToken);
l2LiskToken.initialize(bridge);
vm.stopPrank();

sigUtils = new SigUtils(l2LiskToken.DOMAIN_SEPARATOR());

(alice, alicePrivateKey) = makeAddrAndKey("alice");
Expand All @@ -48,43 +54,92 @@ contract L2LiskTokenTest is Test {
assertEq(l2LiskToken.supportsInterface(type(IOptimismMintableERC20).interfaceId), true);
}

function test_Initialize_ValidInitializer() public {
// initialize the contract being alice
vm.prank(alice, alice);
L2LiskToken l2LiskTokenNew = new L2LiskToken{ salt: salt }(remoteToken);

// initialize the contract being alice and the initializer
vm.prank(alice);
l2LiskTokenNew.initialize(bridge);

// check that the contract is initialized
assertEq(l2LiskTokenNew.BRIDGE(), bridge);
}

function test_InitializeFail_NotInitializer() public {
// initialize the contract being alice
vm.prank(alice);
L2LiskToken l2LiskTokenNew = new L2LiskToken{ salt: salt }(remoteToken);

// try to initialize the contract being bob and not the initializer
vm.prank(bob);
vm.expectRevert("L2LiskToken: only initializer can initialize this contract");
l2LiskTokenNew.initialize(bridge);
}

function test_InitializeFail_AlreadyInitialized() public {
vm.expectRevert("L2LiskToken: already initialized");
l2LiskToken.initialize(bridge);
}

function test_UnifiedTokenAddress() public {
// calculate L2LiskToken contract address
address l2LiskTokenAddressCalculated = computeCreate2Address(
salt, hashInitCode(type(L2LiskToken).creationCode, abi.encode(bridge, remoteToken)), alice
);
address l2LiskTokenAddressCalculated =
computeCreate2Address(salt, hashInitCode(type(L2LiskToken).creationCode, abi.encode(remoteToken)), alice);

// use the same salt and the same deployer as in calculated address of L2LiskToken contract
vm.prank(alice, alice);
L2LiskToken l2LiskTokenSalted = new L2LiskToken{ salt: salt }(remoteToken);
vm.prank(alice);
L2LiskToken l2LiskTokenSalted = new L2LiskToken{ salt: salt }(bridge, remoteToken);
l2LiskTokenSalted.initialize(bridge);

// check that both token contracts and the calculated address have the same address
assertEq(address(l2LiskTokenSalted), l2LiskTokenAddressCalculated);
}

function test_UnifiedTokenAddressFail_DifferentDeployers() public {
function test_UnifiedTokenAddress_DifferentStandardBridgeAddress() public {
// calculate L2LiskToken contract address
address l2LiskTokenAddressCalculated = computeCreate2Address(
salt, hashInitCode(type(L2LiskToken).creationCode, abi.encode(bridge, remoteToken)), alice
);
address l2LiskTokenAddressCalculated =
computeCreate2Address(salt, hashInitCode(type(L2LiskToken).creationCode, abi.encode(remoteToken)), alice);

// use the same salt and the same deployer as in calculated address of L2LiskToken contract
vm.prank(alice, alice);
L2LiskToken l2LiskTokenSalted = new L2LiskToken{ salt: salt }(remoteToken);

// use different Standard Bridge addresses
vm.prank(alice);
l2LiskTokenSalted.initialize(vm.addr(uint256(bytes32("differentBridge"))));

// check that both token contracts and the calculated address have the same address
assertEq(address(l2LiskTokenSalted), l2LiskTokenAddressCalculated);
}

function test_UnifiedTokenAddressFail_DifferentDeployer() public {
// calculate L2LiskToken contract address
address l2LiskTokenAddressCalculated =
computeCreate2Address(salt, hashInitCode(type(L2LiskToken).creationCode, abi.encode(remoteToken)), alice);

// use the same salt but different deployer as in calculated address of L2LiskToken contract
vm.prank(bob, bob);
L2LiskToken l2LiskTokenSalted = new L2LiskToken{ salt: salt }(remoteToken);
vm.prank(bob);
L2LiskToken l2LiskTokenSalted = new L2LiskToken{ salt: salt }(bridge, remoteToken);
l2LiskTokenSalted.initialize(bridge);

// check that token contracts and the calculated address have different addresses
assertNotEq(address(l2LiskTokenSalted), l2LiskTokenAddressCalculated);
}

function test_UnifiedTokenAddressFail_DifferentSalt() public {
// calculate L2LiskToken contract address
address l2LiskTokenAddressCalculated = computeCreate2Address(
salt, hashInitCode(type(L2LiskToken).creationCode, abi.encode(bridge, remoteToken)), alice
);
address l2LiskTokenAddressCalculated =
computeCreate2Address(salt, hashInitCode(type(L2LiskToken).creationCode, abi.encode(remoteToken)), alice);

// use different salt but the same deployer as in calculated address of L2LiskToken contract
vm.prank(alice, alice);
L2LiskToken l2LiskTokenSalted = new L2LiskToken{ salt: keccak256(bytes("different_salt")) }(remoteToken);
vm.prank(alice);
L2LiskToken l2LiskTokenSalted = new L2LiskToken{ salt: keccak256(bytes("different_salt")) }(bridge, remoteToken);
l2LiskTokenSalted.initialize(bridge);

// check that token contracts and the calculated address have different addresses
assertNotEq(address(l2LiskTokenSalted), l2LiskTokenAddressCalculated);
Expand Down Expand Up @@ -121,9 +176,9 @@ contract L2LiskTokenTest is Test {
}

function test_MintFail_NotBridge() public {
// try to mint new tokens beeing alice and not the Standard Bridge
// try to mint new tokens being alice and not the Standard Bridge
vm.prank(alice);
vm.expectRevert();
vm.expectRevert("L2LiskToken: only bridge can mint or burn");
l2LiskToken.mint(bob, 100 * 10 ** 18);
}

Expand Down Expand Up @@ -170,9 +225,9 @@ contract L2LiskTokenTest is Test {
l2LiskToken.mint(bob, 100 * 10 ** 18);
assertEq(l2LiskToken.balanceOf(bob), 100 * 10 ** 18);

// try to burn tokens beeing alice and not the Standard Bridge
// try to burn tokens being alice and not the Standard Bridge
vm.prank(alice);
vm.expectRevert();
vm.expectRevert("L2LiskToken: only bridge can mint or burn");
l2LiskToken.burn(bob, 100 * 10 ** 18);
}

Expand Down

0 comments on commit 931c71d

Please sign in to comment.