mirror of
https://github.com/vacp2p/linea-monorepo.git
synced 2026-01-09 04:08:01 -05:00
Feat/122 Add TokenBridge e2e tests (#210)
* fix: add mutex in account manager to avoid nonce issue * fix: optimize global setup * Limiting number of concurrent traces API requests for the local stack to avoid occasional OOM-s * Limiting number of verticles for Traces API node * Add E2E TokenBridge tests * fixing test and adding concurency * fixing test and adding concurency * fixing test and adding concurency * fixing test and adding concurency * fixing nonce management * deploying l2token for the L2 -> L1 test * adjusting accounts for L2->L1 test * adjusting l2TestContractAddress * use nonce management for L1->L2 test * adjusting the TestERC20 contract and tests * rebasing with fix/133-improve-e2e-tests-performance * fix: update jest config to exit even if there are open handles * Trying out Besu untuned and raising limit per endpoint to 2 for traces * Trying out Besu untuned and raising limit per endpoint to 2 for traces and Shomei node * Using besu untuned for arithmetization as well * Compile once and parallelise setRemoteTokenBridge * feat: deploy smart contracts from artifacts + change e2e tests setup * fix: update pnpm * fix: remove compile contracts gradle task * fix: remove compileContracts gradle task * fix: refactor genesis generator dockerfile + downgrade l1-el-node besu version * fix: move abi from e2e folder to contract folder + refactor contracts deployments scripts * feat: add deployment script from artifacts for LineaRollupV6 * update pnpm version in get-started.md * fix: update console log in deployment scripts * fix: update besu version + fix deployment scripts * correct addresses * fix import * use abi and bytecode for deployments * use upgradable beacon for BridgedToken ABI deploys * use saved abi and bytecode for TestERC20 deploy * correct deployBridgedTokenAndTokenBridge casing * optimize token bridge e2e calls * use explicit message event data * use precomputed nonces for e2e stack --------- Co-authored-by: VGau <victorien.gauch@consensys.net> Co-authored-by: Victorien Gauch <85494462+VGau@users.noreply.github.com> Co-authored-by: Roman <4833306+Filter94@users.noreply.github.com> Co-authored-by: thedarkjester <grant.southey@consensys.net> Co-authored-by: The Dark Jester <thedarkjester@users.noreply.github.com>
This commit is contained in:
53
Makefile
53
Makefile
@@ -108,6 +108,53 @@ deploy-l2messageservice:
|
||||
L2MSGSERVICE_RATE_LIMIT_AMOUNT=1000000000000000000000 \
|
||||
npx ts-node local-deployments-artifacts/deployL2MessageService.ts
|
||||
|
||||
deploy-token-bridge-l1:
|
||||
# WARNING: FOR LOCAL DEV ONLY - DO NOT REUSE THESE KEYS ELSEWHERE
|
||||
cd contracts/; \
|
||||
PRIVATE_KEY=0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80 \
|
||||
RPC_URL=http:\\localhost:8445/ \
|
||||
REMOTE_CHAIN_ID=1337 \
|
||||
TOKEN_BRIDGE_L1=true \
|
||||
TOKEN_BRIDGE_SECURITY_COUNCIL=0x90F79bf6EB2c4f870365E785982E1f101E93b906 \
|
||||
L2_MESSAGE_SERVICE_ADDRESS=0xe537D669CA013d86EBeF1D64e40fC74CADC91987 \
|
||||
LINEA_ROLLUP_ADDRESS=0xCf7Ed3AccA5a467e9e704C703E8D87F634fB0Fc9 \
|
||||
npx ts-node local-deployments-artifacts/deployBridgedTokenAndTokenBridge.ts
|
||||
|
||||
deploy-token-bridge-l2:
|
||||
# WARNING: FOR LOCAL DEV ONLY - DO NOT REUSE THESE KEYS ELSEWHERE
|
||||
cd contracts/; \
|
||||
SAVE_ADDRESS=true \
|
||||
PRIVATE_KEY=0x1dd171cec7e2995408b5513004e8207fe88d6820aeff0d82463b3e41df251aae \
|
||||
RPC_URL=http:\\localhost:8545/ \
|
||||
REMOTE_CHAIN_ID=31648428 \
|
||||
TOKEN_BRIDGE_L1=false \
|
||||
TOKEN_BRIDGE_SECURITY_COUNCIL=0xf17f52151EbEF6C7334FAD080c5704D77216b732 \
|
||||
L2_MESSAGE_SERVICE_ADDRESS=0xe537D669CA013d86EBeF1D64e40fC74CADC91987 \
|
||||
LINEA_ROLLUP_ADDRESS=0xCf7Ed3AccA5a467e9e704C703E8D87F634fB0Fc9 \
|
||||
npx ts-node local-deployments-artifacts/deployBridgedTokenAndTokenBridge.ts
|
||||
|
||||
deploy-l1-test-erc20:
|
||||
# WARNING: FOR LOCAL DEV ONLY - DO NOT REUSE THESE KEYS ELSEWHERE
|
||||
cd contracts/; \
|
||||
PRIVATE_KEY=0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80 \
|
||||
RPC_URL=http:\\localhost:8445/ \
|
||||
TEST_ERC20_L1=true \
|
||||
TEST_ERC20_NAME=TestERC20 \
|
||||
TEST_ERC20_SYMBOL=TERC20 \
|
||||
TEST_ERC20_INITIAL_SUPPLY=100000 \
|
||||
npx ts-node local-deployments-artifacts/deployTestERC20.ts
|
||||
|
||||
deploy-l2-test-erc20:
|
||||
# WARNING: FOR LOCAL DEV ONLY - DO NOT REUSE THESE KEYS ELSEWHERE
|
||||
cd contracts/; \
|
||||
PRIVATE_KEY=0x1dd171cec7e2995408b5513004e8207fe88d6820aeff0d82463b3e41df251aae \
|
||||
RPC_URL=http:\\localhost:8545/ \
|
||||
TEST_ERC20_L1=false \
|
||||
TEST_ERC20_NAME=TestERC20 \
|
||||
TEST_ERC20_SYMBOL=TERC20 \
|
||||
TEST_ERC20_INITIAL_SUPPLY=100000 \
|
||||
npx ts-node local-deployments-artifacts/deployTestERC20.ts
|
||||
|
||||
upgrade-linea-rollup-on-uat:
|
||||
cd contracts/; \
|
||||
rm -f .openzeppelin/goerli.json; \
|
||||
@@ -153,7 +200,11 @@ deploy-contracts-v4:
|
||||
$(MAKE) -j2 deploy-linea-rollup-v4 deploy-l2messageservice
|
||||
|
||||
deploy-contracts:
|
||||
$(MAKE) -j2 deploy-linea-rollup-v5 deploy-l2messageservice
|
||||
cd contracts/; \
|
||||
export L1_NONCE=$$(npx ts-node local-deployments-artifacts/get-wallet-nonce.ts --wallet-priv-key 0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80 --rpc-url http://localhost:8445) && \
|
||||
export L2_NONCE=$$(npx ts-node local-deployments-artifacts/get-wallet-nonce.ts --wallet-priv-key 0x1dd171cec7e2995408b5513004e8207fe88d6820aeff0d82463b3e41df251aae --rpc-url http://localhost:8545) && \
|
||||
cd .. && \
|
||||
$(MAKE) -j6 deploy-linea-rollup-v5 deploy-token-bridge-l1 deploy-l1-test-erc20 deploy-l2messageservice deploy-token-bridge-l2 deploy-l2-test-erc20
|
||||
|
||||
testnet-start-l2:
|
||||
docker compose -f docker/compose.yml -f docker/compose-testnet-sync.overrides.yml --profile l2 up -d
|
||||
|
||||
@@ -15,7 +15,7 @@ export const tryStoreAddress = async (
|
||||
const network = await ethers.provider.getNetwork();
|
||||
|
||||
const ContractFactory = await ethers.getContractFactory(contractName);
|
||||
const dirPath = path.join(__dirname, "..", "deployments", `${networkName}`);
|
||||
const dirPath = path.join(__dirname, "..", "..", "deployments", `${networkName}`);
|
||||
|
||||
if (!fs.existsSync(dirPath)) {
|
||||
fs.mkdirSync(dirPath, { recursive: true });
|
||||
|
||||
40
contracts/contracts/tokenBridge/mocks/TestERC20.sol
Normal file
40
contracts/contracts/tokenBridge/mocks/TestERC20.sol
Normal file
@@ -0,0 +1,40 @@
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
pragma solidity 0.8.19;
|
||||
|
||||
import "@openzeppelin/contracts/token/ERC20/ERC20.sol";
|
||||
import "@openzeppelin/contracts/access/Ownable.sol";
|
||||
|
||||
/**
|
||||
* @title TestERC20
|
||||
* @dev Simple ERC20 Token example.
|
||||
*/
|
||||
|
||||
contract TestERC20 is ERC20, Ownable {
|
||||
/**
|
||||
* @dev Constructor that gives msg.sender all of existing tokens.
|
||||
*/
|
||||
|
||||
constructor(string memory _name, string memory _symbol, uint256 _initialSupply) ERC20(_name, _symbol) {
|
||||
_mint(msg.sender, _initialSupply);
|
||||
}
|
||||
|
||||
/**
|
||||
* @dev Function to mint tokens
|
||||
* @param _to The address that will receive the minted tokens.
|
||||
* @param _amount The amount of tokens to mint.
|
||||
*/
|
||||
|
||||
function mint(address _to, uint256 _amount) public {
|
||||
_mint(_to, _amount);
|
||||
}
|
||||
|
||||
/**
|
||||
* @dev Function to burn tokens
|
||||
* @param _amount The amount of tokens to burn.
|
||||
*/
|
||||
|
||||
function burn(uint256 _amount) public {
|
||||
_burn(msg.sender, _amount);
|
||||
}
|
||||
}
|
||||
@@ -43,7 +43,11 @@ const func: DeployFunction = async function (hre: HardhatRuntimeEnvironment) {
|
||||
|
||||
await tryStoreAddress(network.name, contractName, bridgedTokenAddress, deployTx.hash);
|
||||
|
||||
console.log(`BridgedToken beacon deployed on ${network.name}, at address:`, bridgedTokenAddress);
|
||||
if (process.env.TOKEN_BRIDGE_L1 === "true") {
|
||||
console.log(`L1 BridgedToken beacon deployed on ${network.name}, at address:`, bridgedTokenAddress);
|
||||
} else {
|
||||
console.log(`L2 BridgedToken beacon deployed on ${network.name}, at address:`, bridgedTokenAddress);
|
||||
}
|
||||
|
||||
await tryVerifyContract(bridgedTokenAddress);
|
||||
};
|
||||
|
||||
@@ -113,8 +113,11 @@ const func: DeployFunction = async function (hre: HardhatRuntimeEnvironment) {
|
||||
|
||||
await tryStoreProxyAdminAddress(network.name, contractName, proxyAdminAddress);
|
||||
|
||||
console.log(`TokenBridge deployed on ${network.name}, at address: ${tokenBridgeAddress}`);
|
||||
|
||||
if (process.env.TOKEN_BRIDGE_L1 === "true") {
|
||||
console.log(`L1 TokenBridge deployed on ${network.name}, at address: ${tokenBridgeAddress}`);
|
||||
} else {
|
||||
console.log(`L2 TokenBridge deployed on ${network.name}, at address: ${tokenBridgeAddress}`);
|
||||
}
|
||||
await tryVerifyContract(tokenBridgeAddress);
|
||||
};
|
||||
export default func;
|
||||
|
||||
48
contracts/deploy/11_deploy_TestERC20.ts
Normal file
48
contracts/deploy/11_deploy_TestERC20.ts
Normal file
@@ -0,0 +1,48 @@
|
||||
import { ethers, network } from "hardhat";
|
||||
import { DeployFunction } from "hardhat-deploy/types";
|
||||
import { HardhatRuntimeEnvironment } from "hardhat/types";
|
||||
import {
|
||||
getRequiredEnvVar,
|
||||
tryVerifyContract,
|
||||
tryStoreAddress,
|
||||
validateDeployBranchAndTags,
|
||||
getDeployedContractAddress,
|
||||
} from "../common/helpers";
|
||||
|
||||
const func: DeployFunction = async function (hre: HardhatRuntimeEnvironment) {
|
||||
const { deployments } = hre;
|
||||
validateDeployBranchAndTags(hre.network.name);
|
||||
|
||||
const contractName = "TestERC20";
|
||||
const existingContractAddress = await getDeployedContractAddress(contractName, deployments);
|
||||
|
||||
const tokenName = getRequiredEnvVar("TEST_ERC20_NAME");
|
||||
const tokenSymbol = getRequiredEnvVar("TEST_ERC20_SYMBOL");
|
||||
const initialSupply = getRequiredEnvVar("TEST_ERC20_INITIAL_SUPPLY");
|
||||
|
||||
if (!existingContractAddress) {
|
||||
console.log(`Deploying initial version, NB: the address will be saved if env SAVE_ADDRESS=true.`);
|
||||
} else {
|
||||
console.log(`Deploying new version, NB: ${existingContractAddress} will be overwritten if env SAVE_ADDRESS=true.`);
|
||||
}
|
||||
|
||||
const TestERC20Factory = await ethers.getContractFactory(contractName);
|
||||
const contract = await TestERC20Factory.deploy(tokenName, tokenSymbol, ethers.parseEther(initialSupply));
|
||||
|
||||
await contract.waitForDeployment();
|
||||
const contractAddress = await contract.getAddress();
|
||||
|
||||
const deployTx = contract.deploymentTransaction();
|
||||
if (!deployTx) {
|
||||
throw "Deployment transaction not found.";
|
||||
}
|
||||
|
||||
await tryStoreAddress(network.name, contractName, contractAddress, deployTx.hash);
|
||||
|
||||
const chainId = (await ethers.provider.getNetwork()).chainId;
|
||||
console.log(`${contractName} deployed on ${network.name}, chainId=${chainId}, at address: ${contractAddress}`);
|
||||
await tryVerifyContract(contractAddress);
|
||||
};
|
||||
|
||||
export default func;
|
||||
func.tags = ["TestERC20"];
|
||||
@@ -0,0 +1,141 @@
|
||||
import { abi as ProxyAdminAbi, bytecode as ProxyAdminBytecode } from "./static-artifacts/ProxyAdmin.json";
|
||||
import { abi as BridgedTokenAbi, bytecode as BridgedTokenBytecode } from "./dynamic-artifacts/BridgedToken.json";
|
||||
import { abi as TokenBridgeAbi, bytecode as TokenBridgeBytecode } from "./dynamic-artifacts/TokenBridge.json";
|
||||
import {
|
||||
abi as UpgradeableBeaconAbi,
|
||||
bytecode as UpgradeableBeaconBytecode,
|
||||
} from "./static-artifacts/UpgradeableBeacon.json";
|
||||
|
||||
import {
|
||||
abi as TransparentUpgradeableProxyAbi,
|
||||
bytecode as TransparentUpgradeableProxyBytecode,
|
||||
} from "./static-artifacts/TransparentUpgradeableProxy.json";
|
||||
import { getEnvVarOrDefault, getRequiredEnvVar } from "../common/helpers/environment";
|
||||
import { generateRoleAssignments } from "../common/helpers/roles";
|
||||
import {
|
||||
TOKEN_BRIDGE_PAUSE_TYPES_ROLES,
|
||||
TOKEN_BRIDGE_ROLES,
|
||||
TOKEN_BRIDGE_UNPAUSE_TYPES_ROLES,
|
||||
} from "contracts/common/constants";
|
||||
import { ethers } from "ethers";
|
||||
import { deployContractFromArtifacts, getInitializerData } from "../common/helpers/deployments";
|
||||
|
||||
async function main() {
|
||||
const ORDERED_NONCE_POST_L2MESSAGESERVICE = 3;
|
||||
const ORDERED_NONCE_POST_LINEAROLLUP = 4;
|
||||
const bridgedTokenName = "BridgedToken";
|
||||
const tokenBridgeName = "TokenBridge";
|
||||
|
||||
const l2MessageServiceAddress = process.env.L2_MESSAGE_SERVICE_ADDRESS;
|
||||
const lineaRollupAddress = process.env.LINEA_ROLLUP_ADDRESS;
|
||||
|
||||
const remoteChainId = getRequiredEnvVar("REMOTE_CHAIN_ID");
|
||||
const tokenBridgeSecurityCouncil = getRequiredEnvVar("TOKEN_BRIDGE_SECURITY_COUNCIL");
|
||||
|
||||
const pauseTypeRoles = getEnvVarOrDefault("TOKEN_BRIDGE_PAUSE_TYPES_ROLES", TOKEN_BRIDGE_PAUSE_TYPES_ROLES);
|
||||
const unpauseTypeRoles = getEnvVarOrDefault("TOKEN_BRIDGE_UNPAUSE_TYPES_ROLES", TOKEN_BRIDGE_UNPAUSE_TYPES_ROLES);
|
||||
const defaultRoleAddresses = generateRoleAssignments(TOKEN_BRIDGE_ROLES, tokenBridgeSecurityCouncil, []);
|
||||
const roleAddresses = getEnvVarOrDefault("TOKEN_BRIDGE_ROLE_ADDRESSES", defaultRoleAddresses);
|
||||
const provider = new ethers.JsonRpcProvider(process.env.RPC_URL);
|
||||
const wallet = new ethers.Wallet(process.env.PRIVATE_KEY!, provider);
|
||||
|
||||
let walletNonce;
|
||||
|
||||
if (process.env.TOKEN_BRIDGE_L1 === "true") {
|
||||
if (process.env.L1_NONCE === undefined) {
|
||||
walletNonce = await wallet.getNonce();
|
||||
} else {
|
||||
walletNonce = parseInt(process.env.L1_NONCE) + ORDERED_NONCE_POST_LINEAROLLUP;
|
||||
}
|
||||
} else {
|
||||
if (process.env.L2_NONCE === undefined) {
|
||||
walletNonce = await wallet.getNonce();
|
||||
} else {
|
||||
walletNonce = parseInt(process.env.L2_NONCE) + ORDERED_NONCE_POST_L2MESSAGESERVICE;
|
||||
}
|
||||
}
|
||||
|
||||
const [bridgedToken, tokenBridgeImplementation, proxyAdmin] = await Promise.all([
|
||||
deployContractFromArtifacts(BridgedTokenAbi, BridgedTokenBytecode, wallet, { nonce: walletNonce }),
|
||||
deployContractFromArtifacts(TokenBridgeAbi, TokenBridgeBytecode, wallet, { nonce: walletNonce + 1 }),
|
||||
deployContractFromArtifacts(ProxyAdminAbi, ProxyAdminBytecode, wallet, { nonce: walletNonce + 2 }),
|
||||
]);
|
||||
|
||||
const bridgedTokenAddress = await bridgedToken.getAddress();
|
||||
const tokenBridgeImplementationAddress = await tokenBridgeImplementation.getAddress();
|
||||
const proxyAdminAddress = await proxyAdmin.getAddress();
|
||||
|
||||
const chainId = (await provider.getNetwork()).chainId;
|
||||
|
||||
console.log(`${bridgedTokenName} contract deployed at ${bridgedTokenAddress}`);
|
||||
console.log(`${tokenBridgeName} Implementation contract deployed at ${tokenBridgeImplementationAddress}`);
|
||||
console.log(`L1 ProxyAdmin deployed: address=${proxyAdminAddress}`);
|
||||
console.log(`Deploying UpgradeableBeacon: chainId=${chainId} bridgedTokenAddress=${bridgedTokenAddress}`);
|
||||
|
||||
const beaconProxy = await deployContractFromArtifacts(
|
||||
UpgradeableBeaconAbi,
|
||||
UpgradeableBeaconBytecode,
|
||||
wallet,
|
||||
bridgedTokenAddress,
|
||||
);
|
||||
|
||||
const beaconProxyAddress = await beaconProxy.getAddress();
|
||||
|
||||
let deployingChainMessageService = l2MessageServiceAddress;
|
||||
let reservedAddresses = process.env.L2_RESERVED_TOKEN_ADDRESSES
|
||||
? process.env.L2_RESERVED_TOKEN_ADDRESSES.split(",")
|
||||
: [];
|
||||
|
||||
if (process.env.TOKEN_BRIDGE_L1 === "true") {
|
||||
console.log(
|
||||
`TOKEN_BRIDGE_L1=${process.env.TOKEN_BRIDGE_L1}. Deploying TokenBridge on L1, using L1_RESERVED_TOKEN_ADDRESSES environment variable`,
|
||||
);
|
||||
deployingChainMessageService = lineaRollupAddress;
|
||||
reservedAddresses = process.env.L1_RESERVED_TOKEN_ADDRESSES
|
||||
? process.env.L1_RESERVED_TOKEN_ADDRESSES.split(",")
|
||||
: [];
|
||||
} else {
|
||||
console.log(
|
||||
`TOKEN_BRIDGE_L1=${process.env.TOKEN_BRIDGE_L1}. Deploying TokenBridge on L2, using L2_RESERVED_TOKEN_ADDRESSES environment variable`,
|
||||
);
|
||||
}
|
||||
|
||||
const initializer = getInitializerData(TokenBridgeAbi, "initialize", [
|
||||
{
|
||||
defaultAdmin: tokenBridgeSecurityCouncil,
|
||||
messageService: deployingChainMessageService,
|
||||
tokenBeacon: beaconProxyAddress,
|
||||
sourceChainId: chainId,
|
||||
targetChainId: remoteChainId,
|
||||
reservedTokens: reservedAddresses,
|
||||
roleAddresses,
|
||||
pauseTypeRoles,
|
||||
unpauseTypeRoles,
|
||||
},
|
||||
]);
|
||||
|
||||
const proxyContract = await deployContractFromArtifacts(
|
||||
TransparentUpgradeableProxyAbi,
|
||||
TransparentUpgradeableProxyBytecode,
|
||||
wallet,
|
||||
tokenBridgeImplementationAddress,
|
||||
proxyAdminAddress,
|
||||
initializer,
|
||||
);
|
||||
|
||||
const proxyContractAddress = await proxyContract.getAddress();
|
||||
const txReceipt = await proxyContract.deploymentTransaction()?.wait();
|
||||
|
||||
if (!txReceipt) {
|
||||
throw "Contract deployment transaction receipt not found.";
|
||||
}
|
||||
|
||||
console.log(
|
||||
`${tokenBridgeName} deployed: chainId=${chainId} address=${proxyContractAddress} blockNumber=${txReceipt.blockNumber}`,
|
||||
);
|
||||
}
|
||||
|
||||
main().catch((error) => {
|
||||
console.error(error);
|
||||
process.exitCode = 1;
|
||||
});
|
||||
@@ -30,7 +30,13 @@ async function main() {
|
||||
|
||||
const provider = new ethers.JsonRpcProvider(process.env.RPC_URL);
|
||||
const wallet = new ethers.Wallet(process.env.PRIVATE_KEY!, provider);
|
||||
const walletNonce = await wallet.getNonce();
|
||||
let walletNonce;
|
||||
|
||||
if (!process.env.L2_NONCE) {
|
||||
walletNonce = await wallet.getNonce();
|
||||
} else {
|
||||
walletNonce = parseInt(process.env.L2_NONCE);
|
||||
}
|
||||
|
||||
const [l2MessageServiceImplementation, proxyAdmin] = await Promise.all([
|
||||
deployContractFromArtifacts(L2MessageServiceAbi, L2MessageServiceBytecode, wallet, { nonce: walletNonce }),
|
||||
|
||||
@@ -52,7 +52,15 @@ async function main() {
|
||||
const provider = new ethers.JsonRpcProvider(process.env.RPC_URL);
|
||||
const wallet = new ethers.Wallet(process.env.PRIVATE_KEY!, provider);
|
||||
|
||||
const [walletNonce, { gasPrice }] = await Promise.all([wallet.getNonce(), get1559Fees(provider)]);
|
||||
const { gasPrice } = await get1559Fees(provider);
|
||||
|
||||
let walletNonce;
|
||||
|
||||
if (!process.env.L1_NONCE) {
|
||||
walletNonce = await wallet.getNonce();
|
||||
} else {
|
||||
walletNonce = parseInt(process.env.L1_NONCE);
|
||||
}
|
||||
|
||||
const [verifier, lineaRollupImplementation, proxyAdmin] = await Promise.all([
|
||||
deployContractFromArtifacts(verifierArtifacts.abi, verifierArtifacts.bytecode, wallet, {
|
||||
|
||||
@@ -65,9 +65,18 @@ async function main() {
|
||||
const verifierArtifacts = findContractArtifacts(path.join(__dirname, "./dynamic-artifacts"), verifierName);
|
||||
|
||||
const provider = new ethers.JsonRpcProvider(process.env.RPC_URL);
|
||||
|
||||
const wallet = new ethers.Wallet(process.env.PRIVATE_KEY!, provider);
|
||||
|
||||
const [walletNonce, { gasPrice }] = await Promise.all([wallet.getNonce(), get1559Fees(provider)]);
|
||||
const { gasPrice } = await get1559Fees(provider);
|
||||
|
||||
let walletNonce;
|
||||
|
||||
if (!process.env.L1_NONCE) {
|
||||
walletNonce = await wallet.getNonce();
|
||||
} else {
|
||||
walletNonce = parseInt(process.env.L1_NONCE);
|
||||
}
|
||||
|
||||
const [verifier, lineaRollupImplementation, proxyAdmin] = await Promise.all([
|
||||
deployContractFromArtifacts(verifierArtifacts.abi, verifierArtifacts.bytecode, wallet, {
|
||||
|
||||
61
contracts/local-deployments-artifacts/deployTestERC20.ts
Normal file
61
contracts/local-deployments-artifacts/deployTestERC20.ts
Normal file
@@ -0,0 +1,61 @@
|
||||
import { ethers } from "ethers";
|
||||
import { abi as TestERC20Abi, bytecode as TestERC20Bytecode } from "./static-artifacts/TestERC20.json";
|
||||
import { deployContractFromArtifacts } from "../common/helpers/deployments";
|
||||
import { get1559Fees } from "../scripts/utils";
|
||||
import { getRequiredEnvVar } from "../common/helpers/environment";
|
||||
|
||||
async function main() {
|
||||
const ORDERED_NONCE_POST_LINEAROLLUP = 4;
|
||||
const ORDERED_NONCE_POST_TOKENBRIDGE = 5;
|
||||
const ORDERED_NONCE_POST_L2MESSAGESERVICE = 3;
|
||||
|
||||
const provider = new ethers.JsonRpcProvider(process.env.RPC_URL);
|
||||
const wallet = new ethers.Wallet(process.env.PRIVATE_KEY!, provider);
|
||||
|
||||
const erc20Name = getRequiredEnvVar("TEST_ERC20_NAME");
|
||||
const erc20Symbol = getRequiredEnvVar("TEST_ERC20_SYMBOL");
|
||||
const erc20Supply = getRequiredEnvVar("TEST_ERC20_INITIAL_SUPPLY");
|
||||
|
||||
const { gasPrice } = await get1559Fees(provider);
|
||||
|
||||
let walletNonce;
|
||||
|
||||
if (process.env.TEST_ERC20_L1 === "true") {
|
||||
if (!process.env.L1_NONCE) {
|
||||
walletNonce = await wallet.getNonce();
|
||||
} else {
|
||||
walletNonce = parseInt(process.env.L1_NONCE) + ORDERED_NONCE_POST_LINEAROLLUP + ORDERED_NONCE_POST_TOKENBRIDGE;
|
||||
}
|
||||
} else {
|
||||
if (!process.env.L2_NONCE) {
|
||||
walletNonce = await wallet.getNonce();
|
||||
} else {
|
||||
walletNonce =
|
||||
parseInt(process.env.L2_NONCE) + ORDERED_NONCE_POST_L2MESSAGESERVICE + ORDERED_NONCE_POST_TOKENBRIDGE;
|
||||
}
|
||||
}
|
||||
|
||||
const testERC20 = await deployContractFromArtifacts(
|
||||
TestERC20Abi,
|
||||
TestERC20Bytecode,
|
||||
wallet,
|
||||
erc20Name,
|
||||
erc20Symbol,
|
||||
erc20Supply,
|
||||
{
|
||||
nonce: walletNonce,
|
||||
gasPrice,
|
||||
},
|
||||
);
|
||||
|
||||
const testERC20Address = await testERC20.getAddress();
|
||||
|
||||
const chainId = (await provider.getNetwork()).chainId;
|
||||
|
||||
console.log(`testERC20 deployed: address=${testERC20Address} chainId=${chainId}`);
|
||||
}
|
||||
|
||||
main().catch((error) => {
|
||||
console.error(error);
|
||||
process.exit(1);
|
||||
});
|
||||
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
33
contracts/local-deployments-artifacts/get-wallet-nonce.ts
Normal file
33
contracts/local-deployments-artifacts/get-wallet-nonce.ts
Normal file
@@ -0,0 +1,33 @@
|
||||
import { JsonRpcProvider, Wallet } from "ethers";
|
||||
import yargs from "yargs";
|
||||
import { hideBin } from "yargs/helpers";
|
||||
import { sanitizePrivKey } from "../scripts/cli";
|
||||
|
||||
const argv = yargs(hideBin(process.argv))
|
||||
.option("rpc-url", {
|
||||
describe: "RPC url",
|
||||
type: "string",
|
||||
demandOption: true,
|
||||
})
|
||||
.option("wallet-priv-key", {
|
||||
describe: "deployer private key",
|
||||
type: "string",
|
||||
demandOption: true,
|
||||
coerce: sanitizePrivKey("priv-key"),
|
||||
})
|
||||
.parseSync();
|
||||
|
||||
async function main(args: typeof argv) {
|
||||
const provider = new JsonRpcProvider(args.rpcUrl);
|
||||
const wallet = new Wallet(args.walletPrivKey, provider);
|
||||
|
||||
const nonce = await wallet.getNonce();
|
||||
|
||||
process.env.L1_NONCE = nonce.toString();
|
||||
process.stdout.write(process.env.L1_NONCE);
|
||||
}
|
||||
|
||||
main(argv).catch((error) => {
|
||||
console.error(error);
|
||||
process.exit(1);
|
||||
});
|
||||
File diff suppressed because one or more lines are too long
@@ -0,0 +1,113 @@
|
||||
{
|
||||
"_format": "hh-sol-artifact-1",
|
||||
"contractName": "UpgradeableBeacon",
|
||||
"sourceName": "@openzeppelin/contracts/proxy/beacon/UpgradeableBeacon.sol",
|
||||
"abi": [
|
||||
{
|
||||
"inputs": [
|
||||
{
|
||||
"internalType": "address",
|
||||
"name": "implementation_",
|
||||
"type": "address"
|
||||
}
|
||||
],
|
||||
"stateMutability": "nonpayable",
|
||||
"type": "constructor"
|
||||
},
|
||||
{
|
||||
"anonymous": false,
|
||||
"inputs": [
|
||||
{
|
||||
"indexed": true,
|
||||
"internalType": "address",
|
||||
"name": "previousOwner",
|
||||
"type": "address"
|
||||
},
|
||||
{
|
||||
"indexed": true,
|
||||
"internalType": "address",
|
||||
"name": "newOwner",
|
||||
"type": "address"
|
||||
}
|
||||
],
|
||||
"name": "OwnershipTransferred",
|
||||
"type": "event"
|
||||
},
|
||||
{
|
||||
"anonymous": false,
|
||||
"inputs": [
|
||||
{
|
||||
"indexed": true,
|
||||
"internalType": "address",
|
||||
"name": "implementation",
|
||||
"type": "address"
|
||||
}
|
||||
],
|
||||
"name": "Upgraded",
|
||||
"type": "event"
|
||||
},
|
||||
{
|
||||
"inputs": [],
|
||||
"name": "implementation",
|
||||
"outputs": [
|
||||
{
|
||||
"internalType": "address",
|
||||
"name": "",
|
||||
"type": "address"
|
||||
}
|
||||
],
|
||||
"stateMutability": "view",
|
||||
"type": "function"
|
||||
},
|
||||
{
|
||||
"inputs": [],
|
||||
"name": "owner",
|
||||
"outputs": [
|
||||
{
|
||||
"internalType": "address",
|
||||
"name": "",
|
||||
"type": "address"
|
||||
}
|
||||
],
|
||||
"stateMutability": "view",
|
||||
"type": "function"
|
||||
},
|
||||
{
|
||||
"inputs": [],
|
||||
"name": "renounceOwnership",
|
||||
"outputs": [],
|
||||
"stateMutability": "nonpayable",
|
||||
"type": "function"
|
||||
},
|
||||
{
|
||||
"inputs": [
|
||||
{
|
||||
"internalType": "address",
|
||||
"name": "newOwner",
|
||||
"type": "address"
|
||||
}
|
||||
],
|
||||
"name": "transferOwnership",
|
||||
"outputs": [],
|
||||
"stateMutability": "nonpayable",
|
||||
"type": "function"
|
||||
},
|
||||
{
|
||||
"inputs": [
|
||||
{
|
||||
"internalType": "address",
|
||||
"name": "newImplementation",
|
||||
"type": "address"
|
||||
}
|
||||
],
|
||||
"name": "upgradeTo",
|
||||
"outputs": [],
|
||||
"stateMutability": "nonpayable",
|
||||
"type": "function"
|
||||
}
|
||||
],
|
||||
"bytecode": "0x608060405234801561001057600080fd5b506040516104e43803806104e483398101604081905261002f91610151565b61003833610047565b61004181610097565b50610181565b600080546001600160a01b038381166001600160a01b0319831681178455604051919092169283917f8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e09190a35050565b6100aa8161014260201b6101a01760201c565b6101205760405162461bcd60e51b815260206004820152603360248201527f5570677261646561626c65426561636f6e3a20696d706c656d656e746174696f60448201527f6e206973206e6f74206120636f6e747261637400000000000000000000000000606482015260840160405180910390fd5b600180546001600160a01b0319166001600160a01b0392909216919091179055565b6001600160a01b03163b151590565b60006020828403121561016357600080fd5b81516001600160a01b038116811461017a57600080fd5b9392505050565b610354806101906000396000f3fe608060405234801561001057600080fd5b50600436106100575760003560e01c80633659cfe61461005c5780635c60da1b14610071578063715018a61461009a5780638da5cb5b146100a2578063f2fde38b146100b3575b600080fd5b61006f61006a3660046102ee565b6100c6565b005b6001546001600160a01b03165b6040516001600160a01b03909116815260200160405180910390f35b61006f61010e565b6000546001600160a01b031661007e565b61006f6100c13660046102ee565b610122565b6100ce6101af565b6100d781610209565b6040516001600160a01b038216907fbc7cd75a20ee27fd9adebab32041f755214dbc6bffa90cc0225b39da2e5c2d3b90600090a250565b6101166101af565b610120600061029e565b565b61012a6101af565b6001600160a01b0381166101945760405162461bcd60e51b815260206004820152602660248201527f4f776e61626c653a206e6577206f776e657220697320746865207a65726f206160448201526564647265737360d01b60648201526084015b60405180910390fd5b61019d8161029e565b50565b6001600160a01b03163b151590565b6000546001600160a01b031633146101205760405162461bcd60e51b815260206004820181905260248201527f4f776e61626c653a2063616c6c6572206973206e6f7420746865206f776e6572604482015260640161018b565b6001600160a01b0381163b61027c5760405162461bcd60e51b815260206004820152603360248201527f5570677261646561626c65426561636f6e3a20696d706c656d656e746174696f6044820152721b881a5cc81b9bdd08184818dbdb9d1c9858dd606a1b606482015260840161018b565b600180546001600160a01b0319166001600160a01b0392909216919091179055565b600080546001600160a01b038381166001600160a01b0319831681178455604051919092169283917f8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e09190a35050565b60006020828403121561030057600080fd5b81356001600160a01b038116811461031757600080fd5b939250505056fea264697066735822122037f5e761a584b5cfedfedcacdd35de142872479851fbaecf66f55a175ffa1d1d64736f6c63430008090033",
|
||||
"deployedBytecode": "0x608060405234801561001057600080fd5b50600436106100575760003560e01c80633659cfe61461005c5780635c60da1b14610071578063715018a61461009a5780638da5cb5b146100a2578063f2fde38b146100b3575b600080fd5b61006f61006a3660046102ee565b6100c6565b005b6001546001600160a01b03165b6040516001600160a01b03909116815260200160405180910390f35b61006f61010e565b6000546001600160a01b031661007e565b61006f6100c13660046102ee565b610122565b6100ce6101af565b6100d781610209565b6040516001600160a01b038216907fbc7cd75a20ee27fd9adebab32041f755214dbc6bffa90cc0225b39da2e5c2d3b90600090a250565b6101166101af565b610120600061029e565b565b61012a6101af565b6001600160a01b0381166101945760405162461bcd60e51b815260206004820152602660248201527f4f776e61626c653a206e6577206f776e657220697320746865207a65726f206160448201526564647265737360d01b60648201526084015b60405180910390fd5b61019d8161029e565b50565b6001600160a01b03163b151590565b6000546001600160a01b031633146101205760405162461bcd60e51b815260206004820181905260248201527f4f776e61626c653a2063616c6c6572206973206e6f7420746865206f776e6572604482015260640161018b565b6001600160a01b0381163b61027c5760405162461bcd60e51b815260206004820152603360248201527f5570677261646561626c65426561636f6e3a20696d706c656d656e746174696f6044820152721b881a5cc81b9bdd08184818dbdb9d1c9858dd606a1b606482015260840161018b565b600180546001600160a01b0319166001600160a01b0392909216919091179055565b600080546001600160a01b038381166001600160a01b0319831681178455604051919092169283917f8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e09190a35050565b60006020828403121561030057600080fd5b81356001600160a01b038116811461031757600080fd5b939250505056fea264697066735822122037f5e761a584b5cfedfedcacdd35de142872479851fbaecf66f55a175ffa1d1d64736f6c63430008090033",
|
||||
"linkReferences": {},
|
||||
"deployedLinkReferences": {}
|
||||
}
|
||||
@@ -48,7 +48,7 @@
|
||||
"f17f52151EbEF6C7334FAD080c5704D77216b732": {
|
||||
"@WARNING": "FOR LOCAL DEV ONLY - DO NOT REUSE THESE KEYS ELSEWHERE",
|
||||
"privateKey": "ae6ae8e5ccbfb04590405997ee2d52d2b330726137b875053c36d94e974d162f",
|
||||
"comment": "account 3, can be used as end user",
|
||||
"comment": "L2 Security Council",
|
||||
"balance": "90000000000000000000003"
|
||||
},
|
||||
"6d26dcc30a1693043aefa35ed9171c16da53f275": {
|
||||
|
||||
211
e2e/src/bridge-tokens.spec.ts
Normal file
211
e2e/src/bridge-tokens.spec.ts
Normal file
@@ -0,0 +1,211 @@
|
||||
import { ethers } from "ethers";
|
||||
import { describe, expect, it } from "@jest/globals";
|
||||
import { config } from "./config/tests-config";
|
||||
import { waitForEvents, etherToWei } from "./common/utils";
|
||||
import { MESSAGE_SENT_EVENT_SIGNATURE } from "./common/constants";
|
||||
|
||||
const l1AccountManager = config.getL1AccountManager();
|
||||
const l2AccountManager = config.getL2AccountManager();
|
||||
const bridgeAmount = ethers.parseEther("100");
|
||||
const messageSentEventMessageNumberIndex = 4;
|
||||
const messageSentEventMessageHashIndex = 6;
|
||||
|
||||
describe("Bridge ERC20 Tokens L1 -> L2 and L2 -> L1", () => {
|
||||
it.concurrent("Bridge a token from L1 to L2", async () => {
|
||||
const [l1Account, l2Account] = await Promise.all([
|
||||
l1AccountManager.generateAccount(),
|
||||
l2AccountManager.generateAccount(),
|
||||
]);
|
||||
|
||||
const lineaRollup = config.getLineaRollupContract();
|
||||
const l2MessageService = config.getL2MessageServiceContract();
|
||||
const l1TokenBridge = config.getL1TokenBridgeContract();
|
||||
const l2TokenBridge = config.getL2TokenBridgeContract();
|
||||
const l1Token = config.getL1TokenContract();
|
||||
const l1Provider = config.getL1Provider();
|
||||
|
||||
console.log("Minting ERC20 tokens to L1 Account");
|
||||
|
||||
let { maxPriorityFeePerGas: l1MaxPriorityFeePerGas, maxFeePerGas: l1MaxFeePerGas } = await l1Provider.getFeeData();
|
||||
let nonce = await l1Provider.getTransactionCount(l1Account.address, "pending");
|
||||
|
||||
console.log("Minting and approving tokens to L1 TokenBridge");
|
||||
|
||||
await Promise.all([
|
||||
(
|
||||
await l1Token.connect(l1Account).mint(l1Account.address, bridgeAmount, {
|
||||
nonce: nonce,
|
||||
maxPriorityFeePerGas: l1MaxPriorityFeePerGas,
|
||||
maxFeePerGas: l1MaxFeePerGas,
|
||||
})
|
||||
).wait(),
|
||||
(
|
||||
await l1Token.connect(l1Account).approve(l1TokenBridge.getAddress(), bridgeAmount, {
|
||||
maxPriorityFeePerGas: l1MaxPriorityFeePerGas,
|
||||
maxFeePerGas: l1MaxFeePerGas,
|
||||
nonce: nonce + 1,
|
||||
})
|
||||
).wait(),
|
||||
]);
|
||||
|
||||
const l1TokenBridgeAddress = await l1TokenBridge.getAddress();
|
||||
const l1TokenAddress = await l1Token.getAddress();
|
||||
|
||||
const allowanceL1Account = await l1Token.allowance(l1Account.address, l1TokenBridgeAddress);
|
||||
console.log("Current allowance of L1 account to L1 TokenBridge is :", allowanceL1Account.toString());
|
||||
|
||||
console.log("Calling the bridgeToken function on the L1 TokenBridge contract");
|
||||
|
||||
({ maxPriorityFeePerGas: l1MaxPriorityFeePerGas, maxFeePerGas: l1MaxFeePerGas } = await l1Provider.getFeeData());
|
||||
nonce = await l1Provider.getTransactionCount(l1Account.address, "pending");
|
||||
|
||||
const bridgeTokenTx = await l1TokenBridge
|
||||
.connect(l1Account)
|
||||
.bridgeToken(l1TokenAddress, bridgeAmount, l2Account.address, {
|
||||
value: etherToWei("0.01"),
|
||||
maxPriorityFeePerGas: l1MaxPriorityFeePerGas,
|
||||
maxFeePerGas: l1MaxFeePerGas,
|
||||
nonce: nonce,
|
||||
});
|
||||
|
||||
const bridgedTxReceipt = await bridgeTokenTx.wait();
|
||||
|
||||
const sentEventLog = bridgedTxReceipt?.logs.find((log) => log.topics[0] == MESSAGE_SENT_EVENT_SIGNATURE);
|
||||
|
||||
const messageSentEvent = lineaRollup.interface.decodeEventLog(
|
||||
"MessageSent",
|
||||
sentEventLog!.data,
|
||||
sentEventLog!.topics,
|
||||
);
|
||||
|
||||
const l1TokenBalance = await l1Token.balanceOf(l1Account.address);
|
||||
console.log("Token balance of L1 account :", l1TokenBalance.toString());
|
||||
|
||||
expect(l1TokenBalance).toEqual(0n);
|
||||
|
||||
console.log("Waiting for MessageSent event on L1.");
|
||||
|
||||
const messageNumber = messageSentEvent[messageSentEventMessageNumberIndex];
|
||||
const messageHash = messageSentEvent[messageSentEventMessageHashIndex];
|
||||
|
||||
console.log(`Message sent on L1 : messageHash=${messageHash}`);
|
||||
|
||||
console.log("Waiting for anchoring...");
|
||||
|
||||
const [rollingHashUpdatedEvent] = await waitForEvents(
|
||||
l2MessageService,
|
||||
l2MessageService.filters.RollingHashUpdated(),
|
||||
1_000,
|
||||
0,
|
||||
"latest",
|
||||
async (events) => events.filter((event) => event.args.messageNumber >= messageNumber),
|
||||
);
|
||||
expect(rollingHashUpdatedEvent).not.toBeNull();
|
||||
|
||||
const anchoredStatus = await l2MessageService.inboxL1L2MessageStatus(messageHash);
|
||||
|
||||
expect(anchoredStatus).toBeGreaterThan(0);
|
||||
|
||||
console.log(`Message anchored : ${JSON.stringify(rollingHashUpdatedEvent)}`);
|
||||
|
||||
console.log("Waiting for MessageClaimed event on L2...");
|
||||
|
||||
const [claimedEvent] = await waitForEvents(l2MessageService, l2MessageService.filters.MessageClaimed(messageHash));
|
||||
expect(claimedEvent).not.toBeNull();
|
||||
|
||||
const [newTokenDeployed] = await waitForEvents(l2TokenBridge, l2TokenBridge.filters.NewTokenDeployed());
|
||||
expect(newTokenDeployed).not.toBeNull();
|
||||
|
||||
console.log(`Message claimed on L2 : ${JSON.stringify(claimedEvent)}.`);
|
||||
|
||||
const l2Token = config.getL2BridgedTokenContract(newTokenDeployed.args.bridgedToken);
|
||||
|
||||
console.log("Verify the token balance on L2");
|
||||
|
||||
const l2TokenBalance = await l2Token.balanceOf(l2Account.address);
|
||||
console.log("Token balance of L2 account :", l2TokenBalance.toString());
|
||||
|
||||
expect(l2TokenBalance).toEqual(bridgeAmount);
|
||||
});
|
||||
|
||||
it.concurrent("Bridge a token from L2 to L1", async () => {
|
||||
const [l1Account, l2Account] = await Promise.all([
|
||||
l1AccountManager.generateAccount(),
|
||||
l2AccountManager.generateAccount(),
|
||||
]);
|
||||
|
||||
const lineaRollup = config.getLineaRollupContract();
|
||||
const l2MessageService = config.getL2MessageServiceContract();
|
||||
const l1TokenBridge = config.getL1TokenBridgeContract();
|
||||
const l2TokenBridge = config.getL2TokenBridgeContract();
|
||||
const l2Token = config.getL2TokenContract();
|
||||
const l2Provider = config.getL2Provider();
|
||||
|
||||
const { maxPriorityFeePerGas: l2MaxPriorityFeePerGas, maxFeePerGas: l2MaxFeePerGas } =
|
||||
await l2Provider.getFeeData();
|
||||
let nonce = await l2Provider.getTransactionCount(l2Account.address, "pending");
|
||||
|
||||
await Promise.all([
|
||||
(
|
||||
await l2Token.connect(l2Account).mint(l2Account.address, bridgeAmount, {
|
||||
nonce: nonce,
|
||||
maxPriorityFeePerGas: l2MaxPriorityFeePerGas,
|
||||
maxFeePerGas: l2MaxFeePerGas,
|
||||
})
|
||||
).wait(),
|
||||
(
|
||||
await l2Token.connect(l2Account).approve(l2TokenBridge.getAddress(), ethers.parseEther("100"), {
|
||||
maxPriorityFeePerGas: l2MaxPriorityFeePerGas,
|
||||
maxFeePerGas: l2MaxFeePerGas,
|
||||
nonce: nonce + 1,
|
||||
})
|
||||
).wait(),
|
||||
]);
|
||||
|
||||
const allowanceL2Account = await l2Token.allowance(l2Account.address, l2TokenBridge.getAddress());
|
||||
console.log("Current allowance of L2 account to L2 TokenBridge is :", allowanceL2Account.toString());
|
||||
console.log("Current balance of L2 account is :", await l2Token.balanceOf(l2Account));
|
||||
|
||||
console.log("Calling the bridgeToken function on the L2 TokenBridge contract");
|
||||
|
||||
nonce = await l2Provider.getTransactionCount(l2Account.address, "pending");
|
||||
|
||||
const bridgeTokenTx = await l2TokenBridge
|
||||
.connect(l2Account)
|
||||
.bridgeToken(await l2Token.getAddress(), bridgeAmount, l1Account.address, {
|
||||
value: etherToWei("0.01"),
|
||||
maxPriorityFeePerGas: l2MaxPriorityFeePerGas,
|
||||
maxFeePerGas: l2MaxFeePerGas,
|
||||
nonce: nonce,
|
||||
});
|
||||
|
||||
const receipt = await bridgeTokenTx.wait();
|
||||
const sentEventLog = receipt?.logs.find((log) => log.topics[0] == MESSAGE_SENT_EVENT_SIGNATURE);
|
||||
|
||||
const messageSentEvent = l2MessageService.interface.decodeEventLog(
|
||||
"MessageSent",
|
||||
sentEventLog!.data,
|
||||
sentEventLog!.topics,
|
||||
);
|
||||
const messageHash = messageSentEvent[messageSentEventMessageHashIndex];
|
||||
|
||||
console.log("Waiting for L1 MessageClaimed event.");
|
||||
|
||||
const [claimedEvent] = await waitForEvents(lineaRollup, lineaRollup.filters.MessageClaimed(messageHash));
|
||||
expect(claimedEvent).not.toBeNull();
|
||||
|
||||
console.log(`Message claimed on L1 : ${JSON.stringify(claimedEvent)}`);
|
||||
|
||||
const [newTokenDeployed] = await waitForEvents(l1TokenBridge, l1TokenBridge.filters.NewTokenDeployed());
|
||||
expect(newTokenDeployed).not.toBeNull();
|
||||
|
||||
const l1BridgedToken = config.getL1BridgedTokenContract(newTokenDeployed.args.bridgedToken);
|
||||
|
||||
console.log("Verify the token balance on L1");
|
||||
|
||||
const l1BridgedTokenBalance = await l1BridgedToken.balanceOf(l1Account.address);
|
||||
console.log("Token balance of L1 account :", l1BridgedTokenBalance.toString());
|
||||
|
||||
expect(l1BridgedTokenBalance).toEqual(bridgeAmount);
|
||||
});
|
||||
});
|
||||
@@ -3,7 +3,7 @@ import assert from "assert";
|
||||
import { AbstractSigner, BaseContract, BlockTag, TransactionReceipt, TransactionRequest, Wallet, ethers } from "ethers";
|
||||
import path from "path";
|
||||
import { exec } from "child_process";
|
||||
import { L2MessageService, LineaRollupV5 } from "../typechain";
|
||||
import { L2MessageService, TokenBridge, LineaRollupV5 } from "../typechain";
|
||||
import { PayableOverrides, TypedContractEvent, TypedDeferredTopicFilter, TypedEventLog } from "../typechain/common";
|
||||
import { MessageEvent, SendMessageArgs } from "./types";
|
||||
|
||||
@@ -147,7 +147,10 @@ export async function getBlockByNumberOrBlockTag(rpcUrl: URL, blockTag: BlockTag
|
||||
}
|
||||
}
|
||||
|
||||
export async function getEvents<TContract extends LineaRollupV5 | L2MessageService, TEvent extends TypedContractEvent>(
|
||||
export async function getEvents<
|
||||
TContract extends LineaRollupV5 | L2MessageService | TokenBridge,
|
||||
TEvent extends TypedContractEvent,
|
||||
>(
|
||||
contract: TContract,
|
||||
eventFilter: TypedDeferredTopicFilter<TEvent>,
|
||||
fromBlock?: BlockTag,
|
||||
@@ -168,7 +171,7 @@ export async function getEvents<TContract extends LineaRollupV5 | L2MessageServi
|
||||
}
|
||||
|
||||
export async function waitForEvents<
|
||||
TContract extends LineaRollupV5 | L2MessageService,
|
||||
TContract extends LineaRollupV5 | L2MessageService | TokenBridge,
|
||||
TEvent extends TypedContractEvent,
|
||||
>(
|
||||
contract: TContract,
|
||||
|
||||
@@ -9,10 +9,18 @@ declare global {
|
||||
}
|
||||
|
||||
export default async (): Promise<void> => {
|
||||
const l1JsonRpcProvider = config.getL1Provider();
|
||||
const l1AccountManager = config.getL1AccountManager();
|
||||
const l2AccountManager = config.getL2AccountManager();
|
||||
|
||||
const account = config.getL1AccountManager().whaleAccount(0);
|
||||
const l2Account = config.getL2AccountManager().whaleAccount(0);
|
||||
const lineaRollup = config.getLineaRollupContract(account);
|
||||
const l1JsonRpcProvider = config.getL1Provider();
|
||||
|
||||
const l1TokenBridge = config.getL1TokenBridgeContract();
|
||||
const l2TokenBridge = config.getL2TokenBridgeContract();
|
||||
const l1SecurityCouncil = l1AccountManager.whaleAccount(3);
|
||||
const l2SecurityCouncil = l2AccountManager.whaleAccount(3);
|
||||
|
||||
const [l1AccountNonce, l2AccountNonce, { maxPriorityFeePerGas, maxFeePerGas }] = await Promise.all([
|
||||
account.getNonce(),
|
||||
@@ -37,6 +45,8 @@ export default async (): Promise<void> => {
|
||||
nonce: l1AccountNonce + 1,
|
||||
})
|
||||
).wait(),
|
||||
(await l1TokenBridge.connect(l1SecurityCouncil).setRemoteTokenBridge(await l2TokenBridge.getAddress())).wait(),
|
||||
(await l2TokenBridge.connect(l2SecurityCouncil).setRemoteTokenBridge(await l1TokenBridge.getAddress())).wait(),
|
||||
]);
|
||||
|
||||
console.log(`L1 Dummy contract deployed at address: ${await dummyContract.getAddress()}`);
|
||||
|
||||
@@ -26,6 +26,8 @@ const config: Config = {
|
||||
rpcUrl: L1_RPC_URL,
|
||||
chainId: L1_CHAIN_ID,
|
||||
lineaRollupAddress: "0x2A5CDCfc38856e2590E9Bd32F54Fa348e5De5f48",
|
||||
tokenBridgeAddress: "",
|
||||
l1TokenAddress: "",
|
||||
accountManager: new EnvironmentBasedAccountManager(
|
||||
new ethers.JsonRpcProvider(L1_RPC_URL.toString()),
|
||||
L1_WHALE_ACCOUNTS,
|
||||
@@ -37,6 +39,8 @@ const config: Config = {
|
||||
rpcUrl: L2_RPC_URL,
|
||||
chainId: L2_CHAIN_ID,
|
||||
l2MessageServiceAddress: "0x33bf916373159A8c1b54b025202517BfDbB7863D",
|
||||
tokenBridgeAddress: "",
|
||||
l2TokenAddress: "",
|
||||
l2TestContractAddress: "",
|
||||
accountManager: new EnvironmentBasedAccountManager(
|
||||
new ethers.JsonRpcProvider(L2_RPC_URL.toString()),
|
||||
|
||||
@@ -16,7 +16,9 @@ const config: Config = {
|
||||
rpcUrl: L1_RPC_URL,
|
||||
chainId: 31648428,
|
||||
lineaRollupAddress: "0xCf7Ed3AccA5a467e9e704C703E8D87F634fB0Fc9",
|
||||
dummyContractAddress: "0xDc64a140Aa3E981100a9becA4E685f962f0cF6C9",
|
||||
dummyContractAddress: "0x610178dA211FEF7D417bC0e6FeD39F05609AD788",
|
||||
tokenBridgeAddress: "0x2279B7A0a67DB372996a5FaB50D91eAA73d2eBe6",
|
||||
l1TokenAddress: "0x8A791620dd6260079BF849Dc5567aDC3F2FdC318",
|
||||
accountManager: new GenesisBasedAccountManager(
|
||||
new ethers.JsonRpcProvider(L1_RPC_URL.toString()),
|
||||
path.resolve(
|
||||
@@ -30,8 +32,10 @@ const config: Config = {
|
||||
besuNodeRpcUrl: L2_BESU_NODE_RPC_URL,
|
||||
chainId: 1337,
|
||||
l2MessageServiceAddress: "0xe537D669CA013d86EBeF1D64e40fC74CADC91987",
|
||||
l2TestContractAddress: "0xeB0b0a14F92e3BA35aEF3a2B6A24D7ED1D11631B",
|
||||
dummyContractAddress: "0x2f6dAaF8A81AB675fbD37Ca6Ed5b72cf86237453",
|
||||
l2TestContractAddress: "0x997FC3aF1F193Cbdc013060076c67A13e218980e",
|
||||
dummyContractAddress: "0xE4392c8ecC46b304C83cDB5edaf742899b1bda93",
|
||||
tokenBridgeAddress: "0x5C95Bcd50E6D1B4E3CDC478484C9030Ff0a7D493",
|
||||
l2TokenAddress: "0xCC1B08B17301e090cbb4c1F5598Cbaa096d591FB",
|
||||
accountManager: new GenesisBasedAccountManager(
|
||||
new ethers.JsonRpcProvider(L2_RPC_URL.toString()),
|
||||
path.resolve(
|
||||
|
||||
@@ -25,6 +25,8 @@ const config: Config = {
|
||||
rpcUrl: L1_RPC_URL,
|
||||
chainId: L1_CHAIN_ID,
|
||||
lineaRollupAddress: "0xB218f8A4Bc926cF1cA7b3423c154a0D627Bdb7E5",
|
||||
tokenBridgeAddress: "0x5A0a48389BB0f12E5e017116c1105da97E129142",
|
||||
l1TokenAddress: "",
|
||||
accountManager: new EnvironmentBasedAccountManager(
|
||||
new ethers.JsonRpcProvider(L1_RPC_URL.toString()),
|
||||
L1_WHALE_ACCOUNTS,
|
||||
@@ -36,6 +38,8 @@ const config: Config = {
|
||||
rpcUrl: L2_RPC_URL,
|
||||
chainId: L2_CHAIN_ID,
|
||||
l2MessageServiceAddress: "0x971e727e956690b9957be6d51Ec16E73AcAC83A7",
|
||||
tokenBridgeAddress: "0x93DcAdf238932e6e6a85852caC89cBd71798F463",
|
||||
l2TokenAddress: "",
|
||||
l2TestContractAddress: "",
|
||||
accountManager: new EnvironmentBasedAccountManager(
|
||||
new ethers.JsonRpcProvider(L2_RPC_URL.toString()),
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
import { AbstractSigner, JsonRpcProvider, Wallet } from "ethers";
|
||||
import { Config, L2Config, LocalL2Config } from "./types";
|
||||
import {
|
||||
BridgedToken,
|
||||
BridgedToken__factory,
|
||||
DummyContract,
|
||||
DummyContract__factory,
|
||||
L2MessageService,
|
||||
@@ -9,6 +11,10 @@ import {
|
||||
LineaRollupV5__factory,
|
||||
TestContract,
|
||||
TestContract__factory,
|
||||
TestERC20,
|
||||
TestERC20__factory,
|
||||
TokenBridge,
|
||||
TokenBridge__factory,
|
||||
} from "../../typechain";
|
||||
import { AccountManager } from "./accounts/account-manager";
|
||||
|
||||
@@ -99,6 +105,71 @@ export default class TestSetup {
|
||||
return l2MessageService;
|
||||
}
|
||||
|
||||
public getL1TokenBridgeContract(signer?: Wallet): TokenBridge {
|
||||
const l1TokenBridge: TokenBridge = TokenBridge__factory.connect(
|
||||
this.config.L1.tokenBridgeAddress,
|
||||
this.getL1Provider(),
|
||||
);
|
||||
|
||||
if (signer) {
|
||||
return l1TokenBridge.connect(signer);
|
||||
}
|
||||
|
||||
return l1TokenBridge;
|
||||
}
|
||||
|
||||
public getL2TokenBridgeContract(signer?: Wallet): TokenBridge {
|
||||
const l2TokenBridge: TokenBridge = TokenBridge__factory.connect(
|
||||
this.config.L2.tokenBridgeAddress,
|
||||
this.getL2Provider(),
|
||||
);
|
||||
|
||||
if (signer) {
|
||||
return l2TokenBridge.connect(signer);
|
||||
}
|
||||
|
||||
return l2TokenBridge;
|
||||
}
|
||||
|
||||
public getL1TokenContract(signer?: Wallet): TestERC20 {
|
||||
const l1Token: TestERC20 = TestERC20__factory.connect(this.config.L1.l1TokenAddress, this.getL1Provider());
|
||||
|
||||
if (signer) {
|
||||
return l1Token.connect(signer);
|
||||
}
|
||||
|
||||
return l1Token;
|
||||
}
|
||||
|
||||
public getL2TokenContract(signer?: Wallet): TestERC20 {
|
||||
const l2Token: TestERC20 = TestERC20__factory.connect(this.config.L2.l2TokenAddress, this.getL2Provider());
|
||||
|
||||
if (signer) {
|
||||
return l2Token.connect(signer);
|
||||
}
|
||||
|
||||
return l2Token;
|
||||
}
|
||||
|
||||
public getL1BridgedTokenContract(bridgedTokenAddress: string, signer?: Wallet): BridgedToken {
|
||||
const l1BridgedToken: BridgedToken = BridgedToken__factory.connect(bridgedTokenAddress, this.getL1Provider());
|
||||
|
||||
if (signer) {
|
||||
return l1BridgedToken.connect(signer);
|
||||
}
|
||||
|
||||
return l1BridgedToken;
|
||||
}
|
||||
|
||||
public getL2BridgedTokenContract(bridgedTokenAddress: string, signer?: Wallet): BridgedToken {
|
||||
const l2BridgedToken: BridgedToken = BridgedToken__factory.connect(bridgedTokenAddress, this.getL2Provider());
|
||||
|
||||
if (signer) {
|
||||
return l2BridgedToken.connect(signer);
|
||||
}
|
||||
|
||||
return l2BridgedToken;
|
||||
}
|
||||
public getL1DummyContract(signer?: Wallet): DummyContract {
|
||||
const dummyContract = DummyContract__factory.connect(this.config.L1.dummyContractAddress, this.getL1Provider());
|
||||
|
||||
|
||||
@@ -9,11 +9,20 @@ export type BaseConfig = {
|
||||
|
||||
export type L1Config = BaseConfig & {
|
||||
lineaRollupAddress: string;
|
||||
tokenBridgeAddress: string;
|
||||
l1TokenAddress: string;
|
||||
};
|
||||
|
||||
export type BaseL2Config = BaseConfig & {
|
||||
l2MessageServiceAddress: string;
|
||||
l2TestContractAddress: string;
|
||||
l2TestContractAddress?: string;
|
||||
besuNodeRpcUrl?: URL;
|
||||
tokenBridgeAddress: string;
|
||||
l2TokenAddress: string;
|
||||
shomeiEndpoint?: URL;
|
||||
shomeiFrontendEndpoint?: URL;
|
||||
sequencerEndpoint?: URL;
|
||||
transactionExclusionEndpoint?: URL;
|
||||
};
|
||||
|
||||
export type LocalL2Config = BaseL2Config & {
|
||||
|
||||
@@ -7,6 +7,8 @@ import { TRANSACTION_CALLDATA_LIMIT } from "./common/constants";
|
||||
const l2AccountManager = config.getL2AccountManager();
|
||||
|
||||
describe("Layer 2 test suite", () => {
|
||||
const l2Provider = config.getL2Provider();
|
||||
|
||||
it.concurrent("Should revert if transaction data size is above the limit", async () => {
|
||||
const account = await l2AccountManager.generateAccount();
|
||||
const dummyContract = config.getL2DummyContract(account);
|
||||
@@ -19,8 +21,14 @@ describe("Layer 2 test suite", () => {
|
||||
it.concurrent("Should succeed if transaction data size is below the limit", async () => {
|
||||
const account = await l2AccountManager.generateAccount();
|
||||
const dummyContract = config.getL2DummyContract(account);
|
||||
const nonce = await l2Provider.getTransactionCount(account.address, "pending");
|
||||
const { maxPriorityFeePerGas, maxFeePerGas } = await l2Provider.getFeeData();
|
||||
|
||||
const tx = await dummyContract.connect(account).setPayload(ethers.randomBytes(1000));
|
||||
const tx = await dummyContract.connect(account).setPayload(ethers.randomBytes(1000), {
|
||||
nonce: nonce,
|
||||
maxPriorityFeePerGas: maxPriorityFeePerGas,
|
||||
maxFeePerGas: maxFeePerGas,
|
||||
});
|
||||
|
||||
const receipt = await tx.wait();
|
||||
expect(receipt?.status).toEqual(1);
|
||||
|
||||
Reference in New Issue
Block a user