diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 1323b64ae..5b1048175 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -22,8 +22,7 @@ jobs: profile: minimal toolchain: nightly-2021-03-25 override: true - - run: make release - - run: ls -lH release.wasm + - run: make test-build - name: Run cargo test uses: actions-rs/cargo@v1 with: diff --git a/Cargo.lock b/Cargo.lock index 7a23e8a91..9b207e93e 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -121,6 +121,7 @@ dependencies = [ "blake2 0.9.1 (git+https://github.com/near/near-blake2.git)", "borsh", "bstr", + "byte-slice-cast", "criterion", "ethabi", "evm", diff --git a/Cargo.toml b/Cargo.toml index a77602742..0d91f3bd6 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -54,6 +54,7 @@ wee_alloc = { version = "0.4.5", default-features = false } lunarity-lexer = { git = "https://github.com/ilblackdragon/lunarity", rev = "5201d9a76f7e491082b7f74af7e64049271e387f", default-features = false } ethabi = { git = "https://github.com/darwinia-network/ethabi", branch = "xavier-no-std", default-features = false } hex = { version = "0.4", default-features = false, features = ["alloc"] } +byte-slice-cast = { version = "1.0", default-features = false } rjson = { version = "0.3.1", default-features = false } [dev-dependencies] @@ -77,3 +78,6 @@ testnet = [] engine = [] contract = ["engine"] evm_bully = [] +log = [] +exit-precompiles = ["contract"] +integration-test = ["log"] diff --git a/Makefile b/Makefile index 6f02e4600..9d2d30eed 100644 --- a/Makefile +++ b/Makefile @@ -1,6 +1,6 @@ CARGO = cargo NEAR = near -FEATURES = contract +FEATURES = contract,log ifeq ($(evm-bully),yes) FEATURES := $(FEATURES),evm_bully @@ -15,6 +15,7 @@ release.wasm: target/wasm32-unknown-unknown/release/aurora_engine.wasm target/wasm32-unknown-unknown/release/aurora_engine.wasm: Cargo.toml Cargo.lock $(wildcard src/*.rs) RUSTFLAGS='-C link-arg=-s' $(CARGO) build --target wasm32-unknown-unknown --release --no-default-features --features=$(FEATURES) -Z avoid-dev-deps + ls -l target/wasm32-unknown-unknown/release/aurora_engine.wasm debug: debug.wasm @@ -24,6 +25,11 @@ debug.wasm: target/wasm32-unknown-unknown/debug/aurora_engine.wasm target/wasm32-unknown-unknown/debug/aurora_engine.wasm: Cargo.toml Cargo.lock $(wildcard src/*.rs) $(CARGO) build --target wasm32-unknown-unknown --no-default-features --features=$(FEATURES) -Z avoid-dev-deps +test-build: + RUSTFLAGS='-C link-arg=-s' $(CARGO) build --target wasm32-unknown-unknown --release --no-default-features --features=contract,integration-test -Z avoid-dev-deps + ln -sf target/wasm32-unknown-unknown/release/aurora_engine.wasm release.wasm + ls -l target/wasm32-unknown-unknown/release/aurora_engine.wasm + .PHONY: all release debug deploy: release.wasm @@ -35,10 +41,10 @@ check-format: $(CARGO) fmt -- --check check-clippy: - $(CARGO) +nightly clippy --no-default-features --features=$(FEATURES) -- -D warnings + $(CARGO) clippy --no-default-features --features=$(FEATURES) -- -D warnings # test depends on release since `tests/test_upgrade.rs` includes `release.wasm` -test: release +test: test-build $(CARGO) test format: diff --git a/doc/eth-connector.md b/doc/eth-connector.md new file mode 100644 index 000000000..56e8f07bc --- /dev/null +++ b/doc/eth-connector.md @@ -0,0 +1,42 @@ +# ETH connector + +## Build +1. For production set in the Makefile + ``` + FEATURES = contract + ``` + 1.1. For **development and testing** set in the Makefile + ``` + FEATURES = contract,integration-test + ``` +2. Build release: `$ make release` +3. Run tests: `$ cargo test` +4. Deploying process is common for Aurora itself. Please reference [README.md](../README.md) + +## Initialize eth-conenctor +With `near-cli` run: +``` +$ near call new_eth_connector '{"prover_account": "", "eth_custodian_address": ""}' --account-id + +``` + +## ETH connector specific methods +* new_eth_connector (call once) +* deposit (mutable) +* withdraw (mutable, payable) +* finish_deposit_near (private, mutable) +* ft_total_supply (view) +* ft_total_supply_near (view) +* ft_total_supply_eth (view) +* ft_balance_of (view) +* ft_balance_of_eth (view) +* ft_transfer (mutable, payable) +* ft_resolve_transfer (private, mutable) +* ft_transfer_call (mutable, payable) +* ft_on_transfer (private, mutable) +* storage_deposit (mutable) +* storage_withdraw (mutable, payable) +* storage_balance_of (view) + +## Ethereum specific flow +Follow by [this instruction](https://github.com/aurora-is-near/eth-connector/blob/master/README.md). diff --git a/etc/state-migration-test/Cargo.lock b/etc/state-migration-test/Cargo.lock index 0ebfde6e3..41d496c5b 100644 --- a/etc/state-migration-test/Cargo.lock +++ b/etc/state-migration-test/Cargo.lock @@ -36,6 +36,7 @@ dependencies = [ "aurora-bn", "blake2", "borsh", + "byte-slice-cast", "ethabi", "evm", "evm-core", @@ -132,7 +133,7 @@ dependencies = [ "borsh-derive-internal", "borsh-schema-derive-internal", "proc-macro-crate", - "proc-macro2 1.0.26", + "proc-macro2 1.0.27", "syn 1.0.72", ] @@ -142,7 +143,7 @@ version = "0.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d2104c73179359431cc98e016998f2f23bc7a05bc53e79741bcba705f30047bc" dependencies = [ - "proc-macro2 1.0.26", + "proc-macro2 1.0.27", "quote 1.0.9", "syn 1.0.72", ] @@ -153,16 +154,22 @@ version = "0.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ae29eb8418fcd46f723f8691a2ac06857d31179d33d2f2d91eb13967de97c728" dependencies = [ - "proc-macro2 1.0.26", + "proc-macro2 1.0.27", "quote 1.0.9", "syn 1.0.72", ] [[package]] name = "bumpalo" -version = "3.6.1" +version = "3.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "63396b8a4b9de3f4fdfb320ab6080762242f66a8ef174c49d8e19b674db4cdbe" +checksum = "9c59e7af012c713f529e7a3ee57ce9b31ddd858d4b512923602f74608b009631" + +[[package]] +name = "byte-slice-cast" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "65c1bf4a04a88c54f589125563643d773f3254b5c38571395e2b591c693bbc81" [[package]] name = "byte-tools" @@ -376,9 +383,9 @@ dependencies = [ [[package]] name = "getrandom" -version = "0.2.2" +version = "0.2.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c9495705279e7140bf035dde1f6e750c162df8b625267cd52cc44e0b156732c8" +checksum = "7fcd999463524c52659517fe2cea98493cfe485d10565e7b0fb07dbba7ad2753" dependencies = [ "cfg-if 1.0.0", "js-sys", @@ -449,9 +456,9 @@ checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" [[package]] name = "libc" -version = "0.2.94" +version = "0.2.95" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "18794a8ad5b29321f790b55d93dfba91e125cb1a9edbd4f8e3150acc771c1a5e" +checksum = "789da6d93f1b866ffe175afc5322a4d76c038605a1c3319bb57b06967ca98a36" [[package]] name = "libsecp256k1" @@ -636,9 +643,9 @@ dependencies = [ [[package]] name = "proc-macro2" -version = "1.0.26" +version = "1.0.27" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a152013215dca273577e18d2bf00fa862b89b24169fb78c4c95aeb07992c9cec" +checksum = "f0d8caf72986c1a598726adc988bb5984792ef84f5ee5aa50209145ee8077038" dependencies = [ "unicode-xid 0.2.2", ] @@ -658,7 +665,7 @@ version = "1.0.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c3d0b9745dc2debf507c8422de05d7226cc1f0644216dfdfead988f9b1ab32a7" dependencies = [ - "proc-macro2 1.0.26", + "proc-macro2 1.0.27", ] [[package]] @@ -736,7 +743,7 @@ version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e33d7b2abe0c340d8797fe2907d3f20d3b5ea5908683618bfe80df7f621f672a" dependencies = [ - "proc-macro2 1.0.26", + "proc-macro2 1.0.27", "quote 1.0.9", "syn 1.0.72", ] @@ -820,7 +827,7 @@ version = "1.0.72" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a1e8cdbefb79a9a5a65e0db8b47b723ee907b7c7f8496c76a1770b5c310bab82" dependencies = [ - "proc-macro2 1.0.26", + "proc-macro2 1.0.27", "quote 1.0.9", "unicode-xid 0.2.2", ] @@ -920,7 +927,7 @@ dependencies = [ "bumpalo", "lazy_static", "log", - "proc-macro2 1.0.26", + "proc-macro2 1.0.27", "quote 1.0.9", "syn 1.0.72", "wasm-bindgen-shared", @@ -942,7 +949,7 @@ version = "0.2.74" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "be2241542ff3d9f241f5e2cb6dd09b37efe786df8851c54957683a49f0987a97" dependencies = [ - "proc-macro2 1.0.26", + "proc-macro2 1.0.27", "quote 1.0.9", "syn 1.0.72", "wasm-bindgen-backend", diff --git a/src/admin_controlled.rs b/src/admin_controlled.rs new file mode 100644 index 000000000..c96c55ccf --- /dev/null +++ b/src/admin_controlled.rs @@ -0,0 +1,28 @@ +use crate::sdk; + +pub type PausedMask = u8; + +pub trait AdminControlled { + /// Returns true if the current account is owner + fn is_owner(&self) -> bool { + sdk::current_account_id() == sdk::predecessor_account_id() + } + + /// Return the current mask representing all paused events. + fn get_paused(&self) -> PausedMask; + + /// Update mask with all paused events. + /// Implementor is responsible for guaranteeing that this function can only be + /// called by owner of the contract. + fn set_paused(&mut self, paused: PausedMask); + + /// Return if the contract is paused for the current flag and user + fn is_paused(&self, flag: PausedMask) -> bool { + (self.get_paused() & flag) != 0 && !self.is_owner() + } + + /// Asserts the passed paused flag is not set. Panics with "ERR_PAUSED" if the flag is set. + fn assert_not_paused(&self, flag: PausedMask) { + assert!(!self.is_paused(flag), "ERR_PAUSED"); + } +} diff --git a/src/connector.rs b/src/connector.rs new file mode 100644 index 000000000..fd4df0110 --- /dev/null +++ b/src/connector.rs @@ -0,0 +1,669 @@ +use crate::fungible_token::*; +use crate::parameters::*; +use crate::sdk; +use crate::types::*; + +use crate::admin_controlled::{AdminControlled, PausedMask}; +use crate::deposit_event::*; +use crate::engine::Engine; +use crate::json::parse_json; +use crate::prelude::*; +use crate::prover::validate_eth_address; +use crate::storage::{self, EthConnectorStorageId, KeyPrefix}; +#[cfg(feature = "log")] +use alloc::format; +use borsh::{BorshDeserialize, BorshSerialize}; + +pub const NO_DEPOSIT: Balance = 0; +const GAS_FOR_FINISH_DEPOSIT: Gas = 50_000_000_000_000; +// Note: Is 40Tgas always enough? +const GAS_FOR_VERIFY_LOG_ENTRY: Gas = 40_000_000_000_000; + +const UNPAUSE_ALL: PausedMask = 0; +const PAUSE_DEPOSIT: PausedMask = 1 << 0; +const PAUSE_WITHDRAW: PausedMask = 1 << 1; + +#[derive(BorshSerialize, BorshDeserialize)] +pub struct EthConnectorContract { + contract: EthConnector, + ft: FungibleToken, + paused_mask: PausedMask, +} + +/// eth-connector specific data +#[derive(BorshSerialize, BorshDeserialize)] +pub struct EthConnector { + pub prover_account: AccountId, + pub eth_custodian_address: EthAddress, +} + +/// Token message data +#[derive(BorshSerialize, BorshDeserialize)] +#[cfg_attr(not(target_arch = "wasm32"), derive(Debug))] +pub enum TokenMessageData { + Near(AccountId), + Eth { address: AccountId, message: String }, +} + +/// On-transfer message +pub struct OnTransferMessageData { + pub relayer: AccountId, + pub recipient: EthAddress, + pub fee: U256, +} + +impl EthConnectorContract { + pub fn get_instance() -> Self { + Self { + contract: Self::get_contract_data(&EthConnectorStorageId::Contract), + ft: Self::get_contract_data(&EthConnectorStorageId::FungibleToken), + paused_mask: Self::get_contract_data(&EthConnectorStorageId::PausedMask), + } + } + + fn get_contract_key(suffix: &EthConnectorStorageId) -> Vec { + storage::bytes_to_key(KeyPrefix::EthConnector, &[*suffix as u8]) + } + + fn get_contract_data(suffix: &EthConnectorStorageId) -> T { + let data = + sdk::read_storage(&Self::get_contract_key(&suffix)).expect("Failed read storage"); + T::try_from_slice(&data[..]).unwrap() + } + + /// Init eth-connector contract specific data + pub fn init_contract(args: InitCallArgs) { + // Check is it already initialized + assert!( + !sdk::storage_has_key(&Self::get_contract_key(&EthConnectorStorageId::Contract)), + "ERR_CONTRACT_INITIALIZED" + ); + #[cfg(feature = "log")] + sdk::log("[init contract]"); + + let contract_data = Self::set_contract_data(SetContractDataCallArgs { + prover_account: args.prover_account, + eth_custodian_address: args.eth_custodian_address, + }); + + let current_account_id = sdk::current_account_id(); + let owner_id = String::from_utf8(current_account_id).unwrap(); + let mut ft = FungibleToken::new(); + // Register FT account for current contract + ft.internal_register_account(&owner_id); + + let paused_mask = UNPAUSE_ALL; + sdk::save_contract( + &Self::get_contract_key(&EthConnectorStorageId::PausedMask), + &paused_mask, + ); + + Self { + contract: contract_data, + ft, + paused_mask, + } + .save_ft_contract(); + } + + /// Sets the contract data and returns it back + pub fn set_contract_data(args: SetContractDataCallArgs) -> EthConnector { + // Get initial contract arguments + let contract_data = EthConnector { + prover_account: args.prover_account, + eth_custodian_address: validate_eth_address(args.eth_custodian_address), + }; + // Save eth-connector specific data + sdk::save_contract( + &Self::get_contract_key(&EthConnectorStorageId::Contract), + &contract_data, + ); + + contract_data + } + + /// Parse event message data for tokens + fn parse_event_message(&self, message: &str) -> TokenMessageData { + let data: Vec<_> = message.split(':').collect(); + assert!(data.len() < 3); + if data.len() == 1 { + let account_id = data[0]; + assert!( + is_valid_account_id(account_id.as_bytes()), + "ERR_INVALID_ACCOUNT_ID" + ); + TokenMessageData::Near(account_id.into()) + } else { + TokenMessageData::Eth { + address: data[0].into(), + message: data[1].into(), + } + } + } + + /// Get on-transfer data from message + fn parse_on_transfer_message(&self, message: &str) -> OnTransferMessageData { + let data: Vec<_> = message.split(':').collect(); + assert_eq!(data.len(), 2); + + let msg = hex::decode(data[1]).expect(ERR_FAILED_PARSE); + let mut fee: [u8; 32] = Default::default(); + assert_eq!(msg.len(), 52, "ERR_WRONG_MESSAGE_LENGTH"); + fee.copy_from_slice(&msg[..32]); + let mut recipient: EthAddress = Default::default(); + recipient.copy_from_slice(&msg[32..52]); + // Checkk account + let account_id = data[0]; + assert!( + is_valid_account_id(account_id.as_bytes()), + "ERR_INVALID_ACCOUNT_ID" + ); + OnTransferMessageData { + relayer: account_id.into(), + recipient, + fee: U256::from_little_endian(&fee[..]), + } + } + + /// Prepare message for `ft_transfer_call` -> `ft_on_transfer` + fn set_message_for_on_transfer(&self, fee: U256, message: String) -> String { + use byte_slice_cast::AsByteSlice; + + // Relayer == predecessor + let relayer_account_id = String::from_utf8(sdk::predecessor_account_id()).unwrap(); + let mut data = fee.as_byte_slice().to_vec(); + let message = hex::decode(message).expect(ERR_FAILED_PARSE); + data.extend(message); + [relayer_account_id, hex::encode(data)].join(":") + } + + /// Deposit all types of tokens + pub fn deposit(&self) { + self.assert_not_paused(PAUSE_DEPOSIT); + + use crate::prover::Proof; + #[cfg(feature = "log")] + sdk::log("[Deposit tokens]"); + + // Get incoming deposit arguments + let raw_proof = sdk::read_input(); + let proof: Proof = Proof::try_from_slice(&raw_proof).expect(ERR_FAILED_PARSE); + // Fetch event data from Proof + let event = DepositedEvent::from_log_entry_data(&proof.log_entry_data); + + #[cfg(feature = "log")] + sdk::log(&format!( + "Deposit started: from {} to recipient {:?} with amount: {:?} and fee {:?}", + hex::encode(event.sender), + event.recipient, + event.amount.as_u128(), + event.fee.as_u128() + )); + + #[cfg(feature = "log")] + sdk::log(&format!( + "Event's address {}, custodian address {}", + hex::encode(&event.eth_custodian_address), + hex::encode(&self.contract.eth_custodian_address), + )); + + assert_eq!( + event.eth_custodian_address, self.contract.eth_custodian_address, + "ERR_WRONG_EVENT_ADDRESS", + ); + assert!(event.amount > event.fee, "ERR_NOT_ENOUGH_BALANCE_FOR_FEE"); + + // Verify proof data with cross-contract call to prover account + #[cfg(feature = "log")] + sdk::log(&format!( + "Deposit verify_log_entry for prover: {}", + self.contract.prover_account, + )); + + // Do not skip bridge call. This is only used for development and diagnostics. + let skip_bridge_call = false.try_to_vec().unwrap(); + let mut proof_to_verify = raw_proof; + proof_to_verify.extend(skip_bridge_call); + let promise0 = sdk::promise_create( + self.contract.prover_account.as_bytes(), + b"verify_log_entry", + &proof_to_verify, + NO_DEPOSIT, + GAS_FOR_VERIFY_LOG_ENTRY, + ); + let predecessor_account_id = String::from_utf8(sdk::predecessor_account_id()).unwrap(); + + // Finalize deposit + let promise1 = match self.parse_event_message(&event.recipient) { + // Deposit to NEAR accounts + TokenMessageData::Near(account_id) => { + let data = FinishDepositCallArgs { + new_owner_id: account_id, + amount: event.amount.as_u128(), + proof_key: proof.get_key(), + relayer_id: predecessor_account_id, + fee: event.fee.as_u128(), + msg: None, + } + .try_to_vec() + .unwrap(); + + sdk::promise_then( + promise0, + &sdk::current_account_id(), + b"finish_deposit_near", + &data[..], + NO_DEPOSIT, + GAS_FOR_FINISH_DEPOSIT, + ) + } + // Deposit to Eth accounts + // fee is being minted in the `ft_on_transfer` callback method + TokenMessageData::Eth { address, message } => { + // Transfer to self and then transfer ETH in `ft_on_transfer` + // address - is NEAR account + let transfer_data = TransferCallCallArgs { + receiver_id: address, + amount: event.amount.as_u128(), + memo: None, + msg: self.set_message_for_on_transfer(event.fee, message), + } + .try_to_vec() + .unwrap(); + let current_account_id = String::from_utf8(sdk::current_account_id()).unwrap(); + // Send to self - current account id + let data = FinishDepositCallArgs { + new_owner_id: current_account_id, + amount: event.amount.as_u128(), + proof_key: proof.get_key(), + relayer_id: predecessor_account_id, + fee: event.fee.as_u128(), + msg: Some(transfer_data), + } + .try_to_vec() + .unwrap(); + + sdk::promise_then( + promise0, + &sdk::current_account_id(), + b"finish_deposit_near", + &data[..], + NO_DEPOSIT, + GAS_FOR_FINISH_DEPOSIT, + ) + } + }; + + sdk::promise_return(promise1); + } + + /// Finish deposit NEAR (private method) + /// NOTE: we should `record_proof` only after `mint` operation. The reason + /// is that in this case we only calculate the amount to be credited but + /// do not save it, however, if an error occurs during the calculation, + /// this will happen before `record_proof`. After that contract will save. + pub fn finish_deposit_near(&mut self) { + sdk::assert_private_call(); + let data: FinishDepositCallArgs = + FinishDepositCallArgs::try_from_slice(&sdk::read_input()).unwrap(); + #[cfg(feature = "log")] + sdk::log(&format!("Finish deposit NEAR amount: {}", data.amount)); + assert_eq!(sdk::promise_results_count(), 1); + + // Check promise results + let data0: Vec = match sdk::promise_result(0) { + PromiseResult::Successful(x) => x, + _ => sdk::panic_utf8(b"ERR_PROMISE_INDEX"), + }; + #[cfg(feature = "log")] + sdk::log("Check verification_success"); + let verification_success = bool::try_from_slice(&data0).unwrap(); + assert!(verification_success, "ERR_VERIFY_PROOF"); + + // Mint tokens to recipient minus fee + if let Some(msg) = data.msg { + // Mint - calculate new balances + self.mint_near(data.new_owner_id, data.amount); + // Store proof only after `mint` calculations + self.record_proof(&data.proof_key); + // Save new contract data + self.save_ft_contract(); + let transfer_call_args = TransferCallCallArgs::try_from_slice(&msg).unwrap(); + self.ft_transfer_call(transfer_call_args); + } else { + // Mint - calculate new balances + self.mint_near(data.new_owner_id.clone(), data.amount - data.fee); + self.mint_near(data.relayer_id, data.fee); + // Store proof only after `mint` calculations + self.record_proof(&data.proof_key); + // Save new contract data + self.save_ft_contract(); + } + } + + /// Internal logic for explicitly setting an eth balance (needed by ApplyBackend for Engine) + pub(crate) fn internal_set_eth_balance(&mut self, address: &Address, amount: &U256) { + // Call to `as_u128` here should be fine because u128::MAX is a value greater than + // all the Wei in existence, so a u128 should always be able to represent + // the balance of a single account. + self.ft + .internal_set_eth_balance(address.0, amount.as_u128()); + self.save_ft_contract(); + } + + /// Internal ETH withdraw ETH logic + pub(crate) fn internal_remove_eth(&mut self, address: &Address, amount: &U256) { + self.burn_eth(address.0, amount.as_u128()); + self.save_ft_contract(); + } + + /// Record used proof as hash key + fn record_proof(&mut self, key: &str) { + #[cfg(feature = "log")] + sdk::log(&format!("Record proof: {}", key)); + + assert!(!self.check_used_event(key), "ERR_PROOF_EXIST"); + self.save_used_event(key); + } + + /// Mint NEAR tokens + fn mint_near(&mut self, owner_id: AccountId, amount: Balance) { + #[cfg(feature = "log")] + sdk::log(&format!("Mint NEAR {} tokens for: {}", amount, owner_id)); + + if self.ft.accounts_get(&owner_id).is_none() { + self.ft.accounts_insert(&owner_id, 0); + } + self.ft.internal_deposit(&owner_id, amount); + } + + /// Mint ETH tokens + fn mint_eth(&mut self, owner_id: EthAddress, amount: Balance) { + #[cfg(feature = "log")] + sdk::log(&format!( + "Mint ETH {} tokens for: {}", + amount, + hex::encode(owner_id) + )); + self.ft.internal_deposit_eth(owner_id, amount); + } + + /// Burn NEAR tokens + fn burn_near(&mut self, owner_id: AccountId, amount: Balance) { + #[cfg(feature = "log")] + sdk::log(&format!("Burn NEAR {} tokens for: {}", amount, owner_id)); + self.ft.internal_withdraw(&owner_id, amount); + } + + /// Burn ETH tokens + fn burn_eth(&mut self, address: EthAddress, amount: Balance) { + #[cfg(feature = "log")] + sdk::log(&format!( + "Burn ETH {} tokens for: {}", + amount, + hex::encode(address) + )); + self.ft.internal_withdraw_eth(address, amount); + } + + /// Withdraw from NEAR accounts + /// NOTE: it should be without any log data + pub fn withdraw_near(&mut self) { + self.assert_not_paused(PAUSE_WITHDRAW); + + sdk::assert_one_yocto(); + let args = WithdrawCallArgs::try_from_slice(&sdk::read_input()).expect(ERR_FAILED_PARSE); + let res = WithdrawResult { + recipient_id: args.recipient_address, + amount: args.amount, + eth_custodian_address: self.contract.eth_custodian_address, + } + .try_to_vec() + .unwrap(); + // Burn tokens to recipient + let predecessor_account_id = String::from_utf8(sdk::predecessor_account_id()).unwrap(); + self.ft + .internal_withdraw(&predecessor_account_id, args.amount); + // Save new contract data + self.save_ft_contract(); + sdk::return_output(&res[..]); + } + + /// Return total supply of NEAR + ETH + pub fn ft_total_supply(&self) { + let total_supply = self.ft.ft_total_supply(); + sdk::return_output(&total_supply.to_string().as_bytes()); + #[cfg(feature = "log")] + sdk::log(&format!("Total supply: {}", total_supply)); + } + + /// Return total supply of NEAR + pub fn ft_total_supply_near(&self) { + let total_supply = self.ft.ft_total_supply_near(); + sdk::return_output(&total_supply.to_string().as_bytes()); + #[cfg(feature = "log")] + sdk::log(&format!("Total supply NEAR: {}", total_supply)); + } + + /// Return total supply of ETH + pub fn ft_total_supply_eth(&self) { + let total_supply = self.ft.ft_total_supply_eth(); + sdk::return_output(&total_supply.to_string().as_bytes()); + #[cfg(feature = "log")] + sdk::log(&format!("Total supply ETH: {}", total_supply)); + } + + /// Return balance of NEAR + pub fn ft_balance_of(&self) { + let args = BalanceOfCallArgs::from( + parse_json(&sdk::read_input()).expect_utf8(ERR_FAILED_PARSE.as_bytes()), + ); + let balance = self.ft.ft_balance_of(&args.account_id); + sdk::return_output(&balance.to_string().as_bytes()); + #[cfg(feature = "log")] + sdk::log(&format!( + "Balance of NEAR [{}]: {}", + args.account_id, balance + )); + } + + /// Return balance of ETH + pub fn ft_balance_of_eth(&self) { + let args = + BalanceOfEthCallArgs::try_from_slice(&sdk::read_input()).expect(ERR_FAILED_PARSE); + let balance = self.ft.internal_unwrap_balance_of_eth(args.address); + #[cfg(feature = "log")] + sdk::log(&format!( + "Balance of ETH [{}]: {}", + hex::encode(args.address), + balance + )); + sdk::return_output(&balance.to_string().as_bytes()); + } + + /// Transfer between NEAR accounts + pub fn ft_transfer(&mut self) { + sdk::assert_one_yocto(); + let args = TransferCallArgs::from( + parse_json(&sdk::read_input()).expect_utf8(ERR_FAILED_PARSE.as_bytes()), + ); + self.ft + .ft_transfer(&args.receiver_id, args.amount, &args.memo); + self.save_ft_contract(); + #[cfg(feature = "log")] + sdk::log(&format!( + "Transfer amount {} to {} success with memo: {:?}", + args.amount, args.receiver_id, args.memo + )); + } + + /// FT resolve transfer logic + pub fn ft_resolve_transfer(&mut self) { + sdk::assert_private_call(); + // Check if previous promise succeeded + assert_eq!(sdk::promise_results_count(), 1); + + let args = ResolveTransferCallArgs::try_from_slice(&sdk::read_input()).unwrap(); + let amount = self + .ft + .ft_resolve_transfer(&args.sender_id, &args.receiver_id, args.amount); + #[cfg(feature = "log")] + sdk::log(&format!( + "Resolve transfer from {} to {} success", + args.sender_id, args.receiver_id + )); + // `ft_resolve_transfer` can change `total_supply` so we should save the contract + self.save_ft_contract(); + sdk::return_output(&amount.to_string().as_bytes()); + } + + /// FT transfer call from sender account (invoker account) to receiver + /// We starting early checking for message data to avoid `ft_on_transfer` call panics + /// But we don't check relayer exists. If relayer doesn't exist we simply not mint/burn the amount of the fee + pub fn ft_transfer_call(&mut self, args: TransferCallCallArgs) { + #[cfg(feature = "log")] + sdk::log(&format!( + "Transfer call to {} amount {}", + args.receiver_id, args.amount, + )); + // Verify message data before `ft_on_transfer` call to avoid verification panics + let message_data = self.parse_on_transfer_message(&args.msg); + // Check is transfer amount > fee + assert!( + args.amount > message_data.fee.as_u128(), + "ERR_NOT_ENOUGH_BALANCE_FOR_FEE" + ); + + // Additional check overflow before process `ft_on_transfer` + // But don't check overflow for relayer + // Note: It can't overflow because the total supply doesn't change during transfer. + let amount_for_check = self + .ft + .internal_unwrap_balance_of_eth(message_data.recipient); + assert!(amount_for_check.checked_add(args.amount).is_some()); + assert!(self.ft.total_supply_eth.checked_add(args.amount).is_some()); + assert!(self.ft.total_supply.checked_add(args.amount).is_some()); + + self.ft + .ft_transfer_call(&args.receiver_id, args.amount, &args.memo, args.msg); + } + + /// FT storage deposit logic + pub fn storage_deposit(&mut self) { + let args = StorageDepositCallArgs::from( + parse_json(&sdk::read_input()).expect_utf8(ERR_FAILED_PARSE.as_bytes()), + ); + + let res = self + .ft + .storage_deposit(args.account_id.as_ref(), args.registration_only); + self.save_ft_contract(); + sdk::return_output(&res.to_json_bytes()); + } + + /// FT storage withdraw + pub fn storage_withdraw(&mut self) { + sdk::assert_one_yocto(); + let args = StorageWithdrawCallArgs::from( + parse_json(&sdk::read_input()).expect_utf8(ERR_FAILED_PARSE.as_bytes()), + ); + let res = self.ft.storage_withdraw(args.amount); + self.save_ft_contract(); + sdk::return_output(&res.to_json_bytes()); + } + + /// Get balance of storage + pub fn storage_balance_of(&self) { + let args = StorageBalanceOfCallArgs::from( + parse_json(&sdk::read_input()).expect_utf8(ERR_FAILED_PARSE.as_bytes()), + ); + sdk::return_output(&self.ft.storage_balance_of(&args.account_id).to_json_bytes()); + } + + /// ft_on_transfer callback function + #[allow(clippy::unnecessary_unwrap)] + pub fn ft_on_transfer(&mut self, engine: &Engine) { + #[cfg(feature = "log")] + sdk::log("Call ft_on_transfer"); + let args = FtOnTransfer::try_from_slice(&sdk::read_input()).expect(ERR_FAILED_PARSE); + let predecessor_account_id = String::from_utf8(sdk::predecessor_account_id()).unwrap(); + let current_account_id = String::from_utf8(sdk::current_account_id()).unwrap(); + // Parse message with specific rules + let message_data = self.parse_on_transfer_message(&args.msg); + + // Special case when predecessor_account_id is current_account_id + if current_account_id == predecessor_account_id { + let fee = message_data.fee.as_u128(); + self.burn_near(current_account_id, args.amount); + // Mint fee to relayer + let relayer = engine.get_relayer(&message_data.relayer.as_bytes()); + if fee > 0 && relayer.is_some() { + self.mint_eth(message_data.recipient, args.amount - fee); + let evm_relayer_address: EthAddress = relayer.unwrap().0; + self.mint_eth(evm_relayer_address, fee); + } else { + self.mint_eth(message_data.recipient, args.amount); + } + } else { + // Implement new scheme for ERC20 + todo!(); + } + self.save_ft_contract(); + sdk::return_output(0.to_string().as_bytes()); + } + + /// Get accounts counter for statistics. + /// It represents total unique accounts (all-time, including accounts which now have zero balance). + pub fn get_accounts_counter(&self) { + sdk::return_output(&self.ft.get_accounts_counter().to_le_bytes()); + } + + /// Save eth-connector contract data + fn save_ft_contract(&mut self) { + sdk::save_contract( + &Self::get_contract_key(&EthConnectorStorageId::FungibleToken), + &self.ft, + ); + } + + /// Generate key for used events from Prood + fn used_event_key(&self, key: &str) -> Vec { + let mut v = Self::get_contract_key(&EthConnectorStorageId::UsedEvent).to_vec(); + v.extend_from_slice(key.as_bytes()); + v + } + + /// Save already used event proof as hash key + fn save_used_event(&self, key: &str) { + sdk::save_contract(&self.used_event_key(key), &0u8); + } + + /// Check is event of proof already used + fn check_used_event(&self, key: &str) -> bool { + sdk::storage_has_key(&self.used_event_key(key)) + } + + /// Get Eth connector paused flags + pub fn get_paused_flags(&self) -> PausedMask { + self.get_paused() + } + + /// Set Eth connector paused flags + pub fn set_paused_flags(&mut self, args: PauseEthConnectorCallArgs) { + self.set_paused(args.paused_mask); + } +} + +impl AdminControlled for EthConnectorContract { + fn get_paused(&self) -> PausedMask { + self.paused_mask + } + + fn set_paused(&mut self, paused_mask: PausedMask) { + self.paused_mask = paused_mask; + sdk::save_contract( + &Self::get_contract_key(&EthConnectorStorageId::PausedMask), + &self.paused_mask, + ); + } +} diff --git a/src/deposit_event.rs b/src/deposit_event.rs new file mode 100644 index 000000000..ac92dad63 --- /dev/null +++ b/src/deposit_event.rs @@ -0,0 +1,65 @@ +use crate::prover::*; +use crate::types::*; +use alloc::{ + string::{String, ToString}, + vec, +}; +use ethabi::{EventParam, ParamType}; +use primitive_types::U256; + +const DEPOSITED_EVENT: &str = "Deposited"; + +/// Data that was emitted by Deposited event. +#[derive(Debug, PartialEq)] +pub struct DepositedEvent { + pub eth_custodian_address: EthAddress, + pub sender: EthAddress, + pub recipient: String, + pub amount: U256, + pub fee: U256, +} + +impl DepositedEvent { + #[allow(dead_code)] + fn event_params() -> EventParams { + vec![ + EventParam { + name: "sender".to_string(), + kind: ParamType::Address, + indexed: true, + }, + EventParam { + name: "recipient".to_string(), + kind: ParamType::String, + indexed: false, + }, + EventParam { + name: "amount".to_string(), + kind: ParamType::Uint(256), + indexed: false, + }, + EventParam { + name: "fee".to_string(), + kind: ParamType::Uint(256), + indexed: false, + }, + ] + } + + /// Parse raw log Etherium proof entry data. + pub fn from_log_entry_data(data: &[u8]) -> Self { + let event = EthEvent::fetch_log_entry_data(DEPOSITED_EVENT, Self::event_params(), data); + let sender = event.log.params[0].value.clone().into_address().unwrap().0; + + let recipient = event.log.params[1].value.clone().to_string(); + let amount = event.log.params[2].value.clone().into_uint().unwrap(); + let fee = event.log.params[3].value.clone().into_uint().unwrap(); + Self { + eth_custodian_address: event.eth_custodian_address, + sender, + recipient, + amount, + fee, + } + } +} diff --git a/src/engine.rs b/src/engine.rs index 2383ce10a..3391feb9b 100644 --- a/src/engine.rs +++ b/src/engine.rs @@ -4,8 +4,10 @@ use evm::executor::{MemoryStackState, StackExecutor, StackSubstateMetadata}; use evm::ExitFatal; use evm::{Config, CreateScheme, ExitError, ExitReason}; +use crate::connector::EthConnectorContract; use crate::map::LookupMap; use crate::parameters::{FunctionCallArgs, NewCallArgs, SubmitResult, ViewCallArgs}; + use crate::precompiles; use crate::prelude::{Address, TryInto, Vec, H256, U256}; use crate::sdk; @@ -92,6 +94,7 @@ impl ExitIntoResult for ExitReason { } } +#[derive(Debug)] pub enum EngineStateError { NotFound, DeserializationFailed, @@ -227,6 +230,9 @@ impl Engine { } pub fn remove_balance(address: &Address) { + let balance = Self::get_balance(address); + // Apply changes for eth-conenctor + EthConnectorContract::get_instance().internal_remove_eth(address, &balance.raw()); sdk::remove_storage(&address_to_key(KeyPrefix::Balance, address)) } @@ -303,7 +309,6 @@ impl Engine { executor.transact_create(origin, value.raw(), input, u64::MAX), address, ); - let is_succeed = status.is_succeed(); status.into_result()?; let used_gas = executor.used_gas(); @@ -363,17 +368,6 @@ impl Engine { Self::set_nonce(address, &new_nonce); } - #[cfg(feature = "testnet")] - /// Credits the address with 10 coins from the faucet. - pub fn credit(&self, address: &Address) -> EngineResult<()> { - let balance = Self::get_balance(address); - // Saturating adds are intentional - let new_balance = balance.saturating_add(U256::from(10_000_000_000_000_000_000)); - - Self::set_balance(address, &new_balance); - Ok(()) - } - pub fn view_with_args(&self, args: ViewCallArgs) -> EngineResult> { let origin = Address::from_slice(&args.sender); let contract = Address::from_slice(&args.address); @@ -548,7 +542,12 @@ impl ApplyBackend for Engine { reset_storage, } => { Engine::set_nonce(&address, &basic.nonce); + + // Apply changes for eth-connector + EthConnectorContract::get_instance() + .internal_set_eth_balance(&address, &basic.balance); Engine::set_balance(&address, &Wei::new(basic.balance)); + if let Some(code) = code { Engine::set_code(&address, &code) } diff --git a/src/fungible_token.rs b/src/fungible_token.rs new file mode 100644 index 000000000..361345cad --- /dev/null +++ b/src/fungible_token.rs @@ -0,0 +1,484 @@ +#[cfg(feature = "engine")] +use crate::parameters::*; +#[cfg(feature = "engine")] +use crate::prelude::{self, Ordering, String, ToString, Vec, U256}; +use crate::types::*; +#[cfg(feature = "engine")] +use crate::{connector, engine, sdk, storage}; +#[cfg(feature = "log")] +use alloc::format; +use borsh::{BorshDeserialize, BorshSerialize}; + +#[cfg(feature = "engine")] +const GAS_FOR_RESOLVE_TRANSFER: Gas = 5_000_000_000_000; +#[cfg(feature = "engine")] +const GAS_FOR_FT_ON_TRANSFER: Gas = 10_000_000_000_000; + +#[derive(Debug, Default, BorshDeserialize, BorshSerialize)] +pub struct FungibleToken { + /// Total supply of the all token. + pub total_supply: Balance, + + /// Total supply of the all ETH token. + pub total_supply_eth: Balance, + + /// The storage size in bytes for one account. + pub account_storage_usage: StorageUsage, +} + +#[cfg(feature = "engine")] +impl FungibleToken { + pub fn new() -> Self { + Self::default() + } + + /// Balance of NEAR tokens + pub fn internal_unwrap_balance_of(&self, account_id: &str) -> Balance { + match self.accounts_get(account_id) { + Some(balance) => u128::try_from_slice(&balance[..]).unwrap(), + None => sdk::panic_utf8(b"ERR_ACCOUNT_NOT_EXIST"), + } + } + + /// Balance of ETH tokens + pub fn internal_unwrap_balance_of_eth(&self, address: EthAddress) -> Balance { + engine::Engine::get_balance(&prelude::Address(address)) + .raw() + .as_u128() + } + + /// Internal deposit NEAR - NEP-141 + pub fn internal_deposit(&mut self, account_id: &str, amount: Balance) { + let balance = self.internal_unwrap_balance_of(account_id); + if let Some(new_balance) = balance.checked_add(amount) { + self.accounts_insert(account_id, new_balance); + self.total_supply = self + .total_supply + .checked_add(amount) + .expect("ERR_TOTAL_SUPPLY_OVERFLOW"); + } else { + sdk::panic_utf8(b"ERR_BALANCE_OVERFLOW"); + } + } + + /// Internal deposit ETH (nETH) + pub fn internal_deposit_eth(&mut self, address: EthAddress, amount: Balance) { + let balance = self.internal_unwrap_balance_of_eth(address); + if let Some(new_balance) = balance.checked_add(amount) { + engine::Engine::set_balance( + &prelude::Address(address), + &Wei::new(U256::from(new_balance)), + ); + self.total_supply_eth = self + .total_supply_eth + .checked_add(amount) + .expect("ERR_TOTAL_SUPPLY_OVERFLOW"); + self.total_supply = self + .total_supply + .checked_add(amount) + .expect("ERR_TOTAL_SUPPLY_OVERFLOW"); + } else { + sdk::panic_utf8(b"ERR_BALANCE_OVERFLOW"); + } + } + + /// Needed by engine to update balances after a transaction (see ApplyBackend for Engine) + pub(crate) fn internal_set_eth_balance(&mut self, address: EthAddress, new_balance: Balance) { + let current_balance = self.internal_unwrap_balance_of_eth(address); + match current_balance.cmp(&new_balance) { + Ordering::Less => { + // current_balance is smaller, so we need to deposit + let diff = new_balance - current_balance; + self.internal_deposit_eth(address, diff); + } + Ordering::Greater => { + // current_balance is larger, so we need to withdraw + let diff = current_balance - new_balance; + self.internal_withdraw_eth(address, diff); + } + // if the balances are equal then we do not need to do anything + Ordering::Equal => (), + } + } + + /// Withdraw NEAR tokens + pub fn internal_withdraw(&mut self, account_id: &str, amount: Balance) { + let balance = self.internal_unwrap_balance_of(account_id); + if let Some(new_balance) = balance.checked_sub(amount) { + self.accounts_insert(account_id, new_balance); + self.total_supply = self + .total_supply + .checked_sub(amount) + .expect("ERR_TOTAL_SUPPLY_OVERFLOW"); + } else { + sdk::panic_utf8(b"ERR_NOT_ENOUGH_BALANCE"); + } + } + + /// Withdraw ETH tokens + pub fn internal_withdraw_eth(&mut self, address: EthAddress, amount: Balance) { + let balance = self.internal_unwrap_balance_of_eth(address); + if let Some(new_balance) = balance.checked_sub(amount) { + engine::Engine::set_balance( + &prelude::Address(address), + &Wei::new(U256::from(new_balance)), + ); + self.total_supply_eth = self + .total_supply_eth + .checked_sub(amount) + .expect("ERR_TOTAL_SUPPLY_OVERFLOW"); + self.total_supply = self + .total_supply + .checked_sub(amount) + .expect("ERR_TOTAL_SUPPLY_OVERFLOW"); + } else { + sdk::panic_utf8(b"ERR_NOT_ENOUGH_BALANCE"); + } + } + + /// Transfer NEAR tokens + pub fn internal_transfer( + &mut self, + sender_id: &str, + receiver_id: &str, + amount: Balance, + #[allow(unused_variables)] memo: &Option, + ) { + assert_ne!( + sender_id, receiver_id, + "Sender and receiver should be different" + ); + assert!(amount > 0, "The amount should be a positive number"); + self.internal_withdraw(sender_id, amount); + self.internal_deposit(receiver_id, amount); + #[cfg(feature = "log")] + sdk::log(&format!( + "Transfer {} from {} to {}", + amount, sender_id, receiver_id + )); + #[cfg(feature = "log")] + if let Some(memo) = memo { + sdk::log(&format!("Memo: {}", memo)); + } + } + + pub fn internal_register_account(&mut self, account_id: &str) { + self.accounts_insert(account_id, 0) + } + + pub fn ft_transfer(&mut self, receiver_id: &str, amount: Balance, memo: &Option) { + sdk::assert_one_yocto(); + let predecessor_account_id = sdk::predecessor_account_id(); + let sender_id = str_from_slice(&predecessor_account_id); + self.internal_transfer(sender_id, receiver_id, amount, memo); + } + + pub fn ft_total_supply(&self) -> u128 { + self.total_supply + } + + pub fn ft_total_supply_near(&self) -> u128 { + self.total_supply - self.total_supply_eth + } + + pub fn ft_total_supply_eth(&self) -> u128 { + self.total_supply_eth + } + + pub fn ft_balance_of(&self, account_id: &str) -> u128 { + if let Some(data) = self.accounts_get(account_id) { + u128::try_from_slice(&data[..]).unwrap() + } else { + 0 + } + } + + pub fn ft_transfer_call( + &mut self, + receiver_id: &str, + amount: Balance, + memo: &Option, + msg: String, + ) { + let predecessor_account_id = sdk::predecessor_account_id(); + let sender_id = str_from_slice(&predecessor_account_id); + // Special case for Aurora transfer itself - we shouldn't transfer + if sender_id != receiver_id { + self.internal_transfer(sender_id, receiver_id, amount, memo); + } + // Note: This seems to be breaking the invariant that sender_id != receiver_id. You need to + // make sure the ft implementation doesn't break after this change. + + let data1 = FtOnTransfer { + amount, + msg, + receiver_id: receiver_id.to_string(), + } + .try_to_vec() + .unwrap(); + let account_id = String::from_utf8(sdk::current_account_id()).unwrap(); + let data2 = FtResolveTransfer { + receiver_id: receiver_id.to_string(), + amount, + current_account_id: account_id, + } + .try_to_vec() + .unwrap(); + // Initiating receiver's call and the callback + let promise0 = sdk::promise_create( + receiver_id.as_bytes(), + b"ft_on_transfer", + &data1[..], + connector::NO_DEPOSIT, + GAS_FOR_FT_ON_TRANSFER, + ); + let promise1 = sdk::promise_then( + promise0, + &sdk::current_account_id(), + b"ft_resolve_transfer", + &data2[..], + connector::NO_DEPOSIT, + GAS_FOR_RESOLVE_TRANSFER, + ); + sdk::promise_return(promise1); + } + + pub fn internal_ft_resolve_transfer( + &mut self, + sender_id: &str, + receiver_id: &str, + amount: Balance, + ) -> (u128, u128) { + // Get the unused amount from the `ft_on_transfer` call result. + let unused_amount = match sdk::promise_result(0) { + PromiseResult::NotReady => unreachable!(), + PromiseResult::Successful(value) => { + if let Ok(unused_amount) = String::from_utf8(value) { + let unused_amount = if let Ok(v) = unused_amount.parse::() { + v + } else { + amount + }; + if amount > unused_amount { + unused_amount + } else { + amount + } + } else { + amount + } + } + PromiseResult::Failed => amount, + }; + + if unused_amount > 0 { + let receiver_balance = if let Some(receiver_balance) = self.accounts_get(receiver_id) { + u128::try_from_slice(&receiver_balance[..]).unwrap() + } else { + self.accounts_insert(receiver_id, 0); + 0 + }; + if receiver_balance > 0 { + let refund_amount = if receiver_balance > unused_amount { + unused_amount + } else { + receiver_balance + }; + self.accounts_insert(receiver_id, receiver_balance - refund_amount); + #[cfg(feature = "log")] + sdk::log(&format!( + "Decrease receiver {} balance to: {}", + receiver_id, + receiver_balance - refund_amount + )); + + return if let Some(sender_balance) = self.accounts_get(sender_id) { + let sender_balance = u128::try_from_slice(&sender_balance[..]).unwrap(); + self.accounts_insert(sender_id, sender_balance + refund_amount); + #[cfg(feature = "log")] + sdk::log(&format!( + "Refund amount {} from {} to {}", + refund_amount, receiver_id, sender_id + )); + (amount - refund_amount, 0) + } else { + // Sender's account was deleted, so we need to burn tokens. + self.total_supply -= refund_amount; + #[cfg(feature = "log")] + sdk::log("The account of the sender was deleted"); + (amount, refund_amount) + }; + } + } + (amount, 0) + } + + pub fn ft_resolve_transfer( + &mut self, + sender_id: &str, + receiver_id: &str, + amount: u128, + ) -> u128 { + self.internal_ft_resolve_transfer(sender_id, receiver_id, amount) + .0 + } + + pub fn internal_storage_unregister( + &mut self, + force: Option, + ) -> Option<(AccountId, Balance)> { + sdk::assert_one_yocto(); + let account_id_key = sdk::predecessor_account_id(); + let account_id = str_from_slice(&account_id_key); + let force = force.unwrap_or(false); + if let Some(balance) = self.accounts_get(account_id) { + let balance = u128::try_from_slice(&balance[..]).unwrap(); + if balance == 0 || force { + self.accounts_remove(account_id); + self.total_supply -= balance; + let amount = self.storage_balance_bounds().min + 1; + let promise0 = sdk::promise_batch_create(&account_id_key); + sdk::promise_batch_action_transfer(promise0, amount); + Some((account_id.to_string(), balance)) + } else { + sdk::panic_utf8(b"ERR_FAILED_UNREGISTER_ACCOUNT_POSITIVE_BALANCE") + } + } else { + #[cfg(feature = "log")] + sdk::log(&format!("The account {} is not registered", &account_id)); + None + } + } + + pub fn storage_balance_bounds(&self) -> StorageBalanceBounds { + let required_storage_balance = + Balance::from(self.account_storage_usage) * sdk::storage_byte_cost(); + StorageBalanceBounds { + min: required_storage_balance, + max: Some(required_storage_balance), + } + } + + pub fn internal_storage_balance_of(&self, account_id: &str) -> Option { + if self.accounts_contains_key(account_id) { + Some(StorageBalance { + total: self.storage_balance_bounds().min, + available: 0, + }) + } else { + None + } + } + + pub fn storage_balance_of(&self, account_id: &str) -> StorageBalance { + self.internal_storage_balance_of(account_id) + .unwrap_or_default() + } + + // `registration_only` doesn't affect the implementation for vanilla fungible token. + #[allow(unused_variables)] + pub fn storage_deposit( + &mut self, + account_id: Option<&AccountId>, + registration_only: Option, + ) -> StorageBalance { + let amount: Balance = sdk::attached_deposit(); + let predecessor_account_id = String::from_utf8(sdk::predecessor_account_id()).unwrap(); + let account_id = account_id.unwrap_or(&predecessor_account_id); + if self.accounts_contains_key(account_id) { + #[cfg(feature = "log")] + sdk::log("The account is already registered, refunding the deposit"); + if amount > 0 { + let promise0 = sdk::promise_batch_create(&sdk::predecessor_account_id()); + sdk::promise_batch_action_transfer(promise0, amount); + } + } else { + let min_balance = self.storage_balance_bounds().min; + if amount < min_balance { + #[cfg(feature = "log")] + sdk::panic_utf8(b"ERR_ATTACHED_DEPOSIT_NOT_ENOUGH"); + } + + self.internal_register_account(account_id); + let refund = amount - min_balance; + if refund > 0 { + let promise0 = sdk::promise_batch_create(&sdk::predecessor_account_id()); + sdk::promise_batch_action_transfer(promise0, refund); + } + } + self.internal_storage_balance_of(account_id).unwrap() + } + + #[allow(dead_code)] + pub fn storage_unregister(&mut self, force: Option) -> bool { + self.internal_storage_unregister(force).is_some() + } + + pub fn storage_withdraw(&mut self, amount: Option) -> StorageBalance { + let predecessor_account_id_bytes = sdk::predecessor_account_id(); + let predecessor_account_id = str_from_slice(&predecessor_account_id_bytes); + if let Some(storage_balance) = self.internal_storage_balance_of(predecessor_account_id) { + match amount { + Some(amount) if amount > 0 => { + sdk::panic_utf8(b"ERR_WRONG_AMOUNT"); + } + _ => storage_balance, + } + } else { + sdk::panic_utf8(b"ERR_ACCOUNT_NOT_REGISTERED"); + } + } + + /// Insert account. + /// Calculate total unique accounts + pub fn accounts_insert(&self, account_id: &str, amount: Balance) { + if !self.accounts_contains_key(account_id) { + let key = Self::get_statistic_key(); + let accounts_counter = sdk::read_u64(&key) + .unwrap_or(Ok(0)) + .unwrap_or(0) + .checked_add(1) + .expect("ERR_ACCOUNTS_COUNTER_OVERFLOW"); + sdk::write_storage(&key, &accounts_counter.to_le_bytes()); + } + sdk::save_contract(&Self::account_to_key(account_id), &amount); + } + + /// Get accounts counter for statistics + /// It represents total unique accounts. + pub fn get_accounts_counter(&self) -> u64 { + sdk::read_u64(&Self::get_statistic_key()) + .unwrap_or(Ok(0)) + .unwrap_or(0) + } + + fn accounts_contains_key(&self, account_id: &str) -> bool { + sdk::storage_has_key(&Self::account_to_key(account_id)) + } + + fn accounts_remove(&self, account_id: &str) { + sdk::remove_storage(&Self::account_to_key(account_id)) + } + + pub fn accounts_get(&self, account_id: &str) -> Option> { + sdk::read_storage(&Self::account_to_key(account_id)) + } + + /// Fungible token key + fn account_to_key(account_id: &str) -> Vec { + let mut key = storage::bytes_to_key( + storage::KeyPrefix::EthConnector, + &[storage::EthConnectorStorageId::FungibleToken as u8], + ); + key.extend_from_slice(account_id.as_bytes()); + key + } + + /// Key for store contract statistics data + fn get_statistic_key() -> Vec { + storage::bytes_to_key( + storage::KeyPrefix::EthConnector, + &[storage::EthConnectorStorageId::StatisticsAuroraAccountsCounter as u8], + ) + } +} diff --git a/src/json.rs b/src/json.rs index de4c611a7..03e08c40a 100644 --- a/src/json.rs +++ b/src/json.rs @@ -1,13 +1,11 @@ use super::prelude::*; use crate::sdk; +use crate::types::ERR_FAILED_PARSE; use alloc::collections::BTreeMap; use core::convert::From; use rjson::{Array, Null, Object, Value}; -#[allow(dead_code)] -pub const FAILED_PARSE: &[u8; 22] = b"\0ERR_FAILED_PARSE_JSON"; - pub enum JsonValue { Null, Number(f64), @@ -69,7 +67,7 @@ impl JsonValue { pub fn parse_u8(v: &JsonValue) -> u8 { match v { JsonValue::Number(n) => *n as u8, - _ => sdk::panic_utf8(FAILED_PARSE), + _ => sdk::panic_utf8(ERR_FAILED_PARSE.as_bytes()), } } diff --git a/src/lib.rs b/src/lib.rs index 27b734a2d..eb4cb5364 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,5 +1,6 @@ #![cfg_attr(not(feature = "std"), no_std)] #![cfg_attr(not(feature = "std"), feature(alloc_error_handler))] +#![cfg_attr(feature = "log", feature(panic_info_message))] #[cfg(not(feature = "std"))] extern crate alloc; @@ -15,14 +16,24 @@ pub mod storage; pub mod transaction; pub mod types; +#[cfg(feature = "engine")] +mod admin_controlled; +#[cfg(feature = "engine")] +mod connector; +#[cfg(feature = "engine")] +mod deposit_event; #[cfg(feature = "engine")] pub mod engine; -#[cfg(feature = "contract")] +#[cfg(any(feature = "engine", test))] +mod fungible_token; +#[cfg(feature = "engine")] mod json; -#[cfg(feature = "contract")] +#[cfg(feature = "engine")] mod log_entry; mod precompiles; #[cfg(feature = "engine")] +mod prover; +#[cfg(feature = "engine")] pub mod sdk; #[cfg(test)] @@ -38,8 +49,24 @@ static ALLOC: wee_alloc::WeeAlloc = wee_alloc::WeeAlloc::INIT; #[cfg(target_arch = "wasm32")] #[panic_handler] +#[cfg_attr(not(feature = "log"), allow(unused_variables))] #[no_mangle] -pub unsafe fn on_panic(_info: &::core::panic::PanicInfo) -> ! { +pub unsafe fn on_panic(info: &::core::panic::PanicInfo) -> ! { + #[cfg(feature = "log")] + { + use alloc::{format, string::ToString}; + if let Some(msg) = info.message() { + let msg = if let Some(log) = info.location() { + format!("{} [{}]", msg, log) + } else { + msg.to_string() + }; + sdk::panic_utf8(msg.as_bytes()); + } else if let Some(log) = info.location() { + sdk::panic_utf8(log.to_string().as_bytes()); + } + } + ::core::arch::wasm32::unreachable(); } @@ -52,16 +79,20 @@ pub unsafe fn on_alloc_error(_: core::alloc::Layout) -> ! { #[cfg(feature = "contract")] mod contract { - use borsh::BorshSerialize; + use borsh::{BorshDeserialize, BorshSerialize}; + use crate::connector::EthConnectorContract; use crate::engine::{Engine, EngineResult, EngineState}; #[cfg(feature = "evm_bully")] use crate::parameters::{BeginBlockArgs, BeginChainArgs}; - use crate::parameters::{FunctionCallArgs, GetStorageAtArgs, NewCallArgs, ViewCallArgs}; + use crate::parameters::{ + ExpectUtf8, FunctionCallArgs, GetStorageAtArgs, InitCallArgs, NewCallArgs, + PauseEthConnectorCallArgs, SetContractDataCallArgs, TransferCallCallArgs, ViewCallArgs, + }; use crate::prelude::{Address, H256, U256}; use crate::sdk; use crate::storage::{bytes_to_key, KeyPrefix}; - use crate::types::{near_account_to_evm_address, u256_to_arr}; + use crate::types::{near_account_to_evm_address, u256_to_arr, ERR_FAILED_PARSE}; const CODE_KEY: &[u8; 4] = b"CODE"; const CODE_STAGE_KEY: &[u8; 10] = b"CODE_STAGE"; @@ -251,20 +282,6 @@ mod contract { .sdk_process(); } - #[cfg(feature = "testnet")] - #[no_mangle] - pub extern "C" fn make_it_rain() { - let input = sdk::read_input(); - let dest_address = Address::from_slice(&input); - let source_address = predecessor_address(); - let engine = Engine::new(source_address).sdk_unwrap(); - - engine.increment_nonce(&source_address); - - let result = engine.credit(&dest_address); - result.map(|_f| Vec::new()).sdk_process(); - } - #[no_mangle] pub extern "C" fn register_relayer() { let relayer_address = sdk::read_input_arr20().sdk_unwrap(); @@ -276,19 +293,6 @@ mod contract { ); } - /// Allow receiving NEP141 tokens to the EVM contract - #[no_mangle] - pub extern "C" fn ft_on_transfer() { - #[allow(clippy::if_same_then_else)] - if sdk::predecessor_account_id() == sdk::current_account_id() { - // TODO(#59) ETH transfer - todo!(); - } else { - // TODO(#51) ERC20 transfer - todo!(); - } - } - /// /// NONMUTATIVE METHODS /// @@ -361,6 +365,141 @@ mod contract { // TODO: https://github.com/aurora-is-near/aurora-engine/issues/2 } + #[no_mangle] + pub extern "C" fn new_eth_connector() { + // Only the owner can initialize the EthConnector + sdk::assert_private_call(); + + let args = InitCallArgs::try_from_slice(&sdk::read_input()).expect(ERR_FAILED_PARSE); + + EthConnectorContract::init_contract(args); + } + + #[no_mangle] + pub extern "C" fn set_eth_connector_contract_data() { + // Only the owner can set the EthConnector contract data + sdk::assert_private_call(); + + let args = + SetContractDataCallArgs::try_from_slice(&sdk::read_input()).expect(ERR_FAILED_PARSE); + + EthConnectorContract::set_contract_data(args); + } + + #[no_mangle] + pub extern "C" fn withdraw() { + EthConnectorContract::get_instance().withdraw_near() + } + + #[no_mangle] + pub extern "C" fn deposit() { + EthConnectorContract::get_instance().deposit() + } + + #[no_mangle] + pub extern "C" fn finish_deposit_near() { + EthConnectorContract::get_instance().finish_deposit_near(); + } + + #[no_mangle] + pub extern "C" fn ft_total_supply() { + EthConnectorContract::get_instance().ft_total_supply(); + } + + #[no_mangle] + pub extern "C" fn ft_total_supply_near() { + EthConnectorContract::get_instance().ft_total_supply_near(); + } + + #[no_mangle] + pub extern "C" fn ft_total_supply_eth() { + EthConnectorContract::get_instance().ft_total_supply_eth(); + } + + #[no_mangle] + pub extern "C" fn ft_balance_of() { + EthConnectorContract::get_instance().ft_balance_of(); + } + + #[no_mangle] + pub extern "C" fn ft_balance_of_eth() { + EthConnectorContract::get_instance().ft_balance_of_eth(); + } + + #[no_mangle] + pub extern "C" fn ft_transfer() { + EthConnectorContract::get_instance().ft_transfer(); + } + + #[no_mangle] + pub extern "C" fn ft_resolve_transfer() { + EthConnectorContract::get_instance().ft_resolve_transfer(); + } + + #[no_mangle] + pub extern "C" fn ft_transfer_call() { + use crate::json::parse_json; + + // Check is payable + sdk::assert_one_yocto(); + + let args = TransferCallCallArgs::from( + parse_json(&sdk::read_input()).expect_utf8(ERR_FAILED_PARSE.as_bytes()), + ); + EthConnectorContract::get_instance().ft_transfer_call(args); + } + + #[no_mangle] + pub extern "C" fn storage_deposit() { + EthConnectorContract::get_instance().storage_deposit() + } + + #[no_mangle] + pub extern "C" fn storage_withdraw() { + EthConnectorContract::get_instance().storage_withdraw() + } + + #[no_mangle] + pub extern "C" fn storage_balance_of() { + EthConnectorContract::get_instance().storage_balance_of() + } + + #[no_mangle] + pub extern "C" fn ft_on_transfer() { + let engine = Engine::new(predecessor_address()).sdk_unwrap(); + EthConnectorContract::get_instance().ft_on_transfer(&engine) + } + + #[no_mangle] + pub extern "C" fn get_paused_flags() { + let paused_flags = EthConnectorContract::get_instance().get_paused_flags(); + let data = paused_flags.try_to_vec().expect(ERR_FAILED_PARSE); + sdk::return_output(&data[..]); + } + + #[no_mangle] + pub extern "C" fn set_paused_flags() { + sdk::assert_private_call(); + + let args = + PauseEthConnectorCallArgs::try_from_slice(&sdk::read_input()).expect(ERR_FAILED_PARSE); + EthConnectorContract::get_instance().set_paused_flags(args); + } + + #[no_mangle] + pub extern "C" fn get_accounts_counter() { + EthConnectorContract::get_instance().get_accounts_counter() + } + + #[cfg(feature = "integration-test")] + #[no_mangle] + pub extern "C" fn verify_log_entry() { + #[cfg(feature = "log")] + sdk::log("Call from verify_log_entry"); + let data = true.try_to_vec().unwrap(); + sdk::return_output(&data[..]); + } + /// /// Utility methods. /// diff --git a/src/parameters.rs b/src/parameters.rs index 3207eb9fc..7b1831427 100644 --- a/src/parameters.rs +++ b/src/parameters.rs @@ -1,7 +1,21 @@ use borsh::{BorshDeserialize, BorshSerialize}; +#[cfg(feature = "engine")] +use crate::admin_controlled::PausedMask; +#[cfg(feature = "engine")] +use crate::json; +#[cfg(feature = "engine")] +use crate::prelude::ToString; use crate::prelude::{String, Vec}; +#[cfg(feature = "engine")] +use crate::prover::Proof; +#[cfg(feature = "engine")] +use crate::sdk; +#[cfg(feature = "engine")] +use crate::types::Balance; use crate::types::{AccountId, RawAddress, RawH256, RawU256}; +#[cfg(feature = "engine")] +use crate::types::{EthAddress, ERR_FAILED_PARSE}; use evm::backend::Log; /// Borsh-encoded parameters for the `new` function. @@ -121,6 +135,297 @@ pub struct BeginBlockArgs { pub gaslimit: RawU256, } +/// Eth-connector deposit arguments +#[cfg(feature = "engine")] +#[derive(BorshSerialize, BorshDeserialize)] +pub struct DepositCallArgs { + /// Proof data + pub proof: Proof, + /// Optional relayer address + pub relayer_eth_account: Option, +} + +/// withdraw result for eth-connector +#[cfg(feature = "engine")] +#[derive(BorshSerialize)] +pub struct WithdrawResult { + pub amount: Balance, + pub recipient_id: RawAddress, + pub eth_custodian_address: RawAddress, +} + +/// ft_on_transfer eth-connector call args +#[cfg(feature = "engine")] +#[derive(BorshSerialize, BorshDeserialize)] +pub struct FtOnTransfer { + pub amount: Balance, + pub msg: String, + pub receiver_id: AccountId, +} + +/// ft_resolve_transfer eth-connector call args +#[cfg(feature = "engine")] +#[derive(BorshSerialize)] +pub struct FtResolveTransfer { + pub receiver_id: AccountId, + pub amount: Balance, + pub current_account_id: AccountId, +} + +/// Fungible token storage balance +#[cfg(feature = "engine")] +#[derive(Default)] +pub struct StorageBalance { + pub total: Balance, + pub available: Balance, +} + +#[cfg(feature = "engine")] +impl StorageBalance { + pub fn to_json_bytes(&self) -> Vec { + use alloc::format; + format!( + "{{\"total\": \"{}\", \"available\": \"{}\",}}", + self.total.to_string(), + self.available.to_string() + ) + .as_bytes() + .to_vec() + } +} + +/// resolve_transfer eth-connector call args +#[cfg(feature = "engine")] +#[derive(BorshSerialize, BorshDeserialize)] +pub struct ResolveTransferCallArgs { + pub sender_id: AccountId, + pub amount: Balance, + pub receiver_id: AccountId, +} + +/// Finish deposit NEAR eth-connector call args +#[cfg(feature = "engine")] +#[derive(BorshSerialize, BorshDeserialize)] +pub struct FinishDepositCallArgs { + pub new_owner_id: AccountId, + pub amount: Balance, + pub proof_key: String, + pub relayer_id: AccountId, + pub fee: Balance, + pub msg: Option>, +} + +/// Deposit ETH args +#[cfg(feature = "engine")] +#[derive(Default, BorshDeserialize, BorshSerialize, Clone)] +pub struct DepositEthCallArgs { + pub proof: Proof, + pub relayer_eth_account: EthAddress, +} + +/// Finish deposit NEAR eth-connector call args +#[cfg(feature = "engine")] +#[derive(BorshSerialize, BorshDeserialize)] +pub struct FinishDepositEthCallArgs { + pub new_owner_id: EthAddress, + pub amount: Balance, + pub fee: Balance, + pub relayer_eth_account: AccountId, + pub proof: Proof, +} + +/// Eth-connector initial args +#[derive(BorshSerialize, BorshDeserialize)] +pub struct InitCallArgs { + pub prover_account: AccountId, + pub eth_custodian_address: AccountId, +} + +/// Eth-connector Set contract data call args +pub type SetContractDataCallArgs = InitCallArgs; + +/// transfer eth-connector call args +#[cfg(feature = "engine")] +#[derive(BorshSerialize, BorshDeserialize)] +pub struct TransferCallCallArgs { + pub receiver_id: AccountId, + pub amount: Balance, + pub memo: Option, + pub msg: String, +} + +#[cfg(feature = "engine")] +impl From for TransferCallCallArgs { + fn from(v: json::JsonValue) -> Self { + Self { + receiver_id: v + .string("receiver_id") + .expect_utf8(ERR_FAILED_PARSE.as_bytes()), + amount: v.u128("amount").expect_utf8(ERR_FAILED_PARSE.as_bytes()), + memo: v.string("memo").ok(), + msg: v.string("msg").expect_utf8(ERR_FAILED_PARSE.as_bytes()), + } + } +} + +/// storage_balance_of eth-connector call args +#[cfg(feature = "engine")] +#[derive(BorshSerialize, BorshDeserialize)] +pub struct StorageBalanceOfCallArgs { + pub account_id: AccountId, +} + +#[cfg(feature = "engine")] +impl From for StorageBalanceOfCallArgs { + fn from(v: json::JsonValue) -> Self { + Self { + account_id: v + .string("account_id") + .expect_utf8(ERR_FAILED_PARSE.as_bytes()), + } + } +} + +/// storage_deposit eth-connector call args +#[cfg(feature = "engine")] +#[derive(BorshSerialize, BorshDeserialize)] +pub struct StorageDepositCallArgs { + pub account_id: Option, + pub registration_only: Option, +} + +#[cfg(feature = "engine")] +impl From for StorageDepositCallArgs { + fn from(v: json::JsonValue) -> Self { + Self { + account_id: v.string("account_id").ok(), + registration_only: v.bool("registration_only").ok(), + } + } +} + +/// storage_withdraw eth-connector call args +#[cfg(feature = "engine")] +#[derive(BorshSerialize, BorshDeserialize)] +pub struct StorageWithdrawCallArgs { + pub amount: Option, +} + +#[cfg(feature = "engine")] +impl From for StorageWithdrawCallArgs { + fn from(v: json::JsonValue) -> Self { + Self { + amount: v.u128("amount").ok(), + } + } +} + +/// transfer args for json invocation +#[cfg(feature = "engine")] +#[derive(BorshSerialize, BorshDeserialize)] +pub struct TransferCallArgs { + pub receiver_id: AccountId, + pub amount: Balance, + pub memo: Option, +} + +#[cfg(feature = "engine")] +impl From for TransferCallArgs { + fn from(v: json::JsonValue) -> Self { + Self { + receiver_id: v + .string("receiver_id") + .expect_utf8(ERR_FAILED_PARSE.as_bytes()), + amount: v.u128("amount").expect_utf8(ERR_FAILED_PARSE.as_bytes()), + memo: v.string("memo").ok(), + } + } +} + +/// withdraw NEAR eth-connector call args +#[cfg(feature = "engine")] +#[derive(BorshSerialize, BorshDeserialize)] +pub struct WithdrawCallArgs { + pub recipient_address: EthAddress, + pub amount: Balance, +} + +/// balance_of args for json invocation +#[cfg(feature = "engine")] +#[derive(BorshSerialize, BorshDeserialize)] +pub struct BalanceOfCallArgs { + pub account_id: AccountId, +} + +#[cfg(feature = "engine")] +#[derive(BorshSerialize, BorshDeserialize)] +pub struct BalanceOfEthCallArgs { + pub address: EthAddress, +} + +#[cfg(feature = "engine")] +impl From for BalanceOfCallArgs { + fn from(v: json::JsonValue) -> Self { + Self { + account_id: v + .string("account_id") + .expect_utf8(ERR_FAILED_PARSE.as_bytes()), + } + } +} + +#[cfg(feature = "engine")] +#[derive(BorshSerialize, BorshDeserialize)] +pub struct RegisterRelayerCallArgs { + pub address: EthAddress, +} + +#[cfg(feature = "engine")] +#[derive(BorshSerialize, BorshDeserialize)] +pub struct PauseEthConnectorCallArgs { + pub paused_mask: PausedMask, +} + +#[cfg(feature = "engine")] +pub trait ExpectUtf8 { + fn expect_utf8(self, message: &[u8]) -> T; +} + +#[cfg(feature = "engine")] +impl ExpectUtf8 for Option { + fn expect_utf8(self, message: &[u8]) -> T { + match self { + Some(t) => t, + None => sdk::panic_utf8(message), + } + } +} + +#[cfg(feature = "engine")] +impl ExpectUtf8 for core::result::Result { + fn expect_utf8(self, message: &[u8]) -> T { + match self { + Ok(t) => t, + Err(_) => sdk::panic_utf8(message), + } + } +} + +#[cfg(feature = "engine")] +impl From for ResolveTransferCallArgs { + fn from(v: json::JsonValue) -> Self { + Self { + sender_id: v + .string("sender_id") + .expect_utf8(ERR_FAILED_PARSE.as_bytes()), + receiver_id: v + .string("receiver_id") + .expect_utf8(ERR_FAILED_PARSE.as_bytes()), + amount: v.u128("amount").expect_utf8(ERR_FAILED_PARSE.as_bytes()), + } + } +} + #[cfg(test)] mod tests { use super::*; diff --git a/src/precompiles/mod.rs b/src/precompiles/mod.rs index 8a29218e6..48f0b7073 100644 --- a/src/precompiles/mod.rs +++ b/src/precompiles/mod.rs @@ -3,6 +3,7 @@ mod bn128; mod hash; mod identity; mod modexp; +#[cfg(feature = "exit-precompiles")] mod native; mod secp256k1; @@ -11,6 +12,7 @@ use crate::precompiles::bn128::{BN128Add, BN128Mul, BN128Pair}; use crate::precompiles::hash::{RIPEMD160, SHA256}; use crate::precompiles::identity::Identity; use crate::precompiles::modexp::ModExp; +#[cfg(feature = "exit-precompiles")] use crate::precompiles::native::{ExitToEthereum, ExitToNear}; pub(crate) use crate::precompiles::secp256k1::ecrecover; use crate::precompiles::secp256k1::ECRecover; @@ -80,7 +82,9 @@ pub fn homestead_precompiles( ECRecover::ADDRESS => Some(ECRecover::run(input, target_gas, context)), SHA256::ADDRESS => Some(SHA256::run(input, target_gas, context)), RIPEMD160::ADDRESS => Some(RIPEMD160::run(input, target_gas, context)), + #[cfg(feature = "exit-precompiles")] ExitToNear::ADDRESS => Some(ExitToNear::run(input, target_gas, context)), + #[cfg(feature = "exit-precompiles")] ExitToEthereum::ADDRESS => Some(ExitToEthereum::run(input, target_gas, context)), _ => None, } @@ -108,7 +112,9 @@ pub fn byzantium_precompiles( bn128::addresses::ADD => Some(BN128Add::::run(input, target_gas, context)), bn128::addresses::MUL => Some(BN128Mul::::run(input, target_gas, context)), bn128::addresses::PAIR => Some(BN128Pair::::run(input, target_gas, context)), + #[cfg(feature = "exit-precompiles")] ExitToNear::ADDRESS => Some(ExitToNear::run(input, target_gas, context)), + #[cfg(feature = "exit-precompiles")] ExitToEthereum::ADDRESS => Some(ExitToEthereum::run(input, target_gas, context)), _ => None, } @@ -137,7 +143,9 @@ pub fn istanbul_precompiles( bn128::addresses::MUL => Some(BN128Mul::::run(input, target_gas, context)), bn128::addresses::PAIR => Some(BN128Pair::::run(input, target_gas, context)), Blake2F::ADDRESS => Some(Blake2F::run(input, target_gas, context)), + #[cfg(feature = "exit-precompiles")] ExitToNear::ADDRESS => Some(ExitToNear::run(input, target_gas, context)), + #[cfg(feature = "exit-precompiles")] ExitToEthereum::ADDRESS => Some(ExitToEthereum::run(input, target_gas, context)), _ => None, } @@ -166,9 +174,9 @@ pub fn berlin_precompiles( bn128::addresses::MUL => Some(BN128Mul::::run(input, target_gas, context)), bn128::addresses::PAIR => Some(BN128Pair::::run(input, target_gas, context)), Blake2F::ADDRESS => Some(Blake2F::run(input, target_gas, context)), - #[cfg(feature = "contract")] + #[cfg(feature = "exit-precompiles")] ExitToNear::ADDRESS => Some(ExitToNear::run(input, target_gas, context)), - #[cfg(feature = "contract")] + #[cfg(feature = "exit-precompiles")] ExitToEthereum::ADDRESS => Some(ExitToEthereum::run(input, target_gas, context)), _ => None, } diff --git a/src/precompiles/native.rs b/src/precompiles/native.rs index 33adcfcde..3163e4698 100644 --- a/src/precompiles/native.rs +++ b/src/precompiles/native.rs @@ -1,9 +1,14 @@ use evm::{Context, ExitError, ExitSucceed}; use super::{Precompile, PrecompileResult}; -use crate::prelude::{Cow, String, ToString, Vec, U256}; -use crate::types::AccountId; - +use crate::prelude::Vec; +#[cfg(feature = "exit-precompiles")] +use crate::{ + prelude::{is_valid_account_id, Cow, String, U256}, + types::AccountId, +}; + +#[cfg(feature = "exit-precompiles")] mod costs { use crate::types::Gas; @@ -22,47 +27,10 @@ mod costs { /// Get the current nep141 token associated with the current erc20 token. /// This will fail is none is associated. -fn get_nep141_from_erc20(_erc20_token: &[u8]) -> AccountId { +#[cfg(feature = "exit-precompiles")] +fn get_nep141_from_erc20(_erc20_token: &[u8]) -> Vec { // TODO(#51): Already implemented - "".to_string() -} - -/// The minimum length of a valid account ID. -const MIN_ACCOUNT_ID_LEN: u64 = 2; -/// The maximum length of a valid account ID. -const MAX_ACCOUNT_ID_LEN: u64 = 64; - -/// Returns `true` if the given account ID is valid and `false` otherwise. -/// -/// Taken from near-sdk-rs: -/// (https://github.com/near/near-sdk-rs/blob/42f62384c3acd024829501ee86e480917da03896/near-sdk/src/environment/env.rs#L816-L843) -pub fn is_valid_account_id(account_id: &[u8]) -> bool { - if (account_id.len() as u64) < MIN_ACCOUNT_ID_LEN - || (account_id.len() as u64) > MAX_ACCOUNT_ID_LEN - { - return false; - } - - // NOTE: We don't want to use Regex here, because it requires extra time to compile it. - // The valid account ID regex is /^(([a-z\d]+[-_])*[a-z\d]+\.)*([a-z\d]+[-_])*[a-z\d]+$/ - // Instead the implementation is based on the previous character checks. - - // We can safely assume that last char was a separator. - let mut last_char_is_separator = true; - - for c in account_id { - let current_char_is_separator = match *c { - b'a'..=b'z' | b'0'..=b'9' => false, - b'-' | b'_' | b'.' => true, - _ => return false, - }; - if current_char_is_separator && last_char_is_separator { - return false; - } - last_char_is_separator = current_char_is_separator; - } - // The account can't end as separator. - !last_char_is_separator + Vec::new() } pub struct ExitToNear; //TransferEthToNear @@ -81,8 +49,8 @@ impl Precompile for ExitToNear { Ok(costs::EXIT_TO_NEAR_GAS) } - #[cfg(not(feature = "contract"))] - fn run(input: &[u8], target_gas: u64, context: &Context) -> PrecompileResult { + #[cfg(not(feature = "exit-precompiles"))] + fn run(input: &[u8], target_gas: u64, _context: &Context) -> PrecompileResult { if Self::required_gas(input)? > target_gas { return Err(ExitError::OutOfGas); } @@ -90,7 +58,7 @@ impl Precompile for ExitToNear { Ok((ExitSucceed::Returned, Vec::new(), 0)) } - #[cfg(feature = "contract")] + #[cfg(feature = "exit-precompiles")] fn run(input: &[u8], target_gas: u64, context: &Context) -> PrecompileResult { if Self::required_gas(input)? > target_gas { return Err(ExitError::OutOfGas); @@ -104,9 +72,9 @@ impl Precompile for ExitToNear { if is_valid_account_id(input) { ( - String::from_utf8(crate::sdk::current_account_id()).unwrap(), + crate::sdk::current_account_id(), crate::prelude::format!( - r#"{{"receiver_id": "{}", "amount": "{}", "memo": null}}"#, + r#"{{"receiver_id": "{}", "amount": "{}"}}"#, String::from_utf8(input.to_vec()).unwrap(), context.apparent_value.as_u128() ), @@ -131,12 +99,14 @@ impl Precompile for ExitToNear { let amount = U256::from_big_endian(&input_mut[..32]).as_u128(); input_mut = &input_mut[32..]; + // TODO: You have to charge caller's account balance for this transfer. + if is_valid_account_id(input_mut) { let receiver_account_id: AccountId = String::from_utf8(input_mut.to_vec()).unwrap(); ( nep141_address, crate::prelude::format!( - r#"{{"receiver_id": "{}", "amount": "{}", "memo": null}}"#, + r#"{{"receiver_id": "{}", "amount": "{}"}}"#, receiver_account_id, amount ), @@ -149,7 +119,7 @@ impl Precompile for ExitToNear { }; let promise0 = crate::sdk::promise_create( - nep141_address, + &nep141_address, b"ft_transfer", args.as_bytes(), 1, @@ -178,8 +148,8 @@ impl Precompile for ExitToEthereum { Ok(costs::EXIT_TO_ETHEREUM_GAS) } - #[cfg(not(feature = "contract"))] - fn run(input: &[u8], target_gas: u64, context: &Context) -> PrecompileResult { + #[cfg(not(feature = "exit-precompiles"))] + fn run(input: &[u8], target_gas: u64, _context: &Context) -> PrecompileResult { if Self::required_gas(input)? > target_gas { return Err(ExitError::OutOfGas); } @@ -187,7 +157,7 @@ impl Precompile for ExitToEthereum { Ok((ExitSucceed::Returned, Vec::new(), 0)) } - #[cfg(feature = "contract")] + #[cfg(feature = "exit-precompiles")] fn run(input: &[u8], target_gas: u64, context: &Context) -> PrecompileResult { if Self::required_gas(input)? > target_gas { return Err(ExitError::OutOfGas); @@ -203,7 +173,7 @@ impl Precompile for ExitToEthereum { if eth_recipient.len() == 20 { ( - String::from_utf8(crate::sdk::current_account_id()).unwrap(), + crate::sdk::current_account_id(), crate::prelude::format!( r#"{{"amount": "{}", "recipient": "{}"}}"#, context.apparent_value.as_u128(), @@ -230,6 +200,8 @@ impl Precompile for ExitToEthereum { let amount = U256::from_big_endian(&input_mut[..32]).as_u128(); input_mut = &input_mut[32..]; + // TODO: Charge the caller's account balance? + if input_mut.len() == 20 { // Parse ethereum address in hex let eth_recipient: String = hex::encode(input_mut.to_vec()); @@ -248,7 +220,7 @@ impl Precompile for ExitToEthereum { }; let promise0 = crate::sdk::promise_create( - nep141_address, + &nep141_address, b"withdraw", serialized_args.as_bytes(), 1, diff --git a/src/prelude.rs b/src/prelude.rs index 86dd659c9..1abd19a5c 100644 --- a/src/prelude.rs +++ b/src/prelude.rs @@ -12,6 +12,7 @@ pub use alloc::{ }; #[cfg(not(feature = "std"))] pub use core::{ + cmp::Ordering, convert::TryInto, marker::PhantomData, mem, @@ -23,6 +24,7 @@ pub use std::{ borrow::Cow::Borrowed, borrow::ToOwned, boxed::Box, + cmp::Ordering, collections::HashMap, convert::TryInto, error::Error, @@ -45,3 +47,41 @@ pub type Address = H160; pub fn Address(input: [u8; 20]) -> Address { H160(input) } + +/// The minimum length of a valid account ID. +const MIN_ACCOUNT_ID_LEN: u64 = 2; +/// The maximum length of a valid account ID. +const MAX_ACCOUNT_ID_LEN: u64 = 64; + +/// Returns `true` if the given account ID is valid and `false` otherwise. +/// +/// Taken from near-sdk-rs: +/// (https://github.com/near/near-sdk-rs/blob/42f62384c3acd024829501ee86e480917da03896/near-sdk/src/environment/env.rs#L816-L843) +pub fn is_valid_account_id(account_id: &[u8]) -> bool { + if (account_id.len() as u64) < MIN_ACCOUNT_ID_LEN + || (account_id.len() as u64) > MAX_ACCOUNT_ID_LEN + { + return false; + } + + // NOTE: We don't want to use Regex here, because it requires extra time to compile it. + // The valid account ID regex is /^(([a-z\d]+[-_])*[a-z\d]+\.)*([a-z\d]+[-_])*[a-z\d]+$/ + // Instead the implementation is based on the previous character checks. + + // We can safely assume that last char was a separator. + let mut last_char_is_separator = true; + + for c in account_id { + let current_char_is_separator = match *c { + b'a'..=b'z' | b'0'..=b'9' => false, + b'-' | b'_' | b'.' => true, + _ => return false, + }; + if current_char_is_separator && last_char_is_separator { + return false; + } + last_char_is_separator = current_char_is_separator; + } + // The account can't end as separator. + !last_char_is_separator +} diff --git a/src/prover.rs b/src/prover.rs new file mode 100644 index 000000000..864a66417 --- /dev/null +++ b/src/prover.rs @@ -0,0 +1,265 @@ +use super::prelude::*; +use super::sdk; +use crate::engine::Engine; +use crate::log_entry::LogEntry; +use crate::precompiles::ecrecover; +use crate::types::{AccountId, EthAddress}; +use alloc::{format, vec::Vec}; +use borsh::{BorshDeserialize, BorshSerialize}; +use ethabi::{Bytes, Event, EventParam, Hash, Log, RawLog, Token}; + +/// Validate Etherium address from string and return EthAddress +#[allow(dead_code)] +pub fn validate_eth_address(address: String) -> EthAddress { + let data = hex::decode(address).expect("ETH_ADDRESS_FAILED"); + assert_eq!(data.len(), 20, "ETH_WRONG_ADDRESS_LENGTH"); + let mut result = [0u8; 20]; + result.copy_from_slice(&data); + result +} + +/// Encodes vector of tokens using non-standard Packed mode into ABI.encodePacked() compliant vector of bytes. +pub fn encode_packed(tokens: &[Token]) -> Bytes { + tokens.iter().flat_map(encode_token_packed).collect() +} + +fn encode_token_packed(token: &Token) -> Vec { + match *token { + Token::Address(ref address) => { + let mut padded = [0u8; 32]; + padded[12..].copy_from_slice(address.as_ref()); + padded[..].to_vec() + } + Token::Bytes(ref bytes) => bytes.to_vec(), + Token::String(ref s) => s.as_bytes().to_vec(), + Token::FixedBytes(ref bytes) => bytes.to_vec(), + Token::Int(int) => { + let data: [u8; 32] = int.into(); + data[..].to_vec() + } + Token::Uint(uint) => { + let data: [u8; 32] = uint.into(); + data[..].to_vec() + } + Token::Bool(b) => { + vec![b.into()] + } + Token::Array(_) | Token::FixedArray(_) | Token::Tuple(_) => { + panic!("These token types are not supported in packed mode"); + } + } +} + +#[derive(Default, BorshDeserialize, BorshSerialize, Clone)] +pub struct Proof { + pub log_index: u64, + pub log_entry_data: Vec, + pub receipt_index: u64, + pub receipt_data: Vec, + pub header_data: Vec, + pub proof: Vec>, +} + +#[allow(dead_code)] +impl Proof { + pub fn get_key(&self) -> String { + let mut data = self.log_index.try_to_vec().unwrap(); + data.extend(self.receipt_index.try_to_vec().unwrap()); + data.extend(self.header_data.clone()); + sdk::sha256(&data[..]) + .0 + .iter() + .map(|n| n.to_string()) + .collect() + } +} + +pub type EventParams = Vec; + +/// Ethereum event +pub struct EthEvent { + pub eth_custodian_address: EthAddress, + pub log: Log, +} + +#[allow(dead_code)] +impl EthEvent { + /// Get Ethereum event from `log_entry_data` + pub fn fetch_log_entry_data(name: &str, params: EventParams, data: &[u8]) -> Self { + let event = Event { + name: name.to_string(), + inputs: params, + anonymous: false, + }; + let log_entry: LogEntry = rlp::decode(data).expect("INVALID_RLP"); + let eth_custodian_address = log_entry.address.0; + let topics = log_entry.topics.iter().map(|h| Hash::from(h.0)).collect(); + + let raw_log = RawLog { + topics, + data: log_entry.data, + }; + let log = event.parse_log(raw_log).expect("Failed to parse event log"); + + Self { + eth_custodian_address, + log, + } + } +} + +const EIP_712_MSG_PREFIX: &[u8] = &[0x19, 0x01]; +const EIP_712_DOMAIN_TYPEHASH: &str = + "EIP712Domain(string name,string version,uint256 chainId,address verifyingContract)"; +const AURORA_DOMAIN_NAME: &str = "Aurora-Engine domain"; +const AURORA_DOMAIN_VERSION: &str = "1.0"; +const WITHDRAW_FROM_EVM_TYPEHASH: &str = + "WithdrawFromEVMRequest(address ethRecipient,uint256 amount,address verifyingContract)"; +const TRANSFER_FROM_EVM_TO_NEAR_TYPEHASH: &str = + "TransferFromEVMtoNearRequest(string nearRecipient,uint256 amount,uint256 fee)"; + +enum EIP712Recipient { + Eth(EthAddress), + Near(AccountId), +} + +/// Encode EIP712 withdraw message data +fn encode_eip712( + eth_recipient: EIP712Recipient, + amount: U256, + custodian_address: EthAddress, + type_hash: &str, +) -> H256 { + let chain_id = U256::from(Engine::get_state().unwrap().chain_id); + + let domain_separator_encoded = encode_packed(&[ + Token::FixedBytes( + sdk::keccak(&encode_packed(&[Token::Bytes( + EIP_712_DOMAIN_TYPEHASH.as_bytes().to_vec(), + )])) + .as_bytes() + .to_vec(), + ), + Token::FixedBytes(encode_packed(&[ + // Domain + Token::Bytes( + sdk::keccak(AURORA_DOMAIN_NAME.as_bytes()) + .as_bytes() + .to_vec(), + ), + // Version + Token::Bytes( + sdk::keccak(AURORA_DOMAIN_VERSION.as_bytes()) + .as_bytes() + .to_vec(), + ), + // ChainID + Token::Uint(chain_id), + // Custodian address + Token::Address(H160::from(custodian_address)), + ])), + ]); + sdk::log(&format!( + "Domain_separator encoded: {}", + hex::encode(domain_separator_encoded.clone()) + )); + + let domain_separator = sdk::keccak(&domain_separator_encoded); + sdk::log(&format!( + "Domain_separator hash: {}", + hex::encode(domain_separator) + )); + + let token_address = match eth_recipient { + EIP712Recipient::Eth(eth_recipient) => Token::Address(H160::from(eth_recipient)), + EIP712Recipient::Near(account_id) => Token::String(account_id), + }; + let withdraw_from_evm_struct_encoded = encode_packed(&[ + Token::FixedBytes( + sdk::keccak(&encode_packed(&[Token::Bytes( + type_hash.as_bytes().to_vec(), + )])) + .as_bytes() + .to_vec(), + ), + Token::FixedBytes(encode_packed(&[ + token_address, + Token::Uint(amount), + Token::Address(H160::from(custodian_address)), + ])), + ]); + sdk::log(&format!( + "WithdrawFromEVM struct encoded: {}", + hex::encode(withdraw_from_evm_struct_encoded.clone()), + )); + + let withdraw_from_evm_struct_hash = sdk::keccak(&withdraw_from_evm_struct_encoded); + sdk::log(&format!( + "WithdrawFromEVM struct hash: {}", + hex::encode(withdraw_from_evm_struct_hash) + )); + + let digest_encoded = encode_packed(&[ + Token::Bytes(EIP_712_MSG_PREFIX.to_vec()), + Token::FixedBytes(domain_separator.as_bytes().to_vec()), + Token::FixedBytes(withdraw_from_evm_struct_hash.as_bytes().to_vec()), + ]); + sdk::log(&format!( + "digest_encoded: {}", + hex::encode(digest_encoded.clone()) + )); + + let digest = sdk::keccak(&digest_encoded); + sdk::log(&format!("digest: {}", hex::encode(digest))); + digest +} + +#[allow(dead_code)] +pub fn verify_withdraw_eip712( + sender: EthAddress, + eth_recipient: EthAddress, + custodian_address: EthAddress, + amount: U256, + eip712_signature: Vec, +) -> bool { + let res = encode_eip712( + EIP712Recipient::Eth(eth_recipient), + amount, + custodian_address, + WITHDRAW_FROM_EVM_TYPEHASH, + ); + let withdraw_msg_signer = ecrecover(res, &eip712_signature[..]).unwrap(); + sdk::log(&format!("sender: {}", hex::encode(sender))); + sdk::log(&format!("ecrecover: {}", hex::encode(withdraw_msg_signer))); + sdk::log(&format!( + "ecrecover: {}", + H160::from(sender) == withdraw_msg_signer + )); + + H160::from(sender) == withdraw_msg_signer +} + +#[allow(dead_code)] +pub fn verify_transfer_eip712( + sender: EthAddress, + near_recipient: AccountId, + custodian_address: EthAddress, + amount: U256, + eip712_signature: Vec, +) -> bool { + let res = encode_eip712( + EIP712Recipient::Near(near_recipient), + amount, + custodian_address, + TRANSFER_FROM_EVM_TO_NEAR_TYPEHASH, + ); + let withdraw_msg_signer = ecrecover(res, &eip712_signature[..]).unwrap(); + sdk::log(&format!("sender: {}", hex::encode(sender))); + sdk::log(&format!("ecrecover: {}", hex::encode(withdraw_msg_signer))); + sdk::log(&format!( + "ecrecover: {}", + H160::from(sender) == withdraw_msg_signer + )); + + H160::from(sender) == withdraw_msg_signer +} diff --git a/src/sdk.rs b/src/sdk.rs index eed9cfcd3..1d21008e6 100644 --- a/src/sdk.rs +++ b/src/sdk.rs @@ -1,13 +1,13 @@ -use crate::prelude::{vec, String, Vec, H256}; +use crate::prelude::{vec, Vec, H256}; +use crate::types::PromiseResult; use crate::types::STORAGE_PRICE_PER_BYTE; use borsh::{BorshDeserialize, BorshSerialize}; const READ_STORAGE_REGISTER_ID: u64 = 0; const INPUT_REGISTER_ID: u64 = 0; -const GAS_FOR_STATE_MIGRATION: u64 = 250_000_000_000_000; +const GAS_FOR_STATE_MIGRATION: u64 = 100_000_000_000_000; mod exports { - #[allow(unused)] extern "C" { // ############# @@ -196,7 +196,6 @@ pub fn read_input_and_store(key: &[u8]) { } } -#[allow(dead_code)] pub fn return_output(value: &[u8]) { unsafe { exports::value_return(value.len() as u64, value.as_ptr() as u64); @@ -243,7 +242,6 @@ pub(crate) fn read_u64(key: &[u8]) -> Option> { }) } -#[allow(dead_code)] pub fn write_storage(key: &[u8], value: &[u8]) { unsafe { exports::storage_write( @@ -256,19 +254,16 @@ pub fn write_storage(key: &[u8], value: &[u8]) { } } -#[allow(dead_code)] pub fn remove_storage(key: &[u8]) { unsafe { exports::storage_remove(key.len() as u64, key.as_ptr() as u64, 0); } } -#[allow(dead_code)] pub fn block_timestamp() -> u64 { unsafe { exports::block_timestamp() } } -#[allow(dead_code)] pub fn block_index() -> u64 { unsafe { exports::block_index() } } @@ -278,7 +273,6 @@ pub fn panic() { unsafe { exports::panic() } } -#[allow(dead_code)] pub fn panic_utf8(bytes: &[u8]) -> ! { unsafe { exports::panic_utf8(bytes.len() as u64, bytes.as_ptr() as u64); @@ -293,7 +287,6 @@ pub fn log_utf8(bytes: &[u8]) { } } -#[allow(dead_code)] pub fn predecessor_account_id() -> Vec { unsafe { exports::predecessor_account_id(1); @@ -304,7 +297,6 @@ pub fn predecessor_account_id() -> Vec { } /// Calls environment sha256 on given input. -#[allow(dead_code)] pub fn sha256(input: &[u8]) -> H256 { unsafe { exports::sha256(input.len() as u64, input.as_ptr() as u64, 1); @@ -315,7 +307,6 @@ pub fn sha256(input: &[u8]) -> H256 { } /// Calls environment keccak256 on given input. -#[allow(dead_code)] pub fn keccak(input: &[u8]) -> H256 { unsafe { exports::keccak256(input.len() as u64, input.as_ptr() as u64, 1); @@ -325,14 +316,6 @@ pub fn keccak(input: &[u8]) -> H256 { } } -/// Calls environment panic with data encoded in hex as panic message. -#[allow(dead_code)] -pub fn panic_hex(data: &[u8]) -> ! { - let message = crate::types::bytes_to_hex(data).into_bytes(); - unsafe { exports::panic_utf8(message.len() as _, message.as_ptr() as _) } - unreachable!() -} - /// Returns account id of the current account. pub fn current_account_id() -> Vec { unsafe { @@ -363,15 +346,8 @@ pub fn self_deploy(code_key: &[u8]) { } } -#[allow(dead_code)] -pub fn save_contract(key: &str, data: &T) { - write_storage(key.as_bytes(), &data.try_to_vec().unwrap()[..]); -} - -#[allow(dead_code)] -pub fn get_contract_data(key: &str) -> T { - let data = read_storage(key.as_bytes()).expect("Failed read storage"); - T::try_from_slice(&data[..]).unwrap() +pub fn save_contract(key: &[u8], data: &T) { + write_storage(key, &data.try_to_vec().unwrap()[..]); } #[allow(dead_code)] @@ -379,25 +355,18 @@ pub fn log(data: &str) { log_utf8(data.as_bytes()) } -#[allow(dead_code)] -pub fn storage_usage() -> u64 { - unsafe { exports::storage_usage() } -} - -#[allow(dead_code)] +#[allow(unused)] pub fn prepaid_gas() -> u64 { unsafe { exports::prepaid_gas() } } -#[allow(dead_code)] pub fn promise_create( - account_id: String, + account_id: &[u8], method_name: &[u8], arguments: &[u8], amount: u128, gas: u64, ) -> u64 { - let account_id = account_id.as_bytes(); unsafe { exports::promise_create( account_id.len() as _, @@ -412,16 +381,14 @@ pub fn promise_create( } } -#[allow(dead_code)] pub fn promise_then( promise_idx: u64, - account_id: String, + account_id: &[u8], method_name: &[u8], arguments: &[u8], amount: u128, gas: u64, ) -> u64 { - let account_id = account_id.as_bytes(); unsafe { exports::promise_then( promise_idx, @@ -437,19 +404,17 @@ pub fn promise_then( } } -#[allow(dead_code)] pub fn promise_return(promise_idx: u64) { unsafe { exports::promise_return(promise_idx); } } -#[allow(dead_code)] pub fn promise_results_count() -> u64 { unsafe { exports::promise_results_count() } } -/*pub fn promise_result(result_idx: u64) -> PromiseResult { +pub fn promise_result(result_idx: u64) -> PromiseResult { unsafe { match exports::promise_result(result_idx, 0) { 0 => PromiseResult::NotReady, @@ -459,17 +424,16 @@ pub fn promise_results_count() -> u64 { PromiseResult::Successful(bytes) } 2 => PromiseResult::Failed, - _ => panic!("{}", RETURN_CODE_ERR), + _ => panic_utf8(b"ERR_PROMISE_RETURN_CODE"), } } -}*/ +} -#[allow(dead_code)] pub fn assert_private_call() { assert_eq!( predecessor_account_id(), current_account_id(), - "Function is private" + "ERR_PRIVATE_CALL" ); } @@ -481,29 +445,21 @@ pub fn attached_deposit() -> u128 { } } -#[allow(dead_code)] pub fn assert_one_yocto() { - assert_eq!( - attached_deposit(), - 1, - "Requires attached deposit of exactly 1 yoctoNEAR" - ) + assert_eq!(attached_deposit(), 1, "ERR_1YOCTO_ATTACH") } -#[allow(dead_code)] pub fn promise_batch_action_transfer(promise_index: u64, amount: u128) { unsafe { exports::promise_batch_action_transfer(promise_index, &amount as *const u128 as _); } } -#[allow(dead_code)] pub fn storage_byte_cost() -> u128 { STORAGE_PRICE_PER_BYTE } -#[allow(dead_code)] -pub fn promise_batch_create(account_id: String) -> u64 { +pub fn promise_batch_create(account_id: &[u8]) -> u64 { unsafe { exports::promise_batch_create(account_id.len() as _, account_id.as_ptr() as _) } } @@ -529,7 +485,7 @@ pub fn promise_batch_action_function_call( #[allow(dead_code)] pub fn storage_has_key(key: &[u8]) -> bool { - unsafe { exports::storage_has_key(key.len() as u64, key.as_ptr() as u64) == 1 } + unsafe { exports::storage_has_key(key.len() as _, key.as_ptr() as _) == 1 } } pub(crate) struct IncorrectInputLength; diff --git a/src/storage.rs b/src/storage.rs index a02a48477..a4eb3286e 100644 --- a/src/storage.rs +++ b/src/storage.rs @@ -16,7 +16,17 @@ pub enum KeyPrefix { Code = 0x3, Storage = 0x4, RelayerEvmAddressMap = 0x5, - // EthConnector = 0x6, + EthConnector = 0x6, +} + +/// Enum used to differentiate different storage keys used by eth-connector +#[derive(Clone, Copy, BorshSerialize, BorshDeserialize)] +pub enum EthConnectorStorageId { + Contract = 0x0, + FungibleToken = 0x1, + UsedEvent = 0x2, + PausedMask = 0x3, + StatisticsAuroraAccountsCounter = 0x4, } /// We can't use const generic over Enum, but we can do it over integral type @@ -32,7 +42,7 @@ impl From for KeyPrefix { 0x3 => Self::Code, 0x4 => Self::Storage, 0x5 => Self::RelayerEvmAddressMap, - // 0x6 used + 0x6 => Self::EthConnector, _ => unreachable!(), } } diff --git a/src/test_utils/mod.rs b/src/test_utils/mod.rs index 9cd3c2a09..75bd49a95 100644 --- a/src/test_utils/mod.rs +++ b/src/test_utils/mod.rs @@ -12,7 +12,8 @@ use primitive_types::U256; use rlp::RlpStream; use secp256k1::{self, Message, PublicKey, SecretKey}; -use crate::parameters::{NewCallArgs, SubmitResult}; +use crate::fungible_token::FungibleToken; +use crate::parameters::{InitCallArgs, NewCallArgs, SubmitResult}; use crate::prelude::Address; use crate::storage; use crate::test_utils::solidity::{ContractConstructor, DeployedContract}; @@ -123,8 +124,23 @@ impl AuroraRunner { let nonce_key = storage::address_to_key(storage::KeyPrefix::Nonce, &address); let nonce_value = types::u256_to_arr(&init_nonce); + let ft_key = storage::bytes_to_key( + storage::KeyPrefix::EthConnector, + &[storage::EthConnectorStorageId::FungibleToken as u8], + ); + let ft_value = { + let mut current_ft: FungibleToken = trie + .get(&ft_key) + .map(|bytes| FungibleToken::try_from_slice(&bytes).unwrap()) + .unwrap_or_default(); + current_ft.total_supply += init_balance.raw().as_u128(); + current_ft.total_supply_eth += init_balance.raw().as_u128(); + current_ft + }; + trie.insert(balance_key.to_vec(), balance_value.to_vec()); trie.insert(nonce_key.to_vec(), nonce_value.to_vec()); + trie.insert(ft_key, ft_value.try_to_vec().unwrap()); } pub fn submit_transaction( @@ -245,7 +261,7 @@ pub(crate) fn deploy_evm() -> AuroraRunner { let args = NewCallArgs { chain_id: types::u256_to_arr(&U256::from(runner.chain_id)), owner_id: runner.aurora_account_id.clone(), - bridge_prover_id: "prover.near".to_string(), + bridge_prover_id: "bridge_prover.near".to_string(), upgrade_delay_blocks: 1, }; @@ -257,6 +273,18 @@ pub(crate) fn deploy_evm() -> AuroraRunner { assert!(maybe_error.is_none()); + let args = InitCallArgs { + prover_account: "prover.near".to_string(), + eth_custodian_address: "d045f7e19B2488924B97F9c145b5E51D0D895A65".to_string(), + }; + let (_, maybe_error) = runner.call( + "new_eth_connector", + runner.aurora_account_id.clone(), + args.try_to_vec().unwrap(), + ); + + assert!(maybe_error.is_none()); + runner } diff --git a/src/types.rs b/src/types.rs index 4a4a84700..b31f7c0f5 100644 --- a/src/types.rs +++ b/src/types.rs @@ -1,4 +1,6 @@ use crate::prelude::{self, Address, String, Vec, H256, U256}; +#[cfg(feature = "engine")] +use alloc::str; use borsh::{BorshDeserialize, BorshSerialize}; #[cfg(not(feature = "contract"))] @@ -8,11 +10,13 @@ use sha3::{Digest, Keccak256}; use crate::sdk; pub type AccountId = String; +pub type Balance = u128; pub type RawAddress = [u8; 20]; pub type RawU256 = [u8; 32]; // Little-endian large integer type. pub type RawH256 = [u8; 32]; // Unformatted binary data of fixed length. +pub type EthAddress = [u8; 20]; pub type Gas = u64; -pub type Balance = u128; +pub type StorageUsage = u64; /// Newtype to distinguish balances (denominated in Wei) from other U256 types. #[derive(Debug, Eq, PartialEq, Copy, Clone, Default)] @@ -72,6 +76,7 @@ impl prelude::Add for Wei { pub struct U128(pub u128); pub const STORAGE_PRICE_PER_BYTE: u128 = 10_000_000_000_000_000_000; // 1e19yN, 0.00001N +pub const ERR_FAILED_PARSE: &str = "ERR_FAILED_PARSE"; /// Internal args format for meta call. #[derive(Debug)] @@ -85,6 +90,37 @@ pub struct InternalMetaCallArgs { pub input: Vec, } +pub struct StorageBalanceBounds { + pub min: Balance, + pub max: Option, +} + +/// promise results structure +#[cfg(feature = "engine")] +pub enum PromiseResult { + NotReady, + Successful(Vec), + Failed, +} + +/// ft_resolve_transfer result of eth-connector +#[cfg(feature = "engine")] +pub struct FtResolveTransferResult { + pub amount: Balance, + pub refund_amount: Balance, +} + +/// Internal errors to propagate up and format in the single place. +pub enum ErrorKind { + ArgumentParseError, + InvalidMetaTransactionMethodName, + InvalidMetaTransactionFunctionArg, + InvalidEcRecoverSignature, +} + +pub type Result = core::result::Result; + +#[allow(dead_code)] pub fn u256_to_arr(value: &U256) -> [u8; 32] { let mut result = [0u8; 32]; value.to_big_endian(&mut result); @@ -120,6 +156,36 @@ pub fn near_account_to_evm_address(addr: &[u8]) -> Address { Address::from_slice(&keccak(addr)[12..]) } +#[cfg(feature = "engine")] +pub fn str_from_slice(inp: &[u8]) -> &str { + str::from_utf8(inp).unwrap() +} + +#[cfg(feature = "contract")] +pub trait ExpectUtf8 { + fn expect_utf8(self, message: &[u8]) -> T; +} + +#[cfg(feature = "contract")] +impl ExpectUtf8 for Option { + fn expect_utf8(self, message: &[u8]) -> T { + match self { + Some(t) => t, + None => sdk::panic_utf8(message), + } + } +} + +#[cfg(feature = "contract")] +impl ExpectUtf8 for core::result::Result { + fn expect_utf8(self, message: &[u8]) -> T { + match self { + Ok(t) => t, + Err(_) => sdk::panic_utf8(message), + } + } +} + #[cfg(test)] mod tests { use super::*; diff --git a/tests/test_connector.rs b/tests/test_connector.rs new file mode 100644 index 000000000..d0e4bda26 --- /dev/null +++ b/tests/test_connector.rs @@ -0,0 +1,1153 @@ +#![allow(dead_code)] + +use near_sdk::borsh::{BorshDeserialize, BorshSerialize}; +use near_sdk::serde::{Deserialize, Serialize}; +use near_sdk::serde_json; +use near_sdk::serde_json::json; +use near_sdk::test_utils::accounts; +use near_sdk_sim::{to_yocto, ExecutionResult, UserAccount, DEFAULT_GAS, STORAGE_AMOUNT}; + +use aurora_engine::parameters::NewCallArgs; +use aurora_engine::types::{Balance, EthAddress}; +use byte_slice_cast::AsByteSlice; +use near_sdk_sim::transaction::ExecutionStatus; +use primitive_types::U256; + +pub type PausedMask = u8; +const UNPAUSE_ALL: PausedMask = 0; +const PAUSE_DEPOSIT: PausedMask = 1 << 0; +const PAUSE_WITHDRAW: PausedMask = 1 << 1; + +const CONTRACT_ACC: &'static str = "eth_connector.root"; +const EXTERNAL_CONTRACT_ACC: &'static str = "eth_recipient.root"; +const PROOF_DATA_NEAR: &'static str = r#"{"log_index":0,"log_entry_data":[248,251,148,9,109,233,194,184,165,184,194,44,238,50,137,177,1,246,150,13,104,229,30,248,66,160,209,66,67,156,39,142,37,218,217,165,7,102,241,83,208,227,210,215,191,43,209,111,194,120,28,75,212,148,178,177,90,157,160,0,0,0,0,0,0,0,0,0,0,0,0,121,24,63,219,216,14,45,138,234,26,202,162,246,123,251,138,54,212,10,141,184,160,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,96,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,12,54,144,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,144,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,18,101,116,104,95,114,101,99,105,112,105,101,110,116,46,114,111,111,116,0,0,0,0,0,0,0,0,0,0,0,0,0,0],"receipt_index":0,"receipt_data":[249,2,6,1,130,107,17,185,1,0,0,0,8,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,128,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,128,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,32,0,0,0,0,0,0,0,0,0,0,128,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,8,0,0,0,0,0,0,0,0,0,0,0,0,0,128,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,128,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,248,253,248,251,148,9,109,233,194,184,165,184,194,44,238,50,137,177,1,246,150,13,104,229,30,248,66,160,209,66,67,156,39,142,37,218,217,165,7,102,241,83,208,227,210,215,191,43,209,111,194,120,28,75,212,148,178,177,90,157,160,0,0,0,0,0,0,0,0,0,0,0,0,121,24,63,219,216,14,45,138,234,26,202,162,246,123,251,138,54,212,10,141,184,160,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,96,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,12,54,144,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,144,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,18,101,116,104,95,114,101,99,105,112,105,101,110,116,46,114,111,111,116,0,0,0,0,0,0,0,0,0,0,0,0,0,0],"header_data":[249,2,10,160,177,33,112,26,26,176,12,12,163,2,249,133,245,12,51,201,55,50,148,156,122,67,27,26,101,178,36,153,54,100,53,137,160,29,204,77,232,222,199,93,122,171,133,181,103,182,204,212,26,211,18,69,27,148,138,116,19,240,161,66,253,64,212,147,71,148,124,28,230,160,8,239,64,193,62,78,177,68,166,204,116,240,224,174,172,126,160,197,65,5,202,188,134,5,164,246,19,133,35,57,28,114,241,186,81,123,163,166,161,24,32,157,168,170,13,108,58,61,46,160,6,199,163,13,91,119,225,39,168,255,213,10,107,252,143,246,138,241,108,139,59,35,187,185,162,223,53,108,222,73,181,109,160,27,154,49,63,26,170,15,177,97,255,6,204,84,221,234,197,159,172,114,47,148,126,32,199,241,127,101,120,182,51,52,100,185,1,0,0,0,8,0,0,0,0,0,0,0,32,0,0,0,0,0,2,0,8,0,0,0,0,0,0,0,0,0,0,2,0,0,0,0,0,0,0,0,8,32,0,32,0,0,128,0,2,0,0,0,1,0,32,0,0,0,2,0,0,0,0,32,0,0,0,0,0,4,0,0,0,0,0,0,0,8,0,0,0,0,0,0,0,128,64,0,0,0,0,1,32,0,0,0,0,0,0,96,32,0,64,0,0,0,128,1,0,0,0,0,1,0,0,0,8,0,0,0,18,32,0,0,64,145,1,8,0,4,0,128,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,32,33,16,0,128,0,0,0,0,0,0,128,0,2,0,0,0,0,0,0,0,0,0,0,2,0,80,0,0,0,0,0,0,0,0,1,128,0,8,0,0,0,0,4,0,0,0,128,2,0,32,0,128,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,128,0,0,0,0,0,0,0,16,0,8,0,0,0,0,0,0,0,0,0,0,128,0,64,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,132,25,1,227,23,131,157,85,14,131,122,18,0,131,75,91,132,132,96,174,58,224,140,115,112,105,100,101,114,49,48,1,2,8,230,160,188,212,199,183,154,22,223,85,103,215,24,122,240,235,79,129,44,93,184,88,161,218,79,5,44,226,106,100,50,40,163,97,136,155,158,202,3,149,91,200,78],"proof":[[248,113,160,46,156,31,85,241,226,241,13,5,56,73,146,176,67,195,109,6,189,172,104,44,103,44,88,32,15,181,152,136,29,121,252,160,191,48,87,174,71,151,208,114,164,150,51,200,171,90,90,106,46,200,79,77,222,145,95,89,141,137,138,149,67,73,8,87,128,128,128,128,128,128,160,175,9,219,77,174,13,247,133,55,172,92,185,202,7,160,10,204,112,44,133,36,96,30,234,235,134,30,209,205,166,212,255,128,128,128,128,128,128,128,128],[249,2,13,48,185,2,9,249,2,6,1,130,107,17,185,1,0,0,0,8,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,128,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,128,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,32,0,0,0,0,0,0,0,0,0,0,128,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,8,0,0,0,0,0,0,0,0,0,0,0,0,0,128,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,128,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,248,253,248,251,148,9,109,233,194,184,165,184,194,44,238,50,137,177,1,246,150,13,104,229,30,248,66,160,209,66,67,156,39,142,37,218,217,165,7,102,241,83,208,227,210,215,191,43,209,111,194,120,28,75,212,148,178,177,90,157,160,0,0,0,0,0,0,0,0,0,0,0,0,121,24,63,219,216,14,45,138,234,26,202,162,246,123,251,138,54,212,10,141,184,160,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,96,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,12,54,144,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,144,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,18,101,116,104,95,114,101,99,105,112,105,101,110,116,46,114,111,111,116,0,0,0,0,0,0,0,0,0,0,0,0,0,0]]}"#; +const PROOF_DATA_ETH: &'static str = r#"{"log_index":0,"log_entry_data":[249,1,27,148,9,109,233,194,184,165,184,194,44,238,50,137,177,1,246,150,13,104,229,30,248,66,160,209,66,67,156,39,142,37,218,217,165,7,102,241,83,208,227,210,215,191,43,209,111,194,120,28,75,212,148,178,177,90,157,160,0,0,0,0,0,0,0,0,0,0,0,0,121,24,63,219,216,14,45,138,234,26,202,162,246,123,251,138,54,212,10,141,184,192,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,96,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,39,216,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,200,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,59,101,116,104,95,99,111,110,110,101,99,116,111,114,46,114,111,111,116,58,56,57,49,66,50,55,52,57,50,51,56,66,50,55,102,70,53,56,101,57,53,49,48,56,56,101,53,53,98,48,52,100,101,55,49,68,99,51,55,52,0,0,0,0,0],"receipt_index":0,"receipt_data":[249,2,40,1,130,121,129,185,1,0,0,0,8,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,128,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,128,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,32,0,0,0,0,0,0,0,0,0,0,128,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,8,0,0,0,0,0,0,0,0,0,0,0,0,0,128,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,128,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,249,1,30,249,1,27,148,9,109,233,194,184,165,184,194,44,238,50,137,177,1,246,150,13,104,229,30,248,66,160,209,66,67,156,39,142,37,218,217,165,7,102,241,83,208,227,210,215,191,43,209,111,194,120,28,75,212,148,178,177,90,157,160,0,0,0,0,0,0,0,0,0,0,0,0,121,24,63,219,216,14,45,138,234,26,202,162,246,123,251,138,54,212,10,141,184,192,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,96,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,39,216,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,200,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,59,101,116,104,95,99,111,110,110,101,99,116,111,114,46,114,111,111,116,58,56,57,49,66,50,55,52,57,50,51,56,66,50,55,102,70,53,56,101,57,53,49,48,56,56,101,53,53,98,48,52,100,101,55,49,68,99,51,55,52,0,0,0,0,0],"header_data":[249,2,23,160,227,118,223,171,207,47,75,187,79,185,74,198,88,140,54,97,161,196,35,70,121,178,154,141,172,91,193,252,86,64,228,227,160,29,204,77,232,222,199,93,122,171,133,181,103,182,204,212,26,211,18,69,27,148,138,116,19,240,161,66,253,64,212,147,71,148,109,150,79,199,61,172,73,162,195,49,105,169,235,252,47,207,92,249,136,136,160,232,74,213,122,210,55,65,43,78,225,85,247,174,212,229,211,176,186,250,113,21,129,16,181,52,172,217,167,148,242,153,45,160,15,198,229,127,6,235,198,161,226,121,173,106,62,0,90,25,158,11,242,44,178,3,137,22,245,126,227,91,74,156,24,115,160,65,253,74,43,97,155,196,93,59,43,202,12,155,49,115,95,124,247,230,15,1,171,150,10,56,115,247,86,81,8,39,11,185,1,0,128,32,9,2,0,0,0,0,0,0,32,16,128,32,0,0,128,2,0,0,64,51,0,0,0,129,0,32,66,32,0,14,0,144,0,0,0,2,13,34,0,128,64,200,128,4,32,16,0,64,0,0,34,0,32,0,40,0,8,0,0,32,176,0,196,1,0,0,10,1,16,8,16,0,0,72,48,0,0,36,0,17,4,128,10,68,0,16,0,1,32,0,128,0,32,0,12,64,162,8,98,2,0,32,0,0,16,136,1,16,40,0,0,0,0,4,0,0,44,32,0,0,192,49,0,8,12,64,96,129,0,2,0,0,128,0,12,64,10,8,1,132,0,32,0,1,4,33,0,4,128,140,128,0,2,66,0,0,192,0,2,16,2,0,0,0,32,16,0,0,64,0,242,4,0,0,0,0,0,0,4,128,0,32,0,14,194,0,16,10,64,32,0,0,0,2,16,96,16,129,0,16,32,32,128,128,32,0,2,68,0,32,1,8,64,16,32,2,5,2,68,0,32,0,2,16,1,0,0,16,2,0,0,16,2,0,0,0,128,0,16,0,36,128,32,0,4,64,16,0,40,16,0,17,0,16,132,25,207,98,158,131,157,85,88,131,122,17,225,131,121,11,191,132,96,174,60,127,153,216,131,1,10,1,132,103,101,116,104,134,103,111,49,46,49,54,135,119,105,110,100,111,119,115,160,33,15,129,167,71,37,0,207,110,217,101,107,71,110,48,237,4,83,174,75,131,188,213,179,154,115,243,94,107,52,238,144,136,84,114,37,115,236,166,252,105],"proof":[[248,177,160,211,36,253,39,157,18,180,1,3,139,140,168,65,238,106,111,239,53,121,48,235,96,8,115,106,93,174,165,66,207,49,216,160,172,74,129,163,113,84,7,35,23,12,83,10,253,21,57,198,143,128,73,112,84,222,23,146,164,219,89,23,138,197,111,237,160,52,220,245,245,91,231,95,169,113,225,49,168,40,77,59,232,33,210,4,93,203,94,247,212,15,42,146,32,70,206,193,54,160,6,140,29,61,156,224,194,173,129,74,84,92,11,129,184,212,37,31,23,140,226,87,230,72,30,52,97,66,185,236,139,228,128,128,128,128,160,190,114,105,101,139,216,178,42,238,75,109,119,227,138,206,144,183,82,34,173,26,173,188,231,152,171,56,163,2,179,13,190,128,128,128,128,128,128,128,128],[249,2,47,48,185,2,43,249,2,40,1,130,121,129,185,1,0,0,0,8,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,128,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,128,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,32,0,0,0,0,0,0,0,0,0,0,128,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,8,0,0,0,0,0,0,0,0,0,0,0,0,0,128,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,128,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,249,1,30,249,1,27,148,9,109,233,194,184,165,184,194,44,238,50,137,177,1,246,150,13,104,229,30,248,66,160,209,66,67,156,39,142,37,218,217,165,7,102,241,83,208,227,210,215,191,43,209,111,194,120,28,75,212,148,178,177,90,157,160,0,0,0,0,0,0,0,0,0,0,0,0,121,24,63,219,216,14,45,138,234,26,202,162,246,123,251,138,54,212,10,141,184,192,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,96,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,39,216,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,200,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,59,101,116,104,95,99,111,110,110,101,99,116,111,114,46,114,111,111,116,58,56,57,49,66,50,55,52,57,50,51,56,66,50,55,102,70,53,56,101,57,53,49,48,56,56,101,53,53,98,48,52,100,101,55,49,68,99,51,55,52,0,0,0,0,0]]}"#; +const DEPOSITED_RECIPIENT: &'static str = "eth_recipient.root"; +const PROVER_ACCOUNT: &'static str = "eth_connector.root"; +const CUSTODIAN_ADDRESS: &'static str = "096DE9C2B8A5B8c22cEe3289B101f6960d68E51E"; +const DEPOSITED_AMOUNT: u128 = 800400; +const DEPOSITED_FEE: u128 = 400; +const RECIPIENT_ETH_ADDRESS: &'static str = "891b2749238b27ff58e951088e55b04de71dc374"; +const EVM_CUSTODIAN_ADDRESS: &'static str = "096DE9C2B8A5B8c22cEe3289B101f6960d68E51E"; +const DEPOSITED_EVM_AMOUNT: u128 = 10200; +const DEPOSITED_EVM_FEE: u128 = 200; +const ERR_NOT_ENOUGH_BALANCE_FOR_FEE: &'static str = "ERR_NOT_ENOUGH_BALANCE_FOR_FEE"; +const ERR_PAUSED: &'static str = "ERR_PAUSED"; + +near_sdk_sim::lazy_static_include::lazy_static_include_bytes! { + EVM_WASM_BYTES => "release.wasm" +} + +#[derive(BorshDeserialize, BorshSerialize, Serialize, Deserialize)] +#[serde(crate = "near_sdk::serde")] +pub struct Proof { + pub log_index: u64, + pub log_entry_data: Vec, + pub receipt_index: u64, + pub receipt_data: Vec, + pub header_data: Vec, + pub proof: Vec>, +} + +#[derive(BorshSerialize, BorshDeserialize)] +pub struct InitCallArgs { + pub prover_account: String, + pub eth_custodian_address: String, +} + +#[derive(BorshSerialize, BorshDeserialize)] +pub struct WithdrawCallArgs { + pub recipient_address: EthAddress, + pub amount: Balance, +} + +#[derive(BorshDeserialize, Debug)] +pub struct WithdrawResult { + pub amount: Balance, + pub recipient_id: EthAddress, + pub eth_custodian_address: EthAddress, +} + +fn init(custodian_address: &str) -> (UserAccount, UserAccount) { + let master_account = near_sdk_sim::init_simulator(None); + let contract = init_contract(&master_account, CONTRACT_ACC, custodian_address); + (master_account, contract) +} + +fn init_contract( + master_account: &UserAccount, + contract_name: &str, + custodian_address: &str, +) -> UserAccount { + let contract_account = master_account.deploy( + *EVM_WASM_BYTES, + contract_name.to_string(), + to_yocto("1000000"), + ); + contract_account + .call( + contract_name.to_string(), + "new", + &NewCallArgs { + chain_id: [0u8; 32], + owner_id: master_account.account_id.clone(), + bridge_prover_id: accounts(0).to_string(), + upgrade_delay_blocks: 1, + } + .try_to_vec() + .unwrap(), + DEFAULT_GAS, + STORAGE_AMOUNT, + ) + .assert_success(); + contract_account + .call( + contract_name.to_string(), + "new_eth_connector", + &InitCallArgs { + prover_account: PROVER_ACCOUNT.into(), + eth_custodian_address: custodian_address.into(), + } + .try_to_vec() + .unwrap(), + DEFAULT_GAS, + 0, + ) + .assert_success(); + contract_account +} + +fn validate_eth_address(address: &str) -> EthAddress { + let data = hex::decode(address).unwrap(); + assert_eq!(data.len(), 20); + let mut result = [0u8; 20]; + result.copy_from_slice(&data); + result +} + +fn call_deposit_near(master_account: &UserAccount, contract: &str) -> Vec> { + let proof: Proof = serde_json::from_str(PROOF_DATA_NEAR).unwrap(); + let res = master_account.call( + contract.to_string(), + "deposit", + &proof.try_to_vec().unwrap(), + DEFAULT_GAS, + 0, + ); + //println!("{:#?}", res.promise_results()); + // Calculate Gas burnt + // let total_gas_burnt = res + // .promise_results() + // .iter() + // .fold(0, |s, v| s + v.as_ref().unwrap().gas_burnt()); + // println!("{:#?}", total_gas_burnt); + res.promise_results() +} + +#[allow(dead_code)] +fn print_logs(logs: &Vec) { + for l in logs { + println!("[log] {}", l); + } +} + +fn call_deposit_eth(master_account: &UserAccount, contract: &str) { + let proof: Proof = serde_json::from_str(PROOF_DATA_ETH).unwrap(); + let res = master_account.call( + contract.to_string(), + "deposit", + &proof.try_to_vec().unwrap(), + DEFAULT_GAS, + 10, + ); + res.assert_success(); + //println!("{:#?}", res.promise_results()); +} + +fn get_near_balance(master_account: &UserAccount, acc: &str, contract: &str) -> u128 { + #[derive(BorshSerialize)] + pub struct BalanceOfCallArgs { + pub account_id: String, + } + + let balance = master_account.view( + contract.to_string(), + "ft_balance_of", + json!({ "account_id": acc }).to_string().as_bytes(), + ); + String::from_utf8(balance.unwrap()) + .unwrap() + .parse() + .unwrap() +} + +fn get_eth_balance(master_account: &UserAccount, address: EthAddress, contract: &str) -> u128 { + #[derive(BorshSerialize, BorshDeserialize)] + pub struct BalanceOfEthCallArgs { + pub address: EthAddress, + } + + let balance = master_account.view( + contract.to_string(), + "ft_balance_of_eth", + &BalanceOfEthCallArgs { address }.try_to_vec().unwrap(), + ); + String::from_utf8(balance.unwrap()) + .unwrap() + .parse() + .unwrap() +} + +fn total_supply(master_account: &UserAccount, contract: &str) -> u128 { + let balance = master_account.view(contract.to_string(), "ft_total_supply", &[]); + String::from_utf8(balance.unwrap()) + .unwrap() + .parse() + .unwrap() +} + +fn total_supply_near(master_account: &UserAccount, contract: &str) -> u128 { + let balance = master_account.view(contract.to_string(), "ft_total_supply_near", &[]); + String::from_utf8(balance.unwrap()) + .unwrap() + .parse() + .unwrap() +} + +fn total_supply_eth(master_account: &UserAccount, contract: &str) -> u128 { + let balance = master_account.view(contract.to_string(), "ft_total_supply_eth", &[]); + String::from_utf8(balance.unwrap()) + .unwrap() + .parse() + .unwrap() +} + +#[test] +fn test_near_deposit_balance_total_supply() { + let (master_account, contract) = init(CUSTODIAN_ADDRESS); + call_deposit_near(&contract, CONTRACT_ACC); + + let balance = get_near_balance(&master_account, DEPOSITED_RECIPIENT, CONTRACT_ACC); + assert_eq!(balance, DEPOSITED_AMOUNT - DEPOSITED_FEE); + + let balance = get_near_balance(&master_account, CONTRACT_ACC, CONTRACT_ACC); + assert_eq!(balance, DEPOSITED_FEE); + + let balance = total_supply(&master_account, CONTRACT_ACC); + assert_eq!(balance, DEPOSITED_AMOUNT); + + let balance = total_supply_near(&master_account, CONTRACT_ACC); + assert_eq!(balance, DEPOSITED_AMOUNT); + + let balance = total_supply_eth(&master_account, CONTRACT_ACC); + assert_eq!(balance, 0); +} + +#[derive(BorshSerialize, BorshDeserialize)] +pub struct RegisterRelayerCallArgs { + pub address: EthAddress, +} + +#[test] +fn test_eth_deposit_balance_total_supply() { + let (master_account, contract) = init(EVM_CUSTODIAN_ADDRESS); + let res = contract.call( + CONTRACT_ACC.to_string(), + "register_relayer", + &RegisterRelayerCallArgs { + address: validate_eth_address(CUSTODIAN_ADDRESS), + } + .try_to_vec() + .unwrap(), + DEFAULT_GAS, + 0, + ); + res.assert_success(); + + call_deposit_eth(&contract, CONTRACT_ACC); + + let balance = get_eth_balance( + &master_account, + validate_eth_address(RECIPIENT_ETH_ADDRESS), + CONTRACT_ACC, + ); + assert_eq!(balance, DEPOSITED_EVM_AMOUNT - DEPOSITED_EVM_FEE); + + let balance = get_eth_balance( + &master_account, + validate_eth_address(CUSTODIAN_ADDRESS), + CONTRACT_ACC, + ); + assert_eq!(balance, DEPOSITED_EVM_FEE); + + let balance = total_supply(&master_account, CONTRACT_ACC); + assert_eq!(balance, DEPOSITED_EVM_AMOUNT); + + let balance = total_supply_eth(&master_account, CONTRACT_ACC); + assert_eq!(balance, DEPOSITED_EVM_AMOUNT); + + let balance = total_supply_near(&master_account, CONTRACT_ACC); + assert_eq!(balance, 0); +} + +#[test] +fn test_withdraw_near() { + let (master_account, contract) = init(CUSTODIAN_ADDRESS); + call_deposit_near(&contract, CONTRACT_ACC); + + let withdraw_amount = 100; + let recipient_addr = validate_eth_address(RECIPIENT_ETH_ADDRESS); + let res = contract.call( + CONTRACT_ACC.to_string(), + "withdraw", + &WithdrawCallArgs { + recipient_address: recipient_addr, + amount: withdraw_amount, + } + .try_to_vec() + .unwrap(), + DEFAULT_GAS, + 1, + ); + res.assert_success(); + let data = res.promise_results(); + assert!(data.len() > 1); + assert!(data[0].is_some()); + match data[1].clone().unwrap().outcome().status { + ExecutionStatus::SuccessValue(ref v) => { + let d: WithdrawResult = WithdrawResult::try_from_slice(&v).unwrap(); + assert_eq!(d.amount, withdraw_amount); + assert_eq!(d.recipient_id, recipient_addr); + let custodian_addr = validate_eth_address(CUSTODIAN_ADDRESS); + assert_eq!(d.eth_custodian_address, custodian_addr); + } + _ => panic!(), + } + + let balance = get_near_balance(&master_account, CONTRACT_ACC, CONTRACT_ACC); + assert_eq!(balance, DEPOSITED_FEE - withdraw_amount as u128); + + let balance = get_near_balance(&master_account, DEPOSITED_RECIPIENT, CONTRACT_ACC); + assert_eq!(balance, DEPOSITED_AMOUNT - DEPOSITED_FEE); + + let balance = total_supply(&master_account, CONTRACT_ACC); + assert_eq!(balance, DEPOSITED_AMOUNT - withdraw_amount as u128); +} + +#[test] +fn test_ft_transfer() { + let (master_account, contract) = init(CUSTODIAN_ADDRESS); + call_deposit_near(&contract, CONTRACT_ACC); + + let transfer_amount = 70; + let res = contract.call( + CONTRACT_ACC.to_string(), + "ft_transfer", + json!({ + "receiver_id": DEPOSITED_RECIPIENT, + "amount": transfer_amount, + "memo": "transfer memo" + }) + .to_string() + .as_bytes(), + DEFAULT_GAS, + 1, + ); + res.assert_success(); + + let balance = get_near_balance(&master_account, DEPOSITED_RECIPIENT, CONTRACT_ACC); + assert_eq!( + balance, + DEPOSITED_AMOUNT - DEPOSITED_FEE + transfer_amount as u128 + ); + + let balance = get_near_balance(&master_account, CONTRACT_ACC, CONTRACT_ACC); + assert_eq!(balance, DEPOSITED_FEE - transfer_amount as u128); + + let balance = total_supply(&master_account, CONTRACT_ACC); + assert_eq!(balance, DEPOSITED_AMOUNT); + + let balance = total_supply_eth(&master_account, CONTRACT_ACC); + assert_eq!(balance, 0); + + let balance = total_supply_near(&master_account, CONTRACT_ACC); + assert_eq!(balance, DEPOSITED_AMOUNT); +} + +#[test] +fn test_ft_transfer_call_eth() { + let (master_account, contract) = init(CUSTODIAN_ADDRESS); + call_deposit_near(&contract, CONTRACT_ACC); + + let balance = get_near_balance(&master_account, DEPOSITED_RECIPIENT, CONTRACT_ACC); + assert_eq!(balance, DEPOSITED_AMOUNT - DEPOSITED_FEE); + + let balance = get_near_balance(&master_account, CONTRACT_ACC, CONTRACT_ACC); + assert_eq!(balance, DEPOSITED_FEE); + + let res = contract.call( + CONTRACT_ACC.to_string(), + "register_relayer", + &RegisterRelayerCallArgs { + address: validate_eth_address(CUSTODIAN_ADDRESS), + } + .try_to_vec() + .unwrap(), + DEFAULT_GAS, + 0, + ); + res.assert_success(); + + let transfer_amount = 50; + let fee = 30; + let mut msg = U256::from(fee).as_byte_slice().to_vec(); + msg.append(&mut validate_eth_address(RECIPIENT_ETH_ADDRESS).to_vec()); + let message = [CONTRACT_ACC, hex::encode(msg).as_str()].join(":"); + let res = contract.call( + CONTRACT_ACC.to_string(), + "ft_transfer_call", + json!({ + "receiver_id": CONTRACT_ACC, + "amount": transfer_amount as u64, + "msg": message, + }) + .to_string() + .as_bytes(), + DEFAULT_GAS, + 1, + ); + res.assert_success(); + + let balance = get_near_balance(&master_account, DEPOSITED_RECIPIENT, CONTRACT_ACC); + assert_eq!(balance, DEPOSITED_AMOUNT - DEPOSITED_FEE); + + let balance = get_near_balance(&master_account, CONTRACT_ACC, CONTRACT_ACC); + assert_eq!(balance, DEPOSITED_FEE - transfer_amount); + + let balance = get_eth_balance( + &master_account, + validate_eth_address(RECIPIENT_ETH_ADDRESS), + CONTRACT_ACC, + ); + assert_eq!(balance, transfer_amount - fee); + + let balance = get_eth_balance( + &master_account, + validate_eth_address(CUSTODIAN_ADDRESS), + CONTRACT_ACC, + ); + assert_eq!(balance, fee); + + let balance = total_supply(&master_account, CONTRACT_ACC); + assert_eq!(balance, DEPOSITED_AMOUNT); + + let balance = total_supply_near(&master_account, CONTRACT_ACC); + assert_eq!(balance, DEPOSITED_AMOUNT - transfer_amount); + + let balance = total_supply_eth(&master_account, CONTRACT_ACC); + assert_eq!(balance, transfer_amount); +} + +#[test] +fn test_deposit_with_same_proof() { + let (_master_account, contract) = init(CUSTODIAN_ADDRESS); + let promises = call_deposit_near(&contract, CONTRACT_ACC); + for p in promises.iter() { + assert!(p.is_some()); + let p = p.as_ref().unwrap(); + p.assert_success() + } + let promises = call_deposit_near(&contract, CONTRACT_ACC); + let promise = &promises[promises.len() - 2]; + assert_execution_status_failure( + promise.as_ref().unwrap().outcome().clone().status, + "ERR_PROOF_EXIST", + "Expected failure as the provided proof already exists, but deposit succeeded", + ); +} + +#[test] +fn test_deposit_wrong_custodian_address() { + let wrong_custodian_address = "0000000000000000000000000000000000000001"; + let (_master_account, contract) = init(wrong_custodian_address); + let promises = call_deposit_near(&contract, CONTRACT_ACC); + let promise = &promises[promises.len() - 2]; + assert_execution_status_failure( + promise.as_ref().unwrap().outcome().clone().status, + "ERR_WRONG_EVENT_ADDRESS", + "Expected failure as the provided proof originated from wrong EthCustodian contract, but deposit succeeded", + ); +} + +#[test] +fn test_ft_transfer_call_without_relayer() { + let (master_account, contract) = init(CUSTODIAN_ADDRESS); + call_deposit_near(&contract, CONTRACT_ACC); + + let balance = get_near_balance(&master_account, DEPOSITED_RECIPIENT, CONTRACT_ACC); + assert_eq!(balance, DEPOSITED_AMOUNT - DEPOSITED_FEE); + + let balance = get_near_balance(&master_account, CONTRACT_ACC, CONTRACT_ACC); + assert_eq!(balance, DEPOSITED_FEE); + + let transfer_amount = 50; + let fee = 30; + let mut msg = U256::from(fee).as_byte_slice().to_vec(); + msg.append(&mut validate_eth_address(RECIPIENT_ETH_ADDRESS).to_vec()); + let relayer_id = "relayer.root"; + let message = [relayer_id, hex::encode(msg).as_str()].join(":"); + let res = contract.call( + CONTRACT_ACC.to_string(), + "ft_transfer_call", + json!({ + "receiver_id": CONTRACT_ACC, + "amount": transfer_amount as u64, + "msg": message, + }) + .to_string() + .as_bytes(), + DEFAULT_GAS, + 1, + ); + res.assert_success(); + + let balance = get_near_balance(&master_account, DEPOSITED_RECIPIENT, CONTRACT_ACC); + assert_eq!(balance, DEPOSITED_AMOUNT - DEPOSITED_FEE); + + let balance = get_near_balance(&master_account, CONTRACT_ACC, CONTRACT_ACC); + assert_eq!(balance, DEPOSITED_FEE - transfer_amount); + + let balance = get_eth_balance( + &master_account, + validate_eth_address(RECIPIENT_ETH_ADDRESS), + CONTRACT_ACC, + ); + assert_eq!(balance, transfer_amount); + + let balance = get_eth_balance( + &master_account, + validate_eth_address(CUSTODIAN_ADDRESS), + CONTRACT_ACC, + ); + assert_eq!(balance, 0); + + let balance = total_supply(&master_account, CONTRACT_ACC); + assert_eq!(balance, DEPOSITED_AMOUNT); + + let balance = total_supply_near(&master_account, CONTRACT_ACC); + assert_eq!(balance, DEPOSITED_AMOUNT - transfer_amount); + + let balance = total_supply_eth(&master_account, CONTRACT_ACC); + assert_eq!(balance, transfer_amount); +} + +#[test] +fn test_ft_transfer_call_fee_greater_than_amount() { + let (master_account, contract) = init(CUSTODIAN_ADDRESS); + call_deposit_near(&contract, CONTRACT_ACC); + + let transfer_amount = 10; + let fee = transfer_amount + 10; + let mut msg = U256::from(fee).as_byte_slice().to_vec(); + msg.append(&mut validate_eth_address(RECIPIENT_ETH_ADDRESS).to_vec()); + let relayer_id = "relayer.root"; + let message = [relayer_id, hex::encode(msg).as_str()].join(":"); + let res = contract.call( + CONTRACT_ACC.to_string(), + "ft_transfer_call", + json!({ + "receiver_id": CONTRACT_ACC, + "amount": transfer_amount as u64, + "msg": message, + }) + .to_string() + .as_bytes(), + DEFAULT_GAS, + 1, + ); + match res.outcome().clone().status { + ExecutionStatus::Failure(_) => {} + _ => panic!(), + } + + let balance = get_near_balance(&master_account, DEPOSITED_RECIPIENT, CONTRACT_ACC); + assert_eq!(balance, DEPOSITED_AMOUNT - DEPOSITED_FEE); + + let balance = get_near_balance(&master_account, CONTRACT_ACC, CONTRACT_ACC); + assert_eq!(balance, DEPOSITED_FEE); + + let balance = get_eth_balance( + &master_account, + validate_eth_address(RECIPIENT_ETH_ADDRESS), + CONTRACT_ACC, + ); + assert_eq!(balance, 0); + + let balance = get_eth_balance( + &master_account, + validate_eth_address(CUSTODIAN_ADDRESS), + CONTRACT_ACC, + ); + assert_eq!(balance, 0); + + let balance = total_supply(&master_account, CONTRACT_ACC); + assert_eq!(balance, DEPOSITED_AMOUNT); + + let balance = total_supply_near(&master_account, CONTRACT_ACC); + assert_eq!(balance, DEPOSITED_AMOUNT); + + let balance = total_supply_eth(&master_account, CONTRACT_ACC); + assert_eq!(balance, 0); +} + +fn call_deposit_with_proof( + account: &UserAccount, + contract: &str, + proof: &str, +) -> Vec> { + let proof: Proof = serde_json::from_str(proof).unwrap(); + let res = account.call( + contract.to_string(), + "deposit", + &proof.try_to_vec().unwrap(), + DEFAULT_GAS, + 0, + ); + res.promise_results() +} + +fn call_set_paused_flags( + account: &UserAccount, + contract: &str, + paused_mask: PausedMask, +) -> ExecutionResult { + let res = account.call( + contract.to_string(), + "set_paused_flags", + &paused_mask.try_to_vec().unwrap(), + DEFAULT_GAS, + 0, + ); + res +} + +fn create_user_account(master_account: &UserAccount) -> UserAccount { + let user_account = master_account.create_user( + "eth_recipient.root".to_string(), + to_yocto("100"), // initial balance + ); + user_account +} + +#[test] +fn test_admin_controlled_only_admin_can_pause() { + let (master_account, contract) = init(CUSTODIAN_ADDRESS); + let user_account = create_user_account(&master_account); + + // Try to pause from the user - should fail + let res = call_set_paused_flags(&user_account, CONTRACT_ACC, PAUSE_DEPOSIT); + let promises = res.promise_results(); + let p = promises[1].clone(); + match p.unwrap().outcome().clone().status { + ExecutionStatus::Failure(_) => {} + _ => panic!("Expected failure as only admin can pause, but user successfully paused"), + } + + // Try to pause from the admin - should succeed + let res = call_set_paused_flags(&contract, CONTRACT_ACC, PAUSE_DEPOSIT); + res.assert_success(); +} + +#[test] +fn test_admin_controlled_admin_can_peform_actions_when_paused() { + let (_master_account, contract) = init(CUSTODIAN_ADDRESS); + + // 1st deposit call when unpaused - should succeed + let promises = call_deposit_with_proof(&contract, CONTRACT_ACC, PROOF_DATA_NEAR); + for p in promises.iter() { + assert!(p.is_some()); + let p = p.as_ref().unwrap(); + p.assert_success() + } + + let withdraw_amount = 100; + let recipient_addr = validate_eth_address(RECIPIENT_ETH_ADDRESS); + + // 1st withdraw call when unpaused - should succeed + let res = contract.call( + CONTRACT_ACC.to_string(), + "withdraw", + &WithdrawCallArgs { + recipient_address: recipient_addr, + amount: withdraw_amount, + } + .try_to_vec() + .unwrap(), + DEFAULT_GAS, + 1, + ); + res.assert_success(); + let promises = res.promise_results(); + for p in promises.iter() { + assert!(p.is_some()); + let p = p.as_ref().unwrap(); + p.assert_success() + } + + // Pause deposit + let res = call_set_paused_flags(&contract, CONTRACT_ACC, PAUSE_DEPOSIT); + res.assert_success(); + + // 2nd deposit call when paused, but the admin is calling it - should succeed + // NB: We can use `PROOF_DATA_ETH` this will be just a different proof but the same deposit + // method which should be paused + let promises = call_deposit_with_proof(&contract, CONTRACT_ACC, PROOF_DATA_ETH); + for p in promises.iter() { + assert!(p.is_some()); + let p = p.as_ref().unwrap(); + p.assert_success() + } + + // Pause withdraw + let res = call_set_paused_flags(&contract, CONTRACT_ACC, PAUSE_WITHDRAW); + res.assert_success(); + + // 2nd withdraw call when paused, but the admin is calling it - should succeed + let res = contract.call( + CONTRACT_ACC.to_string(), + "withdraw", + &WithdrawCallArgs { + recipient_address: recipient_addr, + amount: withdraw_amount, + } + .try_to_vec() + .unwrap(), + DEFAULT_GAS, + 1, + ); + res.assert_success(); + let promises = res.promise_results(); + for p in promises.iter() { + assert!(p.is_some()); + let p = p.as_ref().unwrap(); + p.assert_success() + } +} + +#[test] +fn test_deposit_pausability() { + let (master_account, contract) = init(CUSTODIAN_ADDRESS); + let user_account = create_user_account(&master_account); + + // 1st deposit call - should succeed + let promises = call_deposit_with_proof(&user_account, CONTRACT_ACC, PROOF_DATA_NEAR); + for p in promises.iter() { + assert!(p.is_some()); + let p = p.as_ref().unwrap(); + p.assert_success() + } + + // Pause deposit + let res = call_set_paused_flags(&contract, CONTRACT_ACC, PAUSE_DEPOSIT); + res.assert_success(); + + // 2nd deposit call - should fail + // NB: We can use `PROOF_DATA_ETH` this will be just a different proof but the same deposit + // method which should be paused + let promises = call_deposit_with_proof(&user_account, CONTRACT_ACC, PROOF_DATA_ETH); + let num_promises = promises.len(); + let p = promises[num_promises - 2].clone(); + assert_execution_status_failure( + p.unwrap().outcome().clone().status, + ERR_PAUSED, + "Expected failure due to pause, but deposit succeeded", + ); + + // Unpause all + let res = call_set_paused_flags(&contract, CONTRACT_ACC, UNPAUSE_ALL); + res.assert_success(); + + // 3rd deposit call - should succeed + let promises = call_deposit_with_proof(&user_account, CONTRACT_ACC, PROOF_DATA_ETH); + for p in promises.iter() { + assert!(p.is_some()); + let p = p.as_ref().unwrap(); + p.assert_success() + } +} + +#[test] +fn test_withdraw_near_pausability() { + let (master_account, contract) = init(CUSTODIAN_ADDRESS); + let user_account = create_user_account(&master_account); + + call_deposit_near(&contract, CONTRACT_ACC); + + let withdraw_amount = 100; + let recipient_addr = validate_eth_address(RECIPIENT_ETH_ADDRESS); + // 1st withdraw - should succeed + let res = user_account.call( + CONTRACT_ACC.to_string(), + "withdraw", + &WithdrawCallArgs { + recipient_address: recipient_addr, + amount: withdraw_amount, + } + .try_to_vec() + .unwrap(), + DEFAULT_GAS, + 1, + ); + res.assert_success(); + let promises = res.promise_results(); + assert!(promises.len() > 1); + for p in promises.iter() { + assert!(p.is_some()); + let p = p.as_ref().unwrap(); + p.assert_success() + } + + // Pause withdraw + let res = call_set_paused_flags(&contract, CONTRACT_ACC, PAUSE_WITHDRAW); + res.assert_success(); + + // 2nd withdraw - should fail + let res = user_account.call( + CONTRACT_ACC.to_string(), + "withdraw", + &WithdrawCallArgs { + recipient_address: recipient_addr, + amount: withdraw_amount, + } + .try_to_vec() + .unwrap(), + DEFAULT_GAS, + 1, + ); + let promises = res.promise_results(); + let p = promises[1].clone(); + assert_execution_status_failure( + p.unwrap().outcome().clone().status, + ERR_PAUSED, + "Expected failure due to pause, but withdraw succeeded", + ); + + // Unpause all + let res = call_set_paused_flags(&contract, CONTRACT_ACC, UNPAUSE_ALL); + res.assert_success(); + + let res = user_account.call( + CONTRACT_ACC.to_string(), + "withdraw", + &WithdrawCallArgs { + recipient_address: recipient_addr, + amount: withdraw_amount, + } + .try_to_vec() + .unwrap(), + DEFAULT_GAS, + 1, + ); + res.assert_success(); + let promises = res.promise_results(); + assert!(promises.len() > 1); + for p in promises.iter() { + assert!(p.is_some()); + let p = p.as_ref().unwrap(); + p.assert_success() + } +} + +#[test] +fn test_get_accounts_counter() { + let (master_account, contract) = init(CUSTODIAN_ADDRESS); + call_deposit_near(&contract, CONTRACT_ACC); + + let counter = master_account + .view(CONTRACT_ACC.into(), "get_accounts_counter", &[]) + .unwrap(); + assert_eq!(u64::try_from_slice(&counter[..]).unwrap(), 2); +} + +#[test] +fn test_get_accounts_counter_and_transfer() { + let (master_account, contract) = init(CUSTODIAN_ADDRESS); + call_deposit_near(&contract, CONTRACT_ACC); + + let counter = master_account + .view(CONTRACT_ACC.into(), "get_accounts_counter", &[]) + .unwrap(); + assert_eq!(u64::try_from_slice(&counter[..]).unwrap(), 2); + + let transfer_amount = 70; + let res = contract.call( + CONTRACT_ACC.to_string(), + "ft_transfer", + json!({ + "receiver_id": DEPOSITED_RECIPIENT, + "amount": transfer_amount, + "memo": "transfer memo" + }) + .to_string() + .as_bytes(), + DEFAULT_GAS, + 1, + ); + res.assert_success(); + + let balance = get_near_balance(&master_account, DEPOSITED_RECIPIENT, CONTRACT_ACC); + assert_eq!( + balance, + DEPOSITED_AMOUNT - DEPOSITED_FEE + transfer_amount as u128 + ); + + let balance = get_near_balance(&master_account, CONTRACT_ACC, CONTRACT_ACC); + assert_eq!(balance, DEPOSITED_FEE - transfer_amount as u128); + + let balance = total_supply(&master_account, CONTRACT_ACC); + assert_eq!(balance, DEPOSITED_AMOUNT); + + let balance = total_supply_eth(&master_account, CONTRACT_ACC); + assert_eq!(balance, 0); + + let balance = total_supply_near(&master_account, CONTRACT_ACC); + assert_eq!(balance, DEPOSITED_AMOUNT); + + let counter = master_account + .view(CONTRACT_ACC.into(), "get_accounts_counter", &[]) + .unwrap(); + assert_eq!(u64::try_from_slice(&counter[..]).unwrap(), 2); +} + +#[test] +fn test_deposit_near_with_zero_fee() { + let (master_account, _) = init(CUSTODIAN_ADDRESS); + let proof = r#"{"log_index":0,"log_entry_data":[248,251,148,9,109,233,194,184,165,184,194,44,238,50,137,177,1,246,150,13,104,229,30,248,66,160,209,66,67,156,39,142,37,218,217,165,7,102,241,83,208,227,210,215,191,43,209,111,194,120,28,75,212,148,178,177,90,157,160,0,0,0,0,0,0,0,0,0,0,0,0,121,24,63,219,216,14,45,138,234,26,202,162,246,123,251,138,54,212,10,141,184,160,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,96,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,11,184,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,18,101,116,104,95,114,101,99,105,112,105,101,110,116,46,114,111,111,116,0,0,0,0,0,0,0,0,0,0,0,0,0,0],"receipt_index":0,"receipt_data":[249,2,6,1,130,106,249,185,1,0,0,0,8,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,128,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,128,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,32,0,0,0,0,0,0,0,0,0,0,128,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,8,0,0,0,0,0,0,0,0,0,0,0,0,0,128,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,128,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,248,253,248,251,148,9,109,233,194,184,165,184,194,44,238,50,137,177,1,246,150,13,104,229,30,248,66,160,209,66,67,156,39,142,37,218,217,165,7,102,241,83,208,227,210,215,191,43,209,111,194,120,28,75,212,148,178,177,90,157,160,0,0,0,0,0,0,0,0,0,0,0,0,121,24,63,219,216,14,45,138,234,26,202,162,246,123,251,138,54,212,10,141,184,160,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,96,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,11,184,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,18,101,116,104,95,114,101,99,105,112,105,101,110,116,46,114,111,111,116,0,0,0,0,0,0,0,0,0,0,0,0,0,0],"header_data":[249,2,23,160,7,139,123,21,146,99,81,234,117,153,151,30,67,221,231,90,105,219,121,127,196,224,201,83,178,31,173,155,190,123,227,174,160,29,204,77,232,222,199,93,122,171,133,181,103,182,204,212,26,211,18,69,27,148,138,116,19,240,161,66,253,64,212,147,71,148,109,150,79,199,61,172,73,162,195,49,105,169,235,252,47,207,92,249,136,136,160,227,202,170,144,85,104,169,90,220,93,227,155,76,252,229,223,163,146,127,223,157,121,27,238,116,64,112,216,124,129,107,9,160,158,128,122,7,117,120,186,231,92,224,181,67,43,66,153,79,155,38,238,166,68,1,151,100,134,126,214,86,59,66,174,201,160,235,177,124,164,253,179,174,206,160,196,186,61,51,64,217,35,121,86,229,24,251,162,51,82,72,31,218,240,150,32,157,48,185,1,0,0,0,8,0,0,32,0,0,0,0,0,0,128,0,0,0,2,0,128,0,64,32,0,0,0,0,0,0,64,0,0,10,0,0,0,0,0,0,3,0,0,0,0,64,128,0,0,64,0,0,0,0,0,16,0,0,130,0,1,16,0,32,4,0,0,0,0,0,2,1,0,0,0,0,0,8,0,8,0,0,32,0,4,128,2,0,128,0,0,0,0,0,0,0,0,0,4,32,0,8,2,0,0,0,128,65,0,136,0,0,40,0,0,0,8,0,0,128,0,34,0,4,0,185,2,0,0,4,32,128,0,2,0,0,0,128,0,0,10,0,1,0,1,0,0,0,0,32,1,8,128,0,0,4,0,0,0,128,128,0,70,0,0,0,0,0,0,16,64,0,64,0,34,64,0,0,0,4,0,0,0,0,1,128,0,9,0,0,0,0,0,16,0,0,64,2,0,0,0,132,0,64,32,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,4,0,0,0,32,8,0,16,0,8,0,16,68,0,0,0,16,0,0,0,128,0,64,0,0,128,0,0,0,0,0,0,0,16,0,1,0,16,132,49,181,116,68,131,157,92,101,131,122,18,0,131,101,155,9,132,96,174,110,74,153,216,131,1,10,1,132,103,101,116,104,134,103,111,49,46,49,54,135,119,105,110,100,111,119,115,160,228,82,26,232,236,82,141,6,111,169,92,14,115,254,59,131,192,3,202,209,126,79,140,182,163,12,185,45,210,17,60,38,136,84,114,37,115,236,183,145,213],"proof":[[248,145,160,187,129,186,104,13,250,13,252,114,170,223,247,137,53,113,225,188,217,54,244,108,193,247,236,197,29,0,161,119,76,227,184,160,66,209,234,66,254,223,80,22,246,80,204,38,2,90,115,201,183,79,207,47,192,234,143,221,89,78,36,199,127,9,55,190,160,91,160,251,58,165,255,90,2,105,47,46,220,67,3,52,105,42,182,130,224,19,162,115,159,136,158,218,93,187,148,188,9,128,128,128,128,128,160,181,223,248,223,173,187,103,169,52,204,62,13,90,70,147,236,199,27,201,112,157,4,139,63,188,12,98,117,10,82,85,125,128,128,128,128,128,128,128,128],[249,2,13,48,185,2,9,249,2,6,1,130,106,249,185,1,0,0,0,8,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,128,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,128,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,32,0,0,0,0,0,0,0,0,0,0,128,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,8,0,0,0,0,0,0,0,0,0,0,0,0,0,128,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,128,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,248,253,248,251,148,9,109,233,194,184,165,184,194,44,238,50,137,177,1,246,150,13,104,229,30,248,66,160,209,66,67,156,39,142,37,218,217,165,7,102,241,83,208,227,210,215,191,43,209,111,194,120,28,75,212,148,178,177,90,157,160,0,0,0,0,0,0,0,0,0,0,0,0,121,24,63,219,216,14,45,138,234,26,202,162,246,123,251,138,54,212,10,141,184,160,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,96,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,11,184,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,18,101,116,104,95,114,101,99,105,112,105,101,110,116,46,114,111,111,116,0,0,0,0,0,0,0,0,0,0,0,0,0,0]]}"#; + let proof: Proof = serde_json::from_str(proof).unwrap(); + let res = master_account.call( + CONTRACT_ACC.to_string(), + "deposit", + &proof.try_to_vec().unwrap(), + DEFAULT_GAS, + 0, + ); + res.assert_success(); + + let deposited_amount = 3000; + + let balance = get_near_balance(&master_account, DEPOSITED_RECIPIENT, CONTRACT_ACC); + assert_eq!(balance, deposited_amount); + + let balance = get_near_balance(&master_account, CONTRACT_ACC, CONTRACT_ACC); + assert_eq!(balance, 0); + + let balance = total_supply(&master_account, CONTRACT_ACC); + assert_eq!(balance, deposited_amount); + + let balance = total_supply_near(&master_account, CONTRACT_ACC); + assert_eq!(balance, deposited_amount); + + let balance = total_supply_eth(&master_account, CONTRACT_ACC); + assert_eq!(balance, 0); +} + +#[test] +fn test_deposit_evm_with_zero_fee() { + let (master_account, contract) = init(EVM_CUSTODIAN_ADDRESS); + let res = contract.call( + CONTRACT_ACC.to_string(), + "register_relayer", + &RegisterRelayerCallArgs { + address: validate_eth_address(CUSTODIAN_ADDRESS), + } + .try_to_vec() + .unwrap(), + DEFAULT_GAS, + 0, + ); + res.assert_success(); + + let proof = r#"{"log_index":0,"log_entry_data":[249,1,27,148,9,109,233,194,184,165,184,194,44,238,50,137,177,1,246,150,13,104,229,30,248,66,160,209,66,67,156,39,142,37,218,217,165,7,102,241,83,208,227,210,215,191,43,209,111,194,120,28,75,212,148,178,177,90,157,160,0,0,0,0,0,0,0,0,0,0,0,0,121,24,63,219,216,14,45,138,234,26,202,162,246,123,251,138,54,212,10,141,184,192,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,96,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,7,208,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,59,101,116,104,95,99,111,110,110,101,99,116,111,114,46,114,111,111,116,58,56,57,49,66,50,55,52,57,50,51,56,66,50,55,102,70,53,56,101,57,53,49,48,56,56,101,53,53,98,48,52,100,101,55,49,68,99,51,55,52,0,0,0,0,0],"receipt_index":3,"receipt_data":[249,2,41,1,131,2,246,200,185,1,0,0,0,8,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,128,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,128,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,32,0,0,0,0,0,0,0,0,0,0,128,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,8,0,0,0,0,0,0,0,0,0,0,0,0,0,128,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,128,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,249,1,30,249,1,27,148,9,109,233,194,184,165,184,194,44,238,50,137,177,1,246,150,13,104,229,30,248,66,160,209,66,67,156,39,142,37,218,217,165,7,102,241,83,208,227,210,215,191,43,209,111,194,120,28,75,212,148,178,177,90,157,160,0,0,0,0,0,0,0,0,0,0,0,0,121,24,63,219,216,14,45,138,234,26,202,162,246,123,251,138,54,212,10,141,184,192,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,96,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,7,208,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,59,101,116,104,95,99,111,110,110,101,99,116,111,114,46,114,111,111,116,58,56,57,49,66,50,55,52,57,50,51,56,66,50,55,102,70,53,56,101,57,53,49,48,56,56,101,53,53,98,48,52,100,101,55,49,68,99,51,55,52,0,0,0,0,0],"header_data":[249,2,23,160,110,48,40,236,52,198,197,25,255,191,199,4,137,3,185,31,202,84,90,80,104,32,176,13,144,141,165,183,36,30,94,138,160,29,204,77,232,222,199,93,122,171,133,181,103,182,204,212,26,211,18,69,27,148,138,116,19,240,161,66,253,64,212,147,71,148,148,156,193,169,167,156,148,249,191,22,225,202,121,212,79,2,197,75,191,164,160,127,26,168,212,111,22,173,213,25,217,187,227,114,86,173,99,166,195,67,16,104,111,200,109,110,147,241,23,71,122,89,215,160,47,120,179,75,110,158,228,18,242,156,38,111,95,25,236,211,158,53,53,62,89,190,2,40,220,41,151,200,127,219,33,219,160,222,177,165,249,98,109,130,37,226,229,165,113,45,12,145,30,16,28,154,86,22,203,218,233,13,246,165,177,61,57,68,83,185,1,0,0,32,8,0,33,0,0,0,64,0,32,0,128,0,0,0,132,0,0,0,64,32,64,0,0,1,0,32,64,0,0,8,0,0,0,0,0,0,137,32,0,0,0,64,128,0,0,16,0,0,0,0,33,64,0,1,0,0,0,0,0,0,0,0,68,0,0,0,2,1,64,0,0,0,0,9,16,0,0,32,0,0,0,128,2,0,0,0,33,0,0,0,128,0,0,0,12,64,32,8,66,2,0,0,64,0,0,8,0,0,40,8,8,0,0,0,0,16,0,0,0,0,64,49,0,0,8,0,96,0,0,18,0,0,0,0,0,64,10,0,1,0,0,32,0,0,0,33,0,0,128,136,10,64,0,64,0,0,192,128,0,0,64,1,0,0,4,0,8,0,64,0,34,0,0,0,0,0,0,0,0,0,0,0,8,8,0,4,0,0,0,32,0,4,0,2,0,0,0,129,4,0,96,16,4,8,0,0,0,0,0,0,1,0,128,16,0,0,2,0,4,0,32,0,8,0,0,0,0,16,0,1,0,0,0,0,64,0,128,0,0,32,36,128,0,0,4,64,0,8,8,16,0,1,4,16,132,50,32,156,229,131,157,92,137,131,122,18,0,131,35,159,183,132,96,174,111,126,153,216,131,1,10,3,132,103,101,116,104,136,103,111,49,46,49,54,46,51,133,108,105,110,117,120,160,59,74,90,253,211,14,166,114,39,213,120,95,221,43,109,173,72,205,160,203,71,44,83,159,36,59,129,84,32,16,254,251,136,49,16,97,244,161,246,244,85],"proof":[[248,113,160,227,103,29,228,16,56,196,146,115,29,122,202,254,140,214,86,189,108,47,197,2,195,50,211,4,126,58,175,71,11,70,78,160,229,239,23,242,100,150,90,169,21,162,252,207,202,244,187,71,172,126,191,33,166,162,45,134,108,114,6,76,78,177,148,140,128,128,128,128,128,128,160,21,91,249,81,132,162,52,236,128,181,5,72,158,228,177,131,87,144,64,194,111,103,180,16,183,103,245,136,125,213,208,76,128,128,128,128,128,128,128,128],[249,1,241,128,160,52,154,34,8,39,210,121,1,151,92,91,225,198,154,204,207,11,204,187,59,223,154,187,102,115,110,193,141,201,198,95,253,160,218,19,188,241,210,48,51,3,76,125,48,152,171,188,45,136,109,71,236,171,242,162,10,34,245,160,191,5,120,9,80,129,160,147,160,142,184,113,171,112,171,131,124,150,117,65,27,207,149,119,136,120,65,7,99,155,114,169,57,91,125,26,117,49,67,160,173,217,104,114,149,170,18,227,251,73,78,11,220,243,240,66,117,32,199,64,138,173,169,43,8,122,39,47,210,54,41,192,160,139,116,124,73,113,242,225,65,167,48,33,13,149,51,152,196,79,93,126,103,116,48,177,25,80,186,34,55,15,116,2,13,160,67,10,207,13,108,228,254,73,175,10,166,107,144,157,150,135,173,179,140,112,129,205,168,132,194,4,191,175,239,50,66,245,160,26,193,195,232,40,106,60,72,133,32,204,205,104,90,20,60,166,16,214,184,115,44,216,62,82,30,141,124,160,72,173,62,160,67,5,174,33,105,28,248,245,48,15,129,153,96,27,97,125,29,194,233,139,228,8,243,221,79,2,151,52,75,30,47,136,160,103,94,192,58,117,224,88,80,21,183,254,178,135,21,78,20,233,250,7,22,243,14,41,56,12,118,206,224,75,42,96,77,160,225,64,237,254,248,145,134,195,166,49,205,129,233,54,142,136,235,242,10,14,175,76,73,131,26,135,102,237,64,23,102,213,160,167,104,45,101,228,93,89,216,167,142,125,0,216,77,167,4,245,156,140,98,117,19,165,25,185,204,84,161,175,153,193,20,160,53,22,192,197,176,225,102,6,251,115,216,238,53,110,254,106,193,134,232,100,173,93,211,71,195,10,192,107,97,190,165,12,160,104,206,244,51,77,131,79,209,64,233,97,35,142,75,42,205,198,120,222,90,199,168,126,235,12,225,30,240,214,56,253,168,160,230,94,127,56,22,169,3,159,236,49,217,88,2,175,168,22,104,177,154,127,106,165,176,238,236,141,83,64,123,28,177,206,160,140,137,2,195,227,9,182,245,76,62,215,174,168,254,15,125,111,241,30,50,110,189,66,58,230,2,252,104,182,247,223,94,128],[249,2,48,32,185,2,44,249,2,41,1,131,2,246,200,185,1,0,0,0,8,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,128,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,128,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,32,0,0,0,0,0,0,0,0,0,0,128,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,8,0,0,0,0,0,0,0,0,0,0,0,0,0,128,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,128,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,249,1,30,249,1,27,148,9,109,233,194,184,165,184,194,44,238,50,137,177,1,246,150,13,104,229,30,248,66,160,209,66,67,156,39,142,37,218,217,165,7,102,241,83,208,227,210,215,191,43,209,111,194,120,28,75,212,148,178,177,90,157,160,0,0,0,0,0,0,0,0,0,0,0,0,121,24,63,219,216,14,45,138,234,26,202,162,246,123,251,138,54,212,10,141,184,192,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,96,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,7,208,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,59,101,116,104,95,99,111,110,110,101,99,116,111,114,46,114,111,111,116,58,56,57,49,66,50,55,52,57,50,51,56,66,50,55,102,70,53,56,101,57,53,49,48,56,56,101,53,53,98,48,52,100,101,55,49,68,99,51,55,52,0,0,0,0,0]]}"#; + let proof: Proof = serde_json::from_str(proof).unwrap(); + let res = master_account.call( + CONTRACT_ACC.to_string(), + "deposit", + &proof.try_to_vec().unwrap(), + DEFAULT_GAS, + 0, + ); + res.assert_success(); + + let deposited_amount = 2000; + + let balance = get_eth_balance( + &master_account, + validate_eth_address(RECIPIENT_ETH_ADDRESS), + CONTRACT_ACC, + ); + assert_eq!(balance, deposited_amount); + + let balance = get_eth_balance( + &master_account, + validate_eth_address(CUSTODIAN_ADDRESS), + CONTRACT_ACC, + ); + assert_eq!(balance, 0); + + let balance = total_supply(&master_account, CONTRACT_ACC); + assert_eq!(balance, deposited_amount); + + let balance = total_supply_eth(&master_account, CONTRACT_ACC); + assert_eq!(balance, deposited_amount); + + let balance = total_supply_near(&master_account, CONTRACT_ACC); + assert_eq!(balance, 0); +} + +#[test] +fn test_deposit_near_amount_less_fee() { + let custodian_address = "73c8931CA2aD746d97a59A7ABDDa0a9205F7ffF9"; + let (master_account, _) = init(custodian_address); + let proof = r#"{"log_index":0,"log_entry_data":[248,251,148,115,200,147,28,162,173,116,109,151,165,154,122,189,218,10,146,5,247,255,249,248,66,160,209,66,67,156,39,142,37,218,217,165,7,102,241,83,208,227,210,215,191,43,209,111,194,120,28,75,212,148,178,177,90,157,160,0,0,0,0,0,0,0,0,0,0,0,0,121,24,63,219,216,14,45,138,234,26,202,162,246,123,251,138,54,212,10,141,184,160,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,96,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,150,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,2,88,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,18,101,116,104,95,114,101,99,105,112,105,101,110,116,46,114,111,111,116,0,0,0,0,0,0,0,0,0,0,0,0,0,0],"receipt_index":0,"receipt_data":[249,2,6,1,130,106,251,185,1,0,0,0,8,0,0,0,0,0,0,0,0,0,0,0,0,0,0,128,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,32,0,0,0,0,0,0,0,0,0,0,128,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,8,0,0,0,0,0,0,8,0,0,0,0,0,0,0,0,0,0,0,0,0,128,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,128,0,0,0,0,0,0,0,0,0,0,0,0,0,0,128,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,248,253,248,251,148,115,200,147,28,162,173,116,109,151,165,154,122,189,218,10,146,5,247,255,249,248,66,160,209,66,67,156,39,142,37,218,217,165,7,102,241,83,208,227,210,215,191,43,209,111,194,120,28,75,212,148,178,177,90,157,160,0,0,0,0,0,0,0,0,0,0,0,0,121,24,63,219,216,14,45,138,234,26,202,162,246,123,251,138,54,212,10,141,184,160,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,96,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,150,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,2,88,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,18,101,116,104,95,114,101,99,105,112,105,101,110,116,46,114,111,111,116,0,0,0,0,0,0,0,0,0,0,0,0,0,0],"header_data":[249,2,10,160,139,92,51,142,163,95,21,160,61,29,148,206,54,147,187,96,77,109,244,8,130,155,249,198,206,30,173,216,144,176,252,123,160,29,204,77,232,222,199,93,122,171,133,181,103,182,204,212,26,211,18,69,27,148,138,116,19,240,161,66,253,64,212,147,71,148,124,28,230,160,8,239,64,193,62,78,177,68,166,204,116,240,224,174,172,126,160,218,9,209,192,173,39,133,109,141,57,2,146,184,12,94,217,6,138,173,67,121,185,24,179,133,189,219,40,81,210,73,106,160,219,108,244,199,44,203,84,71,126,74,82,240,203,255,238,20,226,29,239,51,7,19,144,34,156,137,232,159,71,30,164,29,160,209,61,241,33,17,103,192,203,57,156,112,250,18,166,26,237,248,153,226,185,87,220,156,93,249,17,39,190,125,96,247,239,185,1,0,0,0,8,0,0,0,0,0,0,0,0,1,0,0,0,0,0,128,0,0,0,2,0,0,0,0,0,0,0,0,0,0,0,0,0,8,0,8,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,8,0,0,0,0,0,0,0,32,0,0,0,0,8,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,16,32,0,0,0,0,0,0,0,0,0,0,0,0,0,0,64,0,0,32,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,128,0,0,0,0,32,0,0,0,0,8,0,0,2,0,0,128,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,8,8,0,0,0,0,0,0,40,0,0,0,0,0,0,0,0,0,0,0,0,0,144,4,0,0,64,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,128,0,0,128,0,0,0,0,0,0,0,0,0,0,0,0,0,0,128,0,16,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,132,91,80,110,139,131,157,118,104,131,122,18,0,131,30,4,87,132,96,175,154,220,140,115,112,105,100,101,114,49,48,1,2,9,64,160,80,163,212,151,183,11,70,219,178,190,167,172,64,187,47,14,29,226,253,132,116,145,81,143,54,249,121,123,193,241,120,249,136,244,120,239,134,243,43,177,139],"proof":[[248,81,160,164,35,68,182,184,52,174,73,6,81,4,92,187,190,187,106,255,124,123,24,244,168,161,247,60,181,75,29,192,175,96,140,128,128,128,128,128,128,128,160,169,157,199,164,106,205,109,88,111,183,255,180,108,15,155,137,126,163,108,44,117,125,138,221,3,188,93,85,146,129,19,139,128,128,128,128,128,128,128,128],[249,2,13,48,185,2,9,249,2,6,1,130,106,251,185,1,0,0,0,8,0,0,0,0,0,0,0,0,0,0,0,0,0,0,128,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,32,0,0,0,0,0,0,0,0,0,0,128,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,8,0,0,0,0,0,0,8,0,0,0,0,0,0,0,0,0,0,0,0,0,128,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,128,0,0,0,0,0,0,0,0,0,0,0,0,0,0,128,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,248,253,248,251,148,115,200,147,28,162,173,116,109,151,165,154,122,189,218,10,146,5,247,255,249,248,66,160,209,66,67,156,39,142,37,218,217,165,7,102,241,83,208,227,210,215,191,43,209,111,194,120,28,75,212,148,178,177,90,157,160,0,0,0,0,0,0,0,0,0,0,0,0,121,24,63,219,216,14,45,138,234,26,202,162,246,123,251,138,54,212,10,141,184,160,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,96,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,150,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,2,88,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,18,101,116,104,95,114,101,99,105,112,105,101,110,116,46,114,111,111,116,0,0,0,0,0,0,0,0,0,0,0,0,0,0]]}"#; + let proof: Proof = serde_json::from_str(proof).unwrap(); + let res = master_account.call( + CONTRACT_ACC.to_string(), + "deposit", + &proof.try_to_vec().unwrap(), + DEFAULT_GAS, + 0, + ); + let promise = &res.promise_results()[res.promise_results().len() - 2]; + assert_execution_status_failure( + promise.as_ref().unwrap().outcome().clone().status, + ERR_NOT_ENOUGH_BALANCE_FOR_FEE, + "Expected failure as the deposited amount is less than fee, but deposit to NEP-141 succeeded", + ); +} + +#[test] +fn test_deposit_evm_amount_less_fee() { + let custodian_address = "73c8931CA2aD746d97a59A7ABDDa0a9205F7ffF9"; + let (master_account, _) = init(custodian_address); + let proof = r#"{"log_index":0,"log_entry_data":[249,1,27,148,115,200,147,28,162,173,116,109,151,165,154,122,189,218,10,146,5,247,255,249,248,66,160,209,66,67,156,39,142,37,218,217,165,7,102,241,83,208,227,210,215,191,43,209,111,194,120,28,75,212,148,178,177,90,157,160,0,0,0,0,0,0,0,0,0,0,0,0,121,24,63,219,216,14,45,138,234,26,202,162,246,123,251,138,54,212,10,141,184,192,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,96,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,150,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,3,132,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,59,101,116,104,95,99,111,110,110,101,99,116,111,114,46,114,111,111,116,58,56,57,49,66,50,55,52,57,50,51,56,66,50,55,102,70,53,56,101,57,53,49,48,56,56,101,53,53,98,48,52,100,101,55,49,68,99,51,55,52,0,0,0,0,0],"receipt_index":0,"receipt_data":[249,2,40,1,130,121,119,185,1,0,0,0,8,0,0,0,0,0,0,0,0,0,0,0,0,0,0,128,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,32,0,0,0,0,0,0,0,0,0,0,128,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,8,0,0,0,0,0,0,8,0,0,0,0,0,0,0,0,0,0,0,0,0,128,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,128,0,0,0,0,0,0,0,0,0,0,0,0,0,0,128,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,249,1,30,249,1,27,148,115,200,147,28,162,173,116,109,151,165,154,122,189,218,10,146,5,247,255,249,248,66,160,209,66,67,156,39,142,37,218,217,165,7,102,241,83,208,227,210,215,191,43,209,111,194,120,28,75,212,148,178,177,90,157,160,0,0,0,0,0,0,0,0,0,0,0,0,121,24,63,219,216,14,45,138,234,26,202,162,246,123,251,138,54,212,10,141,184,192,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,96,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,150,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,3,132,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,59,101,116,104,95,99,111,110,110,101,99,116,111,114,46,114,111,111,116,58,56,57,49,66,50,55,52,57,50,51,56,66,50,55,102,70,53,56,101,57,53,49,48,56,56,101,53,53,98,48,52,100,101,55,49,68,99,51,55,52,0,0,0,0,0],"header_data":[249,2,10,160,234,97,221,132,104,51,119,219,129,206,197,27,130,197,14,113,167,32,152,214,207,205,156,210,35,213,198,227,116,42,51,224,160,29,204,77,232,222,199,93,122,171,133,181,103,182,204,212,26,211,18,69,27,148,138,116,19,240,161,66,253,64,212,147,71,148,124,28,230,160,8,239,64,193,62,78,177,68,166,204,116,240,224,174,172,126,160,15,150,233,184,181,140,226,81,205,139,229,87,226,149,49,207,117,33,36,83,124,8,75,199,231,48,13,23,189,217,179,12,160,241,37,169,74,233,62,231,112,0,207,95,228,68,240,108,254,57,199,255,130,142,158,161,180,243,50,255,222,77,251,252,126,160,31,111,236,60,142,91,35,119,195,92,158,134,65,138,8,247,98,122,229,21,226,85,38,130,141,139,168,60,83,90,63,244,185,1,0,0,0,8,0,0,0,0,0,0,0,0,0,128,0,0,0,0,128,0,0,0,32,0,0,0,0,0,0,64,0,0,10,0,0,0,0,0,0,1,0,0,0,0,64,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,2,1,0,0,0,0,0,8,0,0,2,0,0,0,4,0,2,0,0,0,0,0,0,0,0,0,0,0,4,0,0,8,2,0,0,0,0,0,0,136,0,4,40,0,0,0,0,0,0,0,0,0,0,0,0,48,0,0,0,0,32,0,0,10,0,0,0,0,0,0,10,0,1,0,0,0,0,0,0,32,0,0,128,0,0,0,0,0,0,0,128,0,0,0,0,0,0,0,0,16,0,0,64,0,34,0,0,0,0,0,8,0,0,0,0,0,0,8,0,0,0,0,0,0,0,0,128,2,0,0,0,128,0,1,32,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,4,0,0,0,32,128,0,0,0,0,0,16,0,0,0,0,0,0,0,0,128,0,0,0,0,128,0,0,0,0,0,0,0,16,0,1,0,16,132,91,127,63,197,131,157,118,142,131,122,18,0,131,25,25,181,132,96,175,156,157,140,115,112,105,100,101,114,49,48,1,2,9,64,160,68,227,115,157,18,184,21,217,93,74,196,34,230,228,210,239,61,26,221,245,191,46,44,135,134,2,20,53,95,18,128,54,136,162,198,27,59,153,146,63,16],"proof":[[248,113,160,204,110,241,220,150,206,51,121,104,130,125,127,249,35,9,242,107,45,164,62,147,221,93,116,73,79,49,96,226,92,235,247,160,43,215,154,177,148,177,15,202,141,217,45,114,108,33,74,0,144,126,189,26,78,152,232,105,119,103,203,51,79,45,113,124,128,128,128,128,128,128,160,74,177,164,103,85,250,153,17,105,68,205,207,176,48,89,230,100,35,20,167,34,117,11,115,14,107,128,214,48,17,53,209,128,128,128,128,128,128,128,128],[249,2,47,48,185,2,43,249,2,40,1,130,121,119,185,1,0,0,0,8,0,0,0,0,0,0,0,0,0,0,0,0,0,0,128,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,32,0,0,0,0,0,0,0,0,0,0,128,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,8,0,0,0,0,0,0,8,0,0,0,0,0,0,0,0,0,0,0,0,0,128,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,128,0,0,0,0,0,0,0,0,0,0,0,0,0,0,128,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,249,1,30,249,1,27,148,115,200,147,28,162,173,116,109,151,165,154,122,189,218,10,146,5,247,255,249,248,66,160,209,66,67,156,39,142,37,218,217,165,7,102,241,83,208,227,210,215,191,43,209,111,194,120,28,75,212,148,178,177,90,157,160,0,0,0,0,0,0,0,0,0,0,0,0,121,24,63,219,216,14,45,138,234,26,202,162,246,123,251,138,54,212,10,141,184,192,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,96,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,150,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,3,132,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,59,101,116,104,95,99,111,110,110,101,99,116,111,114,46,114,111,111,116,58,56,57,49,66,50,55,52,57,50,51,56,66,50,55,102,70,53,56,101,57,53,49,48,56,56,101,53,53,98,48,52,100,101,55,49,68,99,51,55,52,0,0,0,0,0]]}"#; + let proof: Proof = serde_json::from_str(proof).unwrap(); + let res = master_account.call( + CONTRACT_ACC.to_string(), + "deposit", + &proof.try_to_vec().unwrap(), + DEFAULT_GAS, + 0, + ); + let promise = &res.promise_results()[res.promise_results().len() - 2]; + assert_execution_status_failure( + promise.as_ref().unwrap().outcome().clone().status, + ERR_NOT_ENOUGH_BALANCE_FOR_FEE, + "Expected failure as the deposited amount is less than fee, but deposit to Aurora succeeded", + ); +} + +#[test] +fn test_deposit_near_amount_zero_fee_non_zero() { + let custodian_address = "73c8931CA2aD746d97a59A7ABDDa0a9205F7ffF9"; + let (master_account, _) = init(custodian_address); + let proof = r#"{"log_index":0,"log_entry_data":[248,251,148,115,200,147,28,162,173,116,109,151,165,154,122,189,218,10,146,5,247,255,249,248,66,160,209,66,67,156,39,142,37,218,217,165,7,102,241,83,208,227,210,215,191,43,209,111,194,120,28,75,212,148,178,177,90,157,160,0,0,0,0,0,0,0,0,0,0,0,0,121,24,63,219,216,14,45,138,234,26,202,162,246,123,251,138,54,212,10,141,184,160,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,96,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,244,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,18,101,116,104,95,114,101,99,105,112,105,101,110,116,46,114,111,111,116,0,0,0,0,0,0,0,0,0,0,0,0,0,0],"receipt_index":0,"receipt_data":[249,2,6,1,130,106,251,185,1,0,0,0,8,0,0,0,0,0,0,0,0,0,0,0,0,0,0,128,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,32,0,0,0,0,0,0,0,0,0,0,128,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,8,0,0,0,0,0,0,8,0,0,0,0,0,0,0,0,0,0,0,0,0,128,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,128,0,0,0,0,0,0,0,0,0,0,0,0,0,0,128,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,248,253,248,251,148,115,200,147,28,162,173,116,109,151,165,154,122,189,218,10,146,5,247,255,249,248,66,160,209,66,67,156,39,142,37,218,217,165,7,102,241,83,208,227,210,215,191,43,209,111,194,120,28,75,212,148,178,177,90,157,160,0,0,0,0,0,0,0,0,0,0,0,0,121,24,63,219,216,14,45,138,234,26,202,162,246,123,251,138,54,212,10,141,184,160,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,96,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,244,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,18,101,116,104,95,114,101,99,105,112,105,101,110,116,46,114,111,111,116,0,0,0,0,0,0,0,0,0,0,0,0,0,0],"header_data":[249,2,10,160,47,76,8,45,83,192,115,218,108,188,181,117,148,40,254,44,169,118,92,188,207,7,122,246,133,75,100,184,134,128,91,12,160,29,204,77,232,222,199,93,122,171,133,181,103,182,204,212,26,211,18,69,27,148,138,116,19,240,161,66,253,64,212,147,71,148,124,28,230,160,8,239,64,193,62,78,177,68,166,204,116,240,224,174,172,126,160,225,211,110,129,173,98,101,150,55,116,11,30,26,161,226,8,234,249,90,46,245,112,225,68,76,26,215,135,27,181,140,22,160,229,44,239,5,102,141,42,118,174,163,144,225,90,152,120,60,150,25,144,217,154,234,25,69,35,226,103,149,188,127,81,106,160,177,89,93,76,113,24,117,182,174,52,148,6,239,129,151,18,222,56,245,9,232,80,7,129,118,118,108,72,76,247,238,101,185,1,0,1,4,200,10,0,0,0,0,8,0,32,0,128,3,1,0,0,145,4,33,72,8,0,2,0,128,0,18,64,26,38,0,4,16,8,1,136,65,40,32,0,0,1,72,0,2,0,128,0,64,0,0,48,0,32,0,0,0,0,192,0,100,9,0,12,0,16,0,0,1,2,8,8,0,8,12,128,64,0,192,2,0,0,64,2,68,129,0,128,1,0,0,128,128,68,0,64,64,32,0,67,0,32,0,0,41,20,1,0,16,40,0,16,16,32,0,0,0,128,0,0,0,64,48,4,8,8,0,0,0,0,66,32,64,0,0,48,0,16,8,1,64,0,0,16,32,0,33,32,0,0,128,0,2,2,128,0,0,192,0,2,40,0,0,0,0,0,1,0,67,1,0,131,32,6,8,0,0,8,96,128,0,0,0,0,12,0,0,0,65,2,160,2,64,0,2,4,32,0,128,0,1,34,0,105,0,160,0,32,18,32,16,1,0,0,0,20,0,32,0,20,0,96,128,0,16,0,0,64,16,2,192,1,0,4,32,0,32,130,2,0,0,32,0,0,0,4,64,12,64,0,0,4,0,0,1,132,93,96,3,163,131,157,117,205,131,122,18,0,131,113,87,104,132,96,175,145,182,140,115,112,105,100,101,114,49,48,1,2,9,64,160,179,183,88,73,3,20,234,255,8,238,6,186,173,204,149,149,235,233,232,35,158,194,53,246,218,39,221,246,90,7,34,255,136,176,36,100,161,146,27,98,29],"proof":[[248,177,160,93,101,188,48,5,53,36,126,41,0,92,130,188,117,104,230,178,29,27,194,22,86,212,235,193,20,241,42,157,88,117,205,160,141,83,180,197,22,126,217,34,74,50,114,118,42,157,161,171,8,158,98,92,183,124,137,130,211,1,106,44,222,37,13,32,160,62,131,146,138,69,63,89,98,140,64,187,93,207,160,0,4,134,154,205,47,168,231,136,249,129,230,137,29,3,210,67,173,160,76,91,176,245,81,3,198,111,175,230,185,70,220,111,189,88,15,154,173,107,239,121,185,13,159,197,61,37,231,252,22,200,128,128,128,128,160,13,246,139,212,38,202,103,201,31,80,247,136,186,58,17,52,66,119,115,128,23,123,59,166,177,68,79,182,9,242,60,106,128,128,128,128,128,128,128,128],[249,2,13,48,185,2,9,249,2,6,1,130,106,251,185,1,0,0,0,8,0,0,0,0,0,0,0,0,0,0,0,0,0,0,128,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,32,0,0,0,0,0,0,0,0,0,0,128,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,8,0,0,0,0,0,0,8,0,0,0,0,0,0,0,0,0,0,0,0,0,128,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,128,0,0,0,0,0,0,0,0,0,0,0,0,0,0,128,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,248,253,248,251,148,115,200,147,28,162,173,116,109,151,165,154,122,189,218,10,146,5,247,255,249,248,66,160,209,66,67,156,39,142,37,218,217,165,7,102,241,83,208,227,210,215,191,43,209,111,194,120,28,75,212,148,178,177,90,157,160,0,0,0,0,0,0,0,0,0,0,0,0,121,24,63,219,216,14,45,138,234,26,202,162,246,123,251,138,54,212,10,141,184,160,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,96,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,244,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,18,101,116,104,95,114,101,99,105,112,105,101,110,116,46,114,111,111,116,0,0,0,0,0,0,0,0,0,0,0,0,0,0]]}"#; + let proof: Proof = serde_json::from_str(proof).unwrap(); + let res = master_account.call( + CONTRACT_ACC.to_string(), + "deposit", + &proof.try_to_vec().unwrap(), + DEFAULT_GAS, + 0, + ); + let promise = &res.promise_results()[res.promise_results().len() - 2]; + assert_execution_status_failure( + promise.as_ref().unwrap().outcome().clone().status, + ERR_NOT_ENOUGH_BALANCE_FOR_FEE, + "Expected failure as the deposited amount is zero and the fee is not zero, but deposit to NEP-141 succeeded", + ); +} + +#[test] +fn test_deposit_evm_amount_zero_fee_non_zero() { + let custodian_address = "73c8931CA2aD746d97a59A7ABDDa0a9205F7ffF9"; + let (master_account, _) = init(custodian_address); + let proof = r#"{"log_index":0,"log_entry_data":[249,1,27,148,115,200,147,28,162,173,116,109,151,165,154,122,189,218,10,146,5,247,255,249,248,66,160,209,66,67,156,39,142,37,218,217,165,7,102,241,83,208,227,210,215,191,43,209,111,194,120,28,75,212,148,178,177,90,157,160,0,0,0,0,0,0,0,0,0,0,0,0,121,24,63,219,216,14,45,138,234,26,202,162,246,123,251,138,54,212,10,141,184,192,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,96,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,174,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,59,101,116,104,95,99,111,110,110,101,99,116,111,114,46,114,111,111,116,58,56,57,49,66,50,55,52,57,50,51,56,66,50,55,102,70,53,56,101,57,53,49,48,56,56,101,53,53,98,48,52,100,101,55,49,68,99,51,55,52,0,0,0,0,0],"receipt_index":1,"receipt_data":[249,2,41,1,131,1,110,54,185,1,0,0,0,8,0,0,0,0,0,0,0,0,0,0,0,0,0,0,128,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,32,0,0,0,0,0,0,0,0,0,0,128,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,8,0,0,0,0,0,0,8,0,0,0,0,0,0,0,0,0,0,0,0,0,128,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,128,0,0,0,0,0,0,0,0,0,0,0,0,0,0,128,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,249,1,30,249,1,27,148,115,200,147,28,162,173,116,109,151,165,154,122,189,218,10,146,5,247,255,249,248,66,160,209,66,67,156,39,142,37,218,217,165,7,102,241,83,208,227,210,215,191,43,209,111,194,120,28,75,212,148,178,177,90,157,160,0,0,0,0,0,0,0,0,0,0,0,0,121,24,63,219,216,14,45,138,234,26,202,162,246,123,251,138,54,212,10,141,184,192,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,96,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,174,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,59,101,116,104,95,99,111,110,110,101,99,116,111,114,46,114,111,111,116,58,56,57,49,66,50,55,52,57,50,51,56,66,50,55,102,70,53,56,101,57,53,49,48,56,56,101,53,53,98,48,52,100,101,55,49,68,99,51,55,52,0,0,0,0,0],"header_data":[249,2,21,160,60,128,9,36,168,69,207,249,164,88,177,15,74,221,137,160,110,246,3,133,209,132,169,179,31,86,142,216,160,11,162,137,160,29,204,77,232,222,199,93,122,171,133,181,103,182,204,212,26,211,18,69,27,148,138,116,19,240,161,66,253,64,212,147,71,148,28,255,226,5,233,121,118,187,157,30,192,6,245,34,35,96,168,147,83,224,160,182,206,231,252,255,115,166,11,152,156,84,169,204,36,0,94,3,17,113,103,104,252,225,161,115,85,74,227,104,249,187,232,160,211,106,68,136,2,141,5,14,201,111,68,218,251,84,103,176,66,10,190,123,58,119,216,141,192,197,222,181,211,87,117,192,160,162,200,112,106,166,13,220,187,223,164,251,102,104,106,40,84,17,101,93,131,125,204,193,62,96,110,167,214,54,41,154,191,185,1,0,0,40,72,0,32,0,0,0,0,0,0,5,128,2,0,8,0,128,144,136,0,34,0,0,32,1,0,0,64,16,0,10,0,16,8,28,0,17,9,0,0,0,0,72,0,16,4,0,0,0,0,128,2,18,0,0,0,0,1,16,0,36,0,1,1,32,8,0,2,1,0,64,64,0,0,8,0,16,0,40,2,0,13,0,2,8,0,0,0,8,0,0,16,0,4,16,36,0,52,8,130,128,8,0,0,0,0,10,0,2,40,64,0,34,32,2,0,2,0,0,0,0,0,48,4,32,128,0,32,0,0,2,96,0,0,0,0,64,10,0,33,64,0,0,0,66,0,32,0,0,192,138,0,0,0,70,0,129,128,0,66,32,0,0,16,64,0,0,0,0,97,0,34,0,6,0,0,32,8,0,1,200,128,48,0,41,128,0,128,0,224,0,0,0,0,2,0,64,0,148,0,0,32,72,8,0,96,0,36,128,25,48,33,0,128,16,0,0,4,2,128,4,32,144,0,20,0,0,0,16,2,0,4,0,2,8,0,0,128,0,16,0,0,128,0,0,16,0,128,0,72,16,0,129,0,80,132,91,116,53,37,131,157,118,157,131,122,18,0,131,48,97,222,132,96,175,157,102,151,214,131,1,10,2,132,103,101,116,104,134,103,111,49,46,49,54,133,108,105,110,117,120,160,218,71,54,233,233,153,85,103,64,10,4,159,150,224,130,134,111,78,188,224,102,166,96,148,216,222,134,254,219,185,88,110,136,87,173,68,252,252,248,190,64],"proof":[[248,177,160,174,171,108,131,83,47,244,139,23,122,146,226,84,189,175,114,176,131,196,80,85,155,220,172,151,31,138,121,78,34,1,37,160,104,209,167,107,221,53,22,163,251,61,251,80,40,239,108,253,251,47,253,90,163,103,58,194,173,111,232,90,174,223,154,156,160,185,232,110,109,245,242,193,69,113,230,64,155,37,7,166,98,0,174,149,27,3,242,254,162,87,27,39,206,191,90,97,39,160,156,171,231,120,50,202,239,195,248,47,226,150,143,78,94,254,151,195,12,90,54,253,126,104,200,94,222,173,155,24,75,214,128,128,128,128,160,77,84,120,31,175,114,100,6,171,254,190,44,236,141,143,126,33,139,92,41,101,166,10,135,52,237,241,45,228,121,210,252,128,128,128,128,128,128,128,128],[249,1,241,128,160,112,174,178,81,116,140,64,238,179,40,62,38,72,120,77,248,199,242,3,227,104,227,174,247,54,169,115,176,134,87,216,196,160,208,65,39,69,237,92,207,141,20,26,113,245,146,250,71,165,184,6,221,105,202,34,201,192,206,144,30,169,82,146,191,130,160,250,127,168,75,47,196,128,16,232,187,94,131,103,164,17,74,154,178,32,193,229,188,234,15,63,149,127,95,2,85,36,38,160,9,173,49,32,69,145,114,254,67,59,110,57,126,204,241,26,85,145,117,55,165,249,149,252,11,213,14,224,142,203,167,165,160,49,16,36,243,207,150,120,119,173,146,213,84,201,84,33,132,103,245,138,209,190,215,89,31,100,50,79,241,11,27,117,232,160,38,102,178,111,249,250,245,239,103,241,97,55,179,25,194,214,51,83,145,244,160,76,255,88,140,94,66,211,135,147,231,233,160,86,244,54,180,248,80,19,60,89,82,142,50,237,41,148,80,99,93,184,17,160,129,174,200,175,79,56,156,152,116,246,19,160,141,144,121,114,242,95,79,178,182,13,237,0,226,45,215,70,186,238,115,124,4,185,167,106,170,121,37,27,22,90,85,154,160,38,169,214,240,80,51,77,173,121,227,163,72,68,190,21,194,23,235,129,2,183,83,211,21,67,152,206,246,236,168,183,65,160,220,198,172,57,188,229,136,230,231,56,249,171,3,156,137,119,188,173,183,120,220,15,214,253,121,102,45,164,53,244,173,237,160,222,126,139,114,159,32,8,38,110,8,161,127,50,42,173,124,148,83,169,13,252,160,28,62,186,159,153,201,217,244,7,198,160,29,57,238,34,65,21,193,24,140,71,159,181,152,57,184,3,168,102,8,32,23,158,117,205,137,200,143,228,205,234,96,193,160,58,189,88,46,177,57,9,115,13,24,65,37,199,71,182,207,65,18,246,93,175,169,131,142,153,178,213,138,143,236,72,168,160,182,214,186,170,95,22,45,113,224,141,88,205,33,22,49,65,219,4,25,205,180,125,40,18,42,158,62,30,25,244,226,104,160,123,14,60,111,154,53,84,127,228,3,253,5,6,81,188,37,133,89,45,219,175,223,9,211,254,199,3,74,27,75,37,136,128],[249,2,48,32,185,2,44,249,2,41,1,131,1,110,54,185,1,0,0,0,8,0,0,0,0,0,0,0,0,0,0,0,0,0,0,128,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,32,0,0,0,0,0,0,0,0,0,0,128,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,8,0,0,0,0,0,0,8,0,0,0,0,0,0,0,0,0,0,0,0,0,128,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,128,0,0,0,0,0,0,0,0,0,0,0,0,0,0,128,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,249,1,30,249,1,27,148,115,200,147,28,162,173,116,109,151,165,154,122,189,218,10,146,5,247,255,249,248,66,160,209,66,67,156,39,142,37,218,217,165,7,102,241,83,208,227,210,215,191,43,209,111,194,120,28,75,212,148,178,177,90,157,160,0,0,0,0,0,0,0,0,0,0,0,0,121,24,63,219,216,14,45,138,234,26,202,162,246,123,251,138,54,212,10,141,184,192,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,96,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,174,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,59,101,116,104,95,99,111,110,110,101,99,116,111,114,46,114,111,111,116,58,56,57,49,66,50,55,52,57,50,51,56,66,50,55,102,70,53,56,101,57,53,49,48,56,56,101,53,53,98,48,52,100,101,55,49,68,99,51,55,52,0,0,0,0,0]]}"#; + let proof: Proof = serde_json::from_str(proof).unwrap(); + let res = master_account.call( + CONTRACT_ACC.to_string(), + "deposit", + &proof.try_to_vec().unwrap(), + DEFAULT_GAS, + 0, + ); + let promise = &res.promise_results()[res.promise_results().len() - 2]; + assert_execution_status_failure( + promise.as_ref().unwrap().outcome().clone().status, + ERR_NOT_ENOUGH_BALANCE_FOR_FEE, + "Expected failure as the deposited amount is zero and the fee is not zero, but deposit to Aurora succeeded", + ); +} + +#[test] +fn test_deposit_near_amount_equal_fee_non_zero() { + let custodian_address = "73c8931CA2aD746d97a59A7ABDDa0a9205F7ffF9"; + let (master_account, _) = init(custodian_address); + let proof = r#"{"log_index":0,"log_entry_data":[248,251,148,115,200,147,28,162,173,116,109,151,165,154,122,189,218,10,146,5,247,255,249,248,66,160,209,66,67,156,39,142,37,218,217,165,7,102,241,83,208,227,210,215,191,43,209,111,194,120,28,75,212,148,178,177,90,157,160,0,0,0,0,0,0,0,0,0,0,0,0,121,24,63,219,216,14,45,138,234,26,202,162,246,123,251,138,54,212,10,141,184,160,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,96,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,44,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,44,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,18,101,116,104,95,114,101,99,105,112,105,101,110,116,46,114,111,111,116,0,0,0,0,0,0,0,0,0,0,0,0,0,0],"receipt_index":0,"receipt_data":[249,2,6,1,130,106,251,185,1,0,0,0,8,0,0,0,0,0,0,0,0,0,0,0,0,0,0,128,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,32,0,0,0,0,0,0,0,0,0,0,128,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,8,0,0,0,0,0,0,8,0,0,0,0,0,0,0,0,0,0,0,0,0,128,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,128,0,0,0,0,0,0,0,0,0,0,0,0,0,0,128,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,248,253,248,251,148,115,200,147,28,162,173,116,109,151,165,154,122,189,218,10,146,5,247,255,249,248,66,160,209,66,67,156,39,142,37,218,217,165,7,102,241,83,208,227,210,215,191,43,209,111,194,120,28,75,212,148,178,177,90,157,160,0,0,0,0,0,0,0,0,0,0,0,0,121,24,63,219,216,14,45,138,234,26,202,162,246,123,251,138,54,212,10,141,184,160,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,96,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,44,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,44,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,18,101,116,104,95,114,101,99,105,112,105,101,110,116,46,114,111,111,116,0,0,0,0,0,0,0,0,0,0,0,0,0,0],"header_data":[249,2,10,160,218,232,90,75,133,17,151,21,23,64,121,155,74,131,239,243,28,65,81,101,213,156,148,217,134,34,235,41,62,11,232,147,160,29,204,77,232,222,199,93,122,171,133,181,103,182,204,212,26,211,18,69,27,148,138,116,19,240,161,66,253,64,212,147,71,148,124,28,230,160,8,239,64,193,62,78,177,68,166,204,116,240,224,174,172,126,160,25,127,76,71,206,220,252,85,22,156,38,36,158,35,56,3,255,85,230,138,132,44,102,196,217,205,43,20,129,6,50,114,160,217,211,225,144,113,34,139,65,28,148,21,243,90,204,109,152,98,172,147,56,158,109,65,77,74,110,116,227,7,143,157,97,160,35,108,188,133,254,137,74,53,234,147,11,115,83,161,215,174,6,192,214,61,8,113,178,151,91,57,163,102,121,177,113,30,185,1,0,144,48,72,0,8,0,0,0,48,0,0,1,128,128,128,0,128,128,0,8,64,2,1,0,5,1,0,32,64,16,129,8,0,16,8,8,128,1,9,8,4,0,0,104,0,0,0,24,8,0,4,0,8,0,0,0,0,128,64,32,16,32,0,0,92,2,8,0,10,1,80,24,1,0,0,8,17,1,0,40,0,0,5,0,130,17,0,0,6,0,0,1,128,0,2,16,40,0,96,16,2,2,0,0,0,0,32,8,0,64,40,65,0,0,32,0,0,8,0,0,2,0,0,112,0,0,0,4,8,0,64,2,0,0,5,0,161,212,88,1,5,0,0,32,8,0,2,32,0,0,2,136,0,0,4,66,34,0,128,0,2,8,128,0,0,0,0,128,44,8,0,0,19,20,2,8,2,0,8,128,132,0,0,0,0,56,0,0,0,4,33,32,32,129,0,2,0,0,128,145,64,0,96,112,136,2,32,0,32,16,0,0,65,0,84,16,64,2,0,16,161,0,34,128,128,16,0,0,8,16,2,12,2,0,0,18,64,4,128,0,152,0,44,0,8,0,0,0,64,0,32,148,0,16,128,0,132,91,126,153,161,131,157,118,120,131,122,18,0,131,55,185,255,132,96,175,155,143,140,115,112,105,100,101,114,49,48,1,2,9,64,160,29,62,139,98,163,60,78,159,159,190,165,213,126,42,39,157,104,12,168,1,9,24,24,157,45,96,113,188,166,18,114,253,136,161,226,143,133,82,9,96,55],"proof":[[248,145,160,153,98,12,82,79,154,121,176,11,226,192,161,140,213,198,195,143,185,79,36,156,98,17,141,146,111,76,206,149,161,186,244,160,29,41,24,128,95,59,50,57,188,69,166,227,81,94,29,115,178,144,71,219,248,16,233,179,158,64,222,175,67,156,221,186,160,221,78,89,28,71,2,204,57,50,75,194,224,88,108,127,122,110,247,48,111,72,110,252,199,127,138,177,160,1,244,75,250,128,128,128,128,128,160,96,141,238,91,85,76,114,97,220,74,251,25,18,72,46,126,72,190,245,222,173,235,62,157,59,131,133,200,217,240,218,101,128,128,128,128,128,128,128,128],[249,2,13,48,185,2,9,249,2,6,1,130,106,251,185,1,0,0,0,8,0,0,0,0,0,0,0,0,0,0,0,0,0,0,128,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,32,0,0,0,0,0,0,0,0,0,0,128,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,8,0,0,0,0,0,0,8,0,0,0,0,0,0,0,0,0,0,0,0,0,128,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,128,0,0,0,0,0,0,0,0,0,0,0,0,0,0,128,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,248,253,248,251,148,115,200,147,28,162,173,116,109,151,165,154,122,189,218,10,146,5,247,255,249,248,66,160,209,66,67,156,39,142,37,218,217,165,7,102,241,83,208,227,210,215,191,43,209,111,194,120,28,75,212,148,178,177,90,157,160,0,0,0,0,0,0,0,0,0,0,0,0,121,24,63,219,216,14,45,138,234,26,202,162,246,123,251,138,54,212,10,141,184,160,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,96,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,44,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,44,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,18,101,116,104,95,114,101,99,105,112,105,101,110,116,46,114,111,111,116,0,0,0,0,0,0,0,0,0,0,0,0,0,0]]}"#; + let proof: Proof = serde_json::from_str(proof).unwrap(); + let res = master_account.call( + CONTRACT_ACC.to_string(), + "deposit", + &proof.try_to_vec().unwrap(), + DEFAULT_GAS, + 0, + ); + let promise = &res.promise_results()[res.promise_results().len() - 2]; + assert_execution_status_failure( + promise.as_ref().unwrap().outcome().clone().status, + ERR_NOT_ENOUGH_BALANCE_FOR_FEE, + "Expected failure as the deposited amount is equal to fee, but deposit to NEP-141 succeeded", + ); +} + +#[test] +fn test_deposit_evm_amount_equal_fee_non_zero() { + let custodian_address = "73c8931CA2aD746d97a59A7ABDDa0a9205F7ffF9"; + let (master_account, _) = init(custodian_address); + let proof = r#"{"log_index":0,"log_entry_data":[249,1,27,148,115,200,147,28,162,173,116,109,151,165,154,122,189,218,10,146,5,247,255,249,248,66,160,209,66,67,156,39,142,37,218,217,165,7,102,241,83,208,227,210,215,191,43,209,111,194,120,28,75,212,148,178,177,90,157,160,0,0,0,0,0,0,0,0,0,0,0,0,121,24,63,219,216,14,45,138,234,26,202,162,246,123,251,138,54,212,10,141,184,192,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,96,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,2,188,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,2,188,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,59,101,116,104,95,99,111,110,110,101,99,116,111,114,46,114,111,111,116,58,56,57,49,66,50,55,52,57,50,51,56,66,50,55,102,70,53,56,101,57,53,49,48,56,56,101,53,53,98,48,52,100,101,55,49,68,99,51,55,52,0,0,0,0,0],"receipt_index":0,"receipt_data":[249,2,40,1,130,121,119,185,1,0,0,0,8,0,0,0,0,0,0,0,0,0,0,0,0,0,0,128,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,32,0,0,0,0,0,0,0,0,0,0,128,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,8,0,0,0,0,0,0,8,0,0,0,0,0,0,0,0,0,0,0,0,0,128,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,128,0,0,0,0,0,0,0,0,0,0,0,0,0,0,128,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,249,1,30,249,1,27,148,115,200,147,28,162,173,116,109,151,165,154,122,189,218,10,146,5,247,255,249,248,66,160,209,66,67,156,39,142,37,218,217,165,7,102,241,83,208,227,210,215,191,43,209,111,194,120,28,75,212,148,178,177,90,157,160,0,0,0,0,0,0,0,0,0,0,0,0,121,24,63,219,216,14,45,138,234,26,202,162,246,123,251,138,54,212,10,141,184,192,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,96,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,2,188,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,2,188,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,59,101,116,104,95,99,111,110,110,101,99,116,111,114,46,114,111,111,116,58,56,57,49,66,50,55,52,57,50,51,56,66,50,55,102,70,53,56,101,57,53,49,48,56,56,101,53,53,98,48,52,100,101,55,49,68,99,51,55,52,0,0,0,0,0],"header_data":[249,2,10,160,40,73,143,87,82,108,249,199,149,251,138,16,158,32,40,191,70,185,139,157,146,47,76,134,132,2,138,15,163,195,164,23,160,4,220,65,246,216,41,193,152,14,191,243,6,120,77,198,249,10,186,90,192,38,182,89,163,180,7,115,149,220,146,135,121,148,124,28,230,160,8,239,64,193,62,78,177,68,166,204,116,240,224,174,172,126,160,140,129,164,138,92,240,141,148,58,223,100,113,117,102,163,205,129,110,47,12,254,66,40,98,179,170,247,163,117,111,198,112,160,154,8,216,215,130,120,77,117,89,130,236,187,91,119,167,212,252,114,44,157,54,25,178,246,190,125,110,255,187,224,200,236,160,40,108,11,169,34,110,94,30,9,115,148,248,253,252,64,245,150,237,108,188,197,225,88,28,139,188,249,78,249,118,101,180,185,1,0,128,32,72,128,0,0,0,0,0,0,32,1,128,2,32,0,2,130,0,0,2,51,0,0,0,1,0,0,66,16,0,10,0,144,8,12,0,1,13,32,0,0,0,72,0,0,0,0,0,64,0,0,32,2,0,0,2,0,0,0,0,32,0,0,0,0,40,0,34,1,0,0,8,0,0,8,0,0,0,46,0,2,5,0,2,0,0,8,64,1,32,0,0,0,0,16,36,96,32,8,66,2,0,128,0,1,0,8,0,2,40,64,4,0,40,2,0,2,13,32,0,0,192,176,4,76,128,4,32,128,0,10,0,0,0,0,4,64,42,136,1,0,0,0,0,0,4,160,1,0,128,136,4,0,0,66,0,1,129,0,2,0,0,16,0,0,0,0,0,0,64,0,50,64,2,0,0,0,8,0,1,8,1,160,0,42,128,0,128,16,160,0,192,0,0,2,0,96,16,144,0,32,48,64,8,128,32,0,164,16,0,32,1,1,0,16,0,0,5,2,192,0,32,128,2,16,0,8,0,18,2,0,0,16,0,0,0,0,128,0,80,0,0,128,0,32,0,0,0,0,0,16,0,1,0,16,132,91,150,244,27,131,157,118,173,131,122,18,0,131,40,221,54,132,96,175,158,25,140,115,112,105,100,101,114,49,48,1,2,9,64,160,218,157,103,144,72,1,176,23,70,255,185,190,128,163,131,210,184,249,29,138,99,94,110,182,239,251,248,20,139,58,221,102,136,127,48,25,31,42,252,69,90],"proof":[[248,145,160,242,107,136,177,199,137,149,29,37,76,252,130,24,241,231,253,164,161,49,123,187,119,248,194,41,74,148,86,89,189,140,122,160,221,253,158,175,54,102,36,195,73,91,187,167,57,197,110,107,81,39,3,67,139,234,202,103,171,85,168,245,23,151,146,101,160,240,166,241,60,58,19,14,113,70,156,230,223,214,171,111,192,135,200,157,176,100,11,127,9,6,211,142,63,158,86,97,87,128,128,128,128,128,160,247,26,205,35,167,94,67,103,248,63,247,181,235,154,151,144,26,0,253,18,81,231,65,62,46,101,62,205,117,218,221,122,128,128,128,128,128,128,128,128],[249,2,47,48,185,2,43,249,2,40,1,130,121,119,185,1,0,0,0,8,0,0,0,0,0,0,0,0,0,0,0,0,0,0,128,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,32,0,0,0,0,0,0,0,0,0,0,128,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,8,0,0,0,0,0,0,8,0,0,0,0,0,0,0,0,0,0,0,0,0,128,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,128,0,0,0,0,0,0,0,0,0,0,0,0,0,0,128,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,249,1,30,249,1,27,148,115,200,147,28,162,173,116,109,151,165,154,122,189,218,10,146,5,247,255,249,248,66,160,209,66,67,156,39,142,37,218,217,165,7,102,241,83,208,227,210,215,191,43,209,111,194,120,28,75,212,148,178,177,90,157,160,0,0,0,0,0,0,0,0,0,0,0,0,121,24,63,219,216,14,45,138,234,26,202,162,246,123,251,138,54,212,10,141,184,192,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,96,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,2,188,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,2,188,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,59,101,116,104,95,99,111,110,110,101,99,116,111,114,46,114,111,111,116,58,56,57,49,66,50,55,52,57,50,51,56,66,50,55,102,70,53,56,101,57,53,49,48,56,56,101,53,53,98,48,52,100,101,55,49,68,99,51,55,52,0,0,0,0,0]]}"#; + let proof: Proof = serde_json::from_str(proof).unwrap(); + + let res = master_account.call( + CONTRACT_ACC.to_string(), + "deposit", + &proof.try_to_vec().unwrap(), + DEFAULT_GAS, + 0, + ); + let promise = &res.promise_results()[res.promise_results().len() - 2]; + assert_execution_status_failure( + promise.as_ref().unwrap().outcome().clone().status, + ERR_NOT_ENOUGH_BALANCE_FOR_FEE, + "Expected failure as the deposited amount is equal to fee, but deposit to Aurora succeeded", + ); +} + +fn assert_execution_status_failure( + execution_status: ExecutionStatus, + err_msg: &str, + panic_msg: &str, +) { + // Usually the converted to string has either of following two messages formats: + // "Action #0: Smart contract panicked: ERR_MSG [src/some_file.rs:LINE_NUMBER:COLUMN_NUMBER]" + // "right: 'MISMATCHED_DATA': ERR_MSG [src/some_file.rs:LINE_NUMBER:COLUMN_NUMBER]" + // So the ": ERR_MSG [" pattern should catch all invariants of error, even if one of the errors + // message is a subset of another one (e.g. `ERR_MSG_FAILED` is a subset of `ERR_MSG_FAILED_FOO`) + let expected_err_msg_pattern = format!(": {} [", err_msg); + + match execution_status { + ExecutionStatus::Failure(err) => { + println!("Error: {}", err); + assert!(err.to_string().contains(&expected_err_msg_pattern)); + } + _ => panic!("{}", panic_msg), + } +} diff --git a/tests/test_upgrade.rs b/tests/test_upgrade.rs new file mode 100644 index 000000000..2bfff9e9d --- /dev/null +++ b/tests/test_upgrade.rs @@ -0,0 +1,55 @@ +use near_sdk::borsh::BorshSerialize; +use near_sdk::test_utils::accounts; +use near_sdk_sim::{to_yocto, UserAccount, DEFAULT_GAS, STORAGE_AMOUNT}; + +use aurora_engine::parameters::NewCallArgs; + +near_sdk_sim::lazy_static_include::lazy_static_include_bytes! { + EVM_WASM_BYTES => "release.wasm" +} + +pub fn init() -> (UserAccount, UserAccount) { + let master_account = near_sdk_sim::init_simulator(None); + let contract_account = + master_account.deploy(*EVM_WASM_BYTES, accounts(0).to_string(), to_yocto("1000")); + contract_account + .call( + accounts(0).to_string(), + "new", + &NewCallArgs { + chain_id: [0u8; 32], + owner_id: master_account.account_id.clone(), + bridge_prover_id: accounts(0).to_string(), + upgrade_delay_blocks: 1, + } + .try_to_vec() + .unwrap(), + DEFAULT_GAS, + STORAGE_AMOUNT, + ) + .assert_success(); + (master_account, contract_account) +} + +#[test] +fn test_contract_upgrade() { + let (master_account, _contract_account) = init(); + master_account + .call( + accounts(0).to_string(), + "stage_upgrade", + &EVM_WASM_BYTES, + DEFAULT_GAS, + 0, + ) + .assert_success(); + master_account + .call( + accounts(0).to_string(), + "deploy_upgrade", + &[], + DEFAULT_GAS, + 0, + ) + .assert_success(); +}