diff --git a/.github/workflows/pr_main.yml b/.github/workflows/pr_main.yml new file mode 100644 index 0000000..a4a46c8 --- /dev/null +++ b/.github/workflows/pr_main.yml @@ -0,0 +1,102 @@ +permissions: + contents: read +name: rex integration tests +on: + push: + branches: ["main"] + merge_group: + pull_request: + branches: ["**"] + +concurrency: + group: ${{ github.workflow }}-${{ github.head_ref || github.run_id }} + cancel-in-progress: true + +jobs: + sdk-integration-test: + name: Integration Test - SDK + runs-on: ubuntu-latest + + steps: + - name: Free Disk Space (Ubuntu) + uses: jlumbroso/free-disk-space@v1.3.1 + with: + tool-cache: false + large-packages: false + - name: Checkout sources + uses: actions/checkout@v4 + - name: Setup Rust Environment + uses: ./.github/actions/setup-rust + + - name: Install solc + uses: pontem-network/get-solc@master + with: + version: v0.8.29 + token: ${{ secrets.GITHUB_TOKEN || '' }} + + - name: Install ethrex + run: | + cd .. + git clone https://github.com/lambdaclass/ethrex.git + echo "ethrex installed successfully" + + # also creates empty verification keys (as workflow runs with exec backend) + - name: Build prover + run: | + cd ../ethrex/crates/l2 + make build-prover + mkdir -p prover/zkvm/interface/sp1/out && touch prover/zkvm/interface/sp1/out/riscv32im-succinct-zkvm-vk + + - name: Build L1 docker image + uses: docker/build-push-action@v6 + with: + context: ../ethrex/ + file: ../ethrex/crates/blockchain/dev/Dockerfile + tags: ethrex_dev:latest + push: false + + - name: Start L1 & Deploy contracts + run: | + cd ../ethrex/crates/l2 + touch .env + CI_ETHREX_WORKDIR=/usr/local/bin \ + ETHREX_DEPLOYER_DEPLOY_RICH=true \ + ETHREX_DEPLOYER_PICO_CONTRACT_ADDRESS=0x00000000000000000000000000000000000000aa \ + ETHREX_DEPLOYER_SP1_CONTRACT_ADDRESS=0x00000000000000000000000000000000000000aa \ + ETHREX_DEPLOYER_RISC0_CONTRACT_ADDRESS=0x00000000000000000000000000000000000000aa \ + ETHREX_L2_VALIDIUM=false \ + docker compose -f docker-compose-l2.yaml up contract_deployer + + - name: Start Sequencer + run: | + cd ../ethrex/crates/l2 + CI_ETHREX_WORKDIR=/usr/local/bin \ + ETHREX_L2_VALIDIUM=false \ + ETHREX_WATCHER_BLOCK_DELAY=0 \ + docker compose -f docker-compose-l2.yaml up --detach ethrex_l2 + + - name: Run test + run: | + sudo chmod -R a+rw ../ethrex/crates/l2 + cd ../ethrex/crates/l2 + RUST_LOG=info,ethrex_prover_lib=debug make init-prover & + docker logs --follow ethrex_l2 & + cd /home/runner/work/rex/rex/sdk + PROPOSER_COINBASE_ADDRESS=0x0007a881CD95B1484fca47615B64803dad620C8d cargo test --package rex-sdk --test tests -- --nocapture --test-threads=1 + killall ethrex_prover -s SIGINT + +# The purpose of this job is to add it as a required check in GitHub so that we don't have to add every individual job as a required check + all-tests: + # "Integration Test" is a required check, don't change the name + name: Integration Test + runs-on: ubuntu-latest + needs: [sdk-integration-test] + # Make sure this job runs even if the previous jobs failed or were skipped + if: ${{ always() && needs.sdk-integration-test.result != 'skipped' }} + steps: + - name: Check if any job failed + run: | + if [ "${{ needs.sdk-integration-test.result }}" != "success" ]; then + echo "Job SDK integration test Check failed" + exit 1 + fi diff --git a/.github/workflows/pr_main_sdk.yml b/.github/workflows/pr_main_sdk.yml index b4dae86..173d598 100644 --- a/.github/workflows/pr_main_sdk.yml +++ b/.github/workflows/pr_main_sdk.yml @@ -55,75 +55,3 @@ jobs: - name: Run tests run: cargo test --manifest-path sdk/Cargo.toml --lib - - integration-test: - name: Integration Test - SDK - runs-on: ubuntu-latest - - steps: - - name: Free Disk Space (Ubuntu) - uses: jlumbroso/free-disk-space@v1.3.1 - with: - tool-cache: false - large-packages: false - - name: Checkout sources - uses: actions/checkout@v4 - - name: Setup Rust Environment - uses: ./.github/actions/setup-rust - - - name: Install solc - uses: pontem-network/get-solc@master - with: - version: v0.8.29 - token: ${{ secrets.GITHUB_TOKEN || '' }} - - - name: Install ethrex - run: | - cd .. - git clone https://github.com/lambdaclass/ethrex.git - echo "ethrex installed successfully" - - # also creates empty verification keys (as workflow runs with exec backend) - - name: Build prover - run: | - cd ../ethrex/crates/l2 - make build-prover - mkdir -p prover/zkvm/interface/sp1/out && touch prover/zkvm/interface/sp1/out/riscv32im-succinct-zkvm-vk - - - name: Build L1 docker image - uses: docker/build-push-action@v6 - with: - context: ../ethrex/ - file: ../ethrex/crates/blockchain/dev/Dockerfile - tags: ethrex_dev:latest - push: false - - - name: Start L1 & Deploy contracts - run: | - cd ../ethrex/crates/l2 - touch .env - CI_ETHREX_WORKDIR=/usr/local/bin \ - ETHREX_DEPLOYER_DEPLOY_RICH=true \ - ETHREX_DEPLOYER_PICO_CONTRACT_ADDRESS=0x00000000000000000000000000000000000000aa \ - ETHREX_DEPLOYER_SP1_CONTRACT_ADDRESS=0x00000000000000000000000000000000000000aa \ - ETHREX_DEPLOYER_RISC0_CONTRACT_ADDRESS=0x00000000000000000000000000000000000000aa \ - ETHREX_L2_VALIDIUM=false \ - docker compose -f docker-compose-l2.yaml up contract_deployer - - - name: Start Sequencer - run: | - cd ../ethrex/crates/l2 - CI_ETHREX_WORKDIR=/usr/local/bin \ - ETHREX_L2_VALIDIUM=false \ - ETHREX_WATCHER_BLOCK_DELAY=0 \ - docker compose -f docker-compose-l2.yaml up --detach ethrex_l2 - - - name: Run test - run: | - sudo chmod -R a+rw ../ethrex/crates/l2 - cd ../ethrex/crates/l2 - RUST_LOG=info,ethrex_prover_lib=debug make init-prover & - docker logs --follow ethrex_l2 & - cd /home/runner/work/rex/rex/sdk - PROPOSER_COINBASE_ADDRESS=0x0007a881CD95B1484fca47615B64803dad620C8d cargo test --package rex-sdk --test tests -- --nocapture --test-threads=1 - killall ethrex_prover -s SIGINT diff --git a/sdk/tests/tests.rs b/sdk/tests/tests.rs index 48af2d0..74e96c2 100644 --- a/sdk/tests/tests.rs +++ b/sdk/tests/tests.rs @@ -9,6 +9,7 @@ use rex_sdk::client::eth::BlockByNumber; use rex_sdk::client::eth::get_address_from_secret_key; use rex_sdk::l2::deposit::{deposit_through_contract_call, deposit_through_transfer}; use rex_sdk::l2::l1_to_l2_tx_data::{L1ToL2TransactionData, send_l1_to_l2_tx}; +use rex_sdk::l2::withdraw::{claim_withdraw, withdraw}; use rex_sdk::transfer; use rex_sdk::wait_for_transaction_receipt; use secp256k1::SecretKey; @@ -17,6 +18,8 @@ use std::{ io::{BufRead, BufReader}, ops::Mul, path::PathBuf, + str::FromStr, + time::Duration, }; const DEFAULT_ETH_URL: &str = "http://localhost:8545"; @@ -96,6 +99,19 @@ async fn sdk_integration_test() -> Result<(), Box> { test_privileged_tx_with_contract_call_revert(&proposer_client, ð_client).await?; + let withdrawals_count = std::env::var("INTEGRATION_TEST_WITHDRAW_COUNT") + .map(|amount| amount.parse().expect("Invalid withdrawal amount value")) + .unwrap_or(5); + + test_n_withdraws( + &rich_wallet_private_key, + ð_client, + &proposer_client, + bridge_address, + withdrawals_count, + ) + .await?; + Ok(()) } @@ -204,7 +220,7 @@ async fn test_deposit_through_transfer( .get_balance(fees_vault(), BlockByNumber::Latest) .await?; - println!("Depositing funds from L1 to L2"); + println!("Depositing funds from L1 to L2 through transfer"); let deposit_tx_hash = deposit_through_transfer( deposit_value, @@ -308,12 +324,12 @@ async fn test_deposit_through_contract_call( .get_balance(fees_vault(), BlockByNumber::Latest) .await?; - println!("Depositing funds from L1 to L2"); + println!("Depositing funds from L1 to L2 through contract call"); let deposit_tx_hash = deposit_through_contract_call( deposit_value, deposit_recipient_address, - 21000 * 5, + 21000 * 10, depositor_private_key, bridge_address, eth_client, @@ -441,6 +457,9 @@ async fn test_transfer_with_privileged_tx( println!("Waiting for L1 to L2 transaction receipt on L1"); + // this sleep time shouldn't be here issue #173 + tokio::time::sleep(std::time::Duration::from_secs(12)).await; + let l1_to_l2_tx_receipt = wait_for_transaction_receipt(l1_to_l2_tx_hash, eth_client, 5, true).await?; println!("Waiting for L1 to L2 transaction receipt on L2"); @@ -914,3 +933,196 @@ async fn test_call_to_contract_with_deposit( Ok(()) } + +async fn test_n_withdraws( + withdrawer_private_key: &SecretKey, + eth_client: &EthClient, + proposer_client: &EthClient, + bridge_address: Address, + n: u64, +) -> Result<(), Box> { + // Withdraw funds from L2 to L1 + let withdrawer_address = get_address_from_secret_key(withdrawer_private_key)?; + let withdraw_value = std::env::var("INTEGRATION_TEST_WITHDRAW_VALUE") + .map(|value| U256::from_dec_str(&value).expect("Invalid withdraw value")) + .unwrap_or(U256::from(100000000000000000000u128)); + + println!("Checking balances on L1 and L2 before withdrawal"); + + let withdrawer_l2_balance_before_withdrawal = proposer_client + .get_balance(withdrawer_address, BlockByNumber::Latest) + .await?; + + assert!( + withdrawer_l2_balance_before_withdrawal >= withdraw_value, + "L2 withdrawer doesn't have enough balance to withdraw" + ); + + let bridge_balance_before_withdrawal = eth_client + .get_balance(common_bridge_address(), BlockByNumber::Latest) + .await?; + + assert!( + bridge_balance_before_withdrawal >= withdraw_value, + "L1 bridge doesn't have enough balance to withdraw" + ); + + let withdrawer_l1_balance_before_withdrawal = eth_client + .get_balance(withdrawer_address, BlockByNumber::Latest) + .await?; + + let fee_vault_balance_before_withdrawal = proposer_client + .get_balance(fees_vault(), BlockByNumber::Latest) + .await?; + + println!("Withdrawing funds from L2 to L1"); + + let mut withdraw_txs = vec![]; + let mut receipts = vec![]; + + for x in 1..n + 1 { + println!("Sending withdraw {x}/{n}"); + let withdraw_tx = withdraw( + withdraw_value, + withdrawer_address, + *withdrawer_private_key, + proposer_client, + None, + ) + .await?; + + withdraw_txs.push(withdraw_tx); + + let withdraw_tx_receipt = + wait_for_transaction_receipt(withdraw_tx, proposer_client, 1000, true) + .await + .expect("Withdraw tx receipt not found"); + + receipts.push(withdraw_tx_receipt); + } + + println!("Checking balances on L1 and L2 after withdrawal"); + + let withdrawer_l2_balance_after_withdrawal = proposer_client + .get_balance(withdrawer_address, BlockByNumber::Latest) + .await?; + + assert!( + (withdrawer_l2_balance_before_withdrawal - withdraw_value * n) + .abs_diff(withdrawer_l2_balance_after_withdrawal) + < L2_GAS_COST_MAX_DELTA * n, + "Withdrawer L2 balance didn't decrease as expected after withdrawal" + ); + + let withdrawer_l1_balance_after_withdrawal = eth_client + .get_balance(withdrawer_address, BlockByNumber::Latest) + .await?; + + assert_eq!( + withdrawer_l1_balance_after_withdrawal, withdrawer_l1_balance_before_withdrawal, + "Withdrawer L1 balance should not change after withdrawal" + ); + + let fee_vault_balance_after_withdrawal = proposer_client + .get_balance(fees_vault(), BlockByNumber::Latest) + .await?; + + let mut withdraw_fees = U256::zero(); + for receipt in receipts { + withdraw_fees += get_fees_details_l2(receipt, proposer_client) + .await + .recoverable_fees; + } + + assert_eq!( + fee_vault_balance_after_withdrawal, + fee_vault_balance_before_withdrawal + withdraw_fees, + "Fee vault balance didn't increase as expected after withdrawal" + ); + + // We need to wait for all the txs to be included in some batch + let mut proofs = vec![]; + for (i, tx) in withdraw_txs.clone().into_iter().enumerate() { + println!("Getting withdrawal proof {}/{n}", i + 1); + let message_proof = proposer_client.wait_for_message_proof(tx, 1000).await?; + let withdrawal_proof = message_proof + .into_iter() + .next() + .expect("no l1messages in withdrawal"); + proofs.push(withdrawal_proof); + } + + let on_chain_proposer_address = Address::from_str( + &std::env::var("ETHREX_COMMITTER_ON_CHAIN_PROPOSER_ADDRESS") + .expect("ETHREX_COMMITTER_ON_CHAIN_PROPOSER_ADDRESS env var not set"), + ) + .unwrap(); + for proof in &proofs { + while eth_client + .get_last_verified_batch(on_chain_proposer_address) + .await + .unwrap() + < proof.batch_number + { + println!("Withdrawal is not verified on L1 yet"); + tokio::time::sleep(Duration::from_secs(2)).await; + } + } + + let mut withdraw_claim_txs_receipts = vec![]; + + for (x, proof) in proofs.iter().enumerate() { + println!("Claiming withdrawal on L1 {x}/{n}"); + + let withdraw_claim_tx = claim_withdraw( + withdraw_value, + withdrawer_address, + *withdrawer_private_key, + eth_client, + proof, + bridge_address, + ) + .await?; + let withdraw_claim_tx_receipt = + wait_for_transaction_receipt(withdraw_claim_tx, eth_client, 5, true).await?; + withdraw_claim_txs_receipts.push(withdraw_claim_tx_receipt); + } + + println!("Checking balances on L1 and L2 after claim"); + + let withdrawer_l1_balance_after_claim = eth_client + .get_balance(withdrawer_address, BlockByNumber::Latest) + .await?; + + let gas_used_value: u64 = withdraw_claim_txs_receipts + .iter() + .map(|x| x.tx_info.gas_used * x.tx_info.effective_gas_price) + .sum(); + + assert_eq!( + withdrawer_l1_balance_after_claim, + withdrawer_l1_balance_after_withdrawal + withdraw_value * n - gas_used_value, + "Withdrawer L1 balance wasn't updated as expected after claim" + ); + + let withdrawer_l2_balance_after_claim = proposer_client + .get_balance(withdrawer_address, BlockByNumber::Latest) + .await?; + + assert_eq!( + withdrawer_l2_balance_after_claim, withdrawer_l2_balance_after_withdrawal, + "Withdrawer L2 balance should not change after claim" + ); + + let bridge_balance_after_withdrawal = eth_client + .get_balance(common_bridge_address(), BlockByNumber::Latest) + .await?; + + assert_eq!( + bridge_balance_after_withdrawal, + bridge_balance_before_withdrawal - withdraw_value * n, + "Bridge balance didn't decrease as expected after withdrawal" + ); + + Ok(()) +}