diff --git a/Cargo.lock b/Cargo.lock index 71b7095..cd88c53 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -318,7 +318,7 @@ dependencies = [ [[package]] name = "aurora-workspace-eth-connector" version = "0.4.1" -source = "git+https://github.com/aurora-is-near/aurora-workspace.git?tag=0.4.1#dbed3551cbfd17f760a58e042c6f4c7f7f9a9a20" +source = "git+https://github.com/aurora-is-near/aurora-workspace.git?rev=16d6263d6561cbc1f838016ae15416eadc88ec90#16d6263d6561cbc1f838016ae15416eadc88ec90" dependencies = [ "anyhow", "aurora-engine-types", @@ -333,7 +333,7 @@ dependencies = [ [[package]] name = "aurora-workspace-utils" version = "0.4.1" -source = "git+https://github.com/aurora-is-near/aurora-workspace.git?tag=0.4.1#dbed3551cbfd17f760a58e042c6f4c7f7f9a9a20" +source = "git+https://github.com/aurora-is-near/aurora-workspace.git?rev=16d6263d6561cbc1f838016ae15416eadc88ec90#16d6263d6561cbc1f838016ae15416eadc88ec90" dependencies = [ "anyhow", "aurora-engine-types", diff --git a/eth-connector-tests/Cargo.toml b/eth-connector-tests/Cargo.toml index a075cda..5e33993 100644 --- a/eth-connector-tests/Cargo.toml +++ b/eth-connector-tests/Cargo.toml @@ -13,7 +13,7 @@ publish = false autobenches = false [dev-dependencies] -aurora-eth-connector = { path = "../eth-connector" } +aurora-eth-connector = { path = "../eth-connector", features = ["integration-test"] } aurora-engine-types = { workspace = true, features = ["impl-serde"] } near-sdk.workspace = true near-primitives.workspace = true @@ -28,8 +28,8 @@ hex = "0.4.3" ethabi = "18.0" rlp = { version = "0.5.0", default-features = false } aurora-engine-migration-tool = { git = "https://github.com/aurora-is-near/aurora-engine-migration-tool.git", tag = "0.2.2" } -aurora-workspace-eth-connector = { git = "https://github.com/aurora-is-near/aurora-workspace.git", tag = "0.4.1" } -aurora-workspace-utils = { git = "https://github.com/aurora-is-near/aurora-workspace.git", tag = "0.4.1" } +aurora-workspace-eth-connector = { git = "https://github.com/aurora-is-near/aurora-workspace.git", rev = "16d6263d6561cbc1f838016ae15416eadc88ec90" } +aurora-workspace-utils = { git = "https://github.com/aurora-is-near/aurora-workspace.git", rev = "16d6263d6561cbc1f838016ae15416eadc88ec90" } [features] migration-tests = [] diff --git a/eth-connector-tests/src/connector.rs b/eth-connector-tests/src/connector.rs index c66dc60..25232c5 100644 --- a/eth-connector-tests/src/connector.rs +++ b/eth-connector-tests/src/connector.rs @@ -776,6 +776,37 @@ async fn test_admin_controlled_admin_can_perform_actions_when_paused() { assert_eq!(data.eth_custodian_address, custodian_addr); } +#[tokio::test] +async fn test_deposit_with_proof_lower_than_acceptance_height() { + let min_proof_acceptance_height = 1000; + let contract = TestContract::new_with_options( + CUSTODIAN_ADDRESS, + "owner.root", + min_proof_acceptance_height, + ) + .await + .unwrap(); + + // Should fail + let proof_header_height = min_proof_acceptance_height - 1; + let user_account = contract.contract_account("eth_recipient").await.unwrap(); + let proof = contract.mock_proof(user_account.id(), 10, 1, proof_header_height); + let res = contract + .user_deposit_with_proof(&user_account, proof) + .await + .unwrap_err(); + assert!(contract.check_error_message(&res, "ERR_VERIFY_PROOF")); + + // Should succeed + let proof_header_height = min_proof_acceptance_height; + let proof = contract.mock_proof(user_account.id(), 10, 1, proof_header_height); + let res = contract + .user_deposit_with_proof(&user_account, proof) + .await + .unwrap(); + assert!(res.is_success()); +} + #[tokio::test] async fn test_deposit_pausability() { use aurora_eth_connector::admin_controlled::{PAUSE_DEPOSIT, UNPAUSE_ALL}; @@ -793,7 +824,7 @@ async fn test_deposit_pausability() { .unwrap(); // 1st deposit call - should succeed - let proof1 = contract.mock_proof(user_acc.id(), 10, 1); + let proof1 = contract.mock_proof(user_acc.id(), 10, 1, 0); let res = contract .user_deposit_with_proof(&user_acc, proof1) .await @@ -811,14 +842,14 @@ async fn test_deposit_pausability() { assert!(res.is_success()); // 2nd deposit call - should fail for `user_acc` - let proof2 = contract.mock_proof(user_acc.id(), 20, 2); + let proof2 = contract.mock_proof(user_acc.id(), 20, 2, 0); let res = contract .user_deposit_with_proof(&user_acc, proof2) .await .unwrap_err(); assert!(contract.check_error_message(&res, "ERR_PAUSED")); - let proof3 = contract.mock_proof(user_acc.id(), 30, 3); + let proof3 = contract.mock_proof(user_acc.id(), 30, 3, 0); let res = contract .user_deposit_with_proof(&owner_acc, proof3) .await @@ -836,7 +867,7 @@ async fn test_deposit_pausability() { assert!(res.is_success()); // 3rd deposit call - should succeed - let proof4 = contract.mock_proof(user_acc.id(), 40, 4); + let proof4 = contract.mock_proof(user_acc.id(), 40, 4, 0); let res = contract .user_deposit_with_proof(&user_acc, proof4) .await diff --git a/eth-connector-tests/src/utils.rs b/eth-connector-tests/src/utils.rs index f82bfd6..0af29cc 100644 --- a/eth-connector-tests/src/utils.rs +++ b/eth-connector-tests/src/utils.rs @@ -33,6 +33,14 @@ impl TestContract { pub async fn new_with_custodian_and_owner( eth_custodian_address: &str, owner_id: &str, + ) -> anyhow::Result { + Self::new_with_options(eth_custodian_address, owner_id, 0).await + } + + pub async fn new_with_options( + eth_custodian_address: &str, + owner_id: &str, + min_proof_acceptance_height: u64, ) -> anyhow::Result { let (contract, root_account) = Self::deploy_eth_connector().await?; let owner_id: AccountId = owner_id.parse().unwrap(); @@ -48,6 +56,7 @@ impl TestContract { metadata, &account_with_access_right, &owner_id, + min_proof_acceptance_height, ) .transact() .await?; @@ -234,6 +243,7 @@ impl TestContract { recipient_id: &AccountId, deposit_amount: u128, proof_index: u64, + header_height: u64, ) -> Proof { use aurora_engine_types::{ types::{Fee, NEP141Wei}, @@ -277,17 +287,25 @@ impl TestContract { ethabi::Token::Uint(U256::from(fee.as_u128())), ]), }; + // The borsh is used instead of rlp to simplify the mock logic + let header_data = + near_sdk::borsh::BorshSerialize::try_to_vec(&aurora_eth_connector::proof::MockHeader { + height: header_height, + }) + .unwrap(); + Proof { log_index: proof_index, // Only this field matters for the purpose of this test log_entry_data: rlp::encode(&log_entry).to_vec(), receipt_index: 1, + header_data, ..Default::default() } } pub async fn call_deposit_contract(&self) -> anyhow::Result<()> { - let proof = self.mock_proof(self.contract.id(), DEPOSITED_CONTRACT, 1); + let proof = self.mock_proof(self.contract.id(), DEPOSITED_CONTRACT, 1, 0); let res = self.deposit_with_proof(&proof).await?; assert!(res.is_success(), "call_deposit_contract: {res:#?}"); Ok(()) diff --git a/eth-connector/src/admin_controlled.rs b/eth-connector/src/admin_controlled.rs index 4138cdb..659ae36 100644 --- a/eth-connector/src/admin_controlled.rs +++ b/eth-connector/src/admin_controlled.rs @@ -104,6 +104,7 @@ fn test_pause_control() { paused_mask: UNPAUSE_ALL, account_with_access_right: "aurora".parse().unwrap(), owner_id: "aurora".parse().unwrap(), + min_proof_acceptance_height: 0, }; assert!(connector.assert_not_paused(PAUSE_DEPOSIT).is_ok()); diff --git a/eth-connector/src/connector.rs b/eth-connector/src/connector.rs index b2db616..1bcd710 100644 --- a/eth-connector/src/connector.rs +++ b/eth-connector/src/connector.rs @@ -34,7 +34,7 @@ pub trait FundsFinish { #[ext_contract(ext_proof_verifier)] pub trait ProofVerifier { #[result_serializer(borsh)] - fn verify_log_entry(&self, #[serializer(borsh)] args: VerifyProofArgs) -> bool; + fn verify_log_entry_in_bound(&self, #[serializer(borsh)] args: VerifyProofArgs) -> bool; } /// Withdraw method for legacy implementation in Engine diff --git a/eth-connector/src/connector_impl.rs b/eth-connector/src/connector_impl.rs index e939352..94f3b16 100644 --- a/eth-connector/src/connector_impl.rs +++ b/eth-connector/src/connector_impl.rs @@ -3,7 +3,7 @@ use crate::{ connector::{ext_funds_finish, ext_proof_verifier}, deposit_event::{DepositedEvent, TokenMessageData}, errors, log, panic_err, - proof::Proof, + proof::{Proof, VerifyProofArgs}, types::SdkUnwrap, AdminControlled, PausedMask, }; @@ -58,6 +58,9 @@ pub struct EthConnector { pub account_with_access_right: AccountId, /// Owner's account id. pub owner_id: AccountId, + /// Proofs from blocks that are below the acceptance height will be rejected. + // If `minBlockAcceptanceHeight` value is zero - proofs from block with any height are accepted. + pub min_proof_acceptance_height: u64, } impl AdminControlled for EthConnector { @@ -160,9 +163,12 @@ impl EthConnector { } }; + let mut verify_log_args: VerifyProofArgs = proof.into(); + verify_log_args.min_header_height = Some(self.min_proof_acceptance_height); + ext_proof_verifier::ext(self.prover_account.clone()) .with_static_gas(GAS_FOR_VERIFY_LOG_ENTRY) - .verify_log_entry(proof.into()) + .verify_log_entry_in_bound(verify_log_args) .then( ext_funds_finish::ext(current_account_id) .with_static_gas(GAS_FOR_FINISH_DEPOSIT) diff --git a/eth-connector/src/lib.rs b/eth-connector/src/lib.rs index da27a65..970ffa6 100644 --- a/eth-connector/src/lib.rs +++ b/eth-connector/src/lib.rs @@ -166,6 +166,7 @@ impl EthConnectorContract { metadata: &FungibleTokenMetadata, account_with_access_right: AccountId, owner_id: &AccountId, + min_proof_acceptance_height: u64, ) -> Self { metadata.assert_valid(); @@ -177,6 +178,7 @@ impl EthConnectorContract { eth_custodian_address, account_with_access_right, owner_id: owner_id.clone(), + min_proof_acceptance_height, }; let mut this = Self { ft: FungibleToken { @@ -204,8 +206,16 @@ impl EthConnectorContract { #[result_serializer(borsh)] #[must_use] #[allow(unused_variables)] - pub fn verify_log_entry(#[serializer(borsh)] proof_args: &VerifyProofArgs) -> bool { - log!("Call from verify_log_entry"); + pub fn verify_log_entry_in_bound( + &self, + #[serializer(borsh)] proof_args: &VerifyProofArgs, + ) -> bool { + log!("Call from verify_log_entry_in_bound"); + if let Ok(header) = proof::MockHeader::try_from_slice(&proof_args.header_data) { + if header.height < self.connector.min_proof_acceptance_height { + return false; + } + } true } @@ -721,12 +731,14 @@ mod tests { }; let account_with_access_right = "engine.near".parse().unwrap(); let owner_id = "owner.near".parse().unwrap(); + let min_proof_acceptance_height = 0; EthConnectorContract::new( prover_account, eth_custodian_address, &metadata, account_with_access_right, &owner_id, + min_proof_acceptance_height, ) } } diff --git a/eth-connector/src/proof.rs b/eth-connector/src/proof.rs index d586b73..5ea9fb7 100644 --- a/eth-connector/src/proof.rs +++ b/eth-connector/src/proof.rs @@ -42,6 +42,8 @@ pub struct VerifyProofArgs { pub receipt_data: Vec, pub header_data: Vec, pub proof: Vec>, + pub min_header_height: Option, + pub max_header_height: Option, pub skip_bridge_call: bool, } @@ -54,11 +56,19 @@ impl From for VerifyProofArgs { receipt_data: value.receipt_data, header_data: value.header_data, proof: value.proof, + min_header_height: None, + max_header_height: None, skip_bridge_call: false, } } } +#[cfg(feature = "integration-test")] +#[derive(BorshDeserialize, BorshSerialize, Default, Debug)] +pub struct MockHeader { + pub height: u64, +} + #[cfg(test)] mod tests { use super::Proof;