From 8699150ec2ffc3f8f7f762037e8d2407c3d24011 Mon Sep 17 00:00:00 2001 From: Oleksandr Anyshchenko Date: Tue, 17 Oct 2023 13:41:04 +0100 Subject: [PATCH] Release 3.2.0 (#852) ## Release 3.2.0 ### Changes - Changed structure `SetEthConnectorContractAccountArgs` for setting eth connector account. It was extended with additional field: `withdraw_serialize_type` for defining serialization type for withdraw arguments by [@aleksuss]. ([#834]) - Updated rocksdb up to 0.21.0 by [@aleksuss]. ([#840]) ### Additions - Added a possibility of mirroring deployed ERC-20 contracts in the main Aurora contract in Silo mode by [@aleksuss]. ([#844]) - Allow to initialize hashchain directly with the `new` method by [@birchmd]. ([#846]) - Added a silo logic which allows to set fixed gas costs per transaction by [@aleksuss]. ([#746]) - Added a new type of transaction which allows to add full access key into account of the smart contract by [@aleksuss]. ([#847]) [#746]: https://github.com/aurora-is-near/aurora-engine/pull/746 [#834]: https://github.com/aurora-is-near/aurora-engine/pull/834 [#840]: https://github.com/aurora-is-near/aurora-engine/pull/840 [#844]: https://github.com/aurora-is-near/aurora-engine/pull/844 [#846]: https://github.com/aurora-is-near/aurora-engine/pull/846 [#847]: https://github.com/aurora-is-near/aurora-engine/pull/847 --------- Signed-off-by: dependabot[bot] Co-authored-by: i-fix-typos <146758284+i-fix-typos@users.noreply.github.com> Co-authored-by: Evgeny Ukhanov Co-authored-by: Michael Birch Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: Karim Co-authored-by: Joshua J. Bouw Co-authored-by: ForwardSlashBack <142098649+ForwardSlashBack@users.noreply.github.com> --- .env/mainnet-silo.env | 10 + .env/testnet-silo.env | 10 + .github/CODEOWNERS | 21 +- .github/workflows/builds.yml | 2 +- .github/workflows/scheduled_lints.yml | 13 +- .github/workflows/tests.yml | 100 +- CHANGES.md | 43 +- Cargo.lock | 219 +- Cargo.toml | 6 +- Makefile.toml | 103 +- README.md | 76 +- VERSION | 2 +- doc/eth-connector.md | 1 - engine-precompiles/Cargo.toml | 1 + engine-precompiles/src/lib.rs | 3 + engine-precompiles/src/native.rs | 118 +- engine-sdk/src/near_runtime.rs | 34 +- engine-sdk/src/promise.rs | 9 + engine-standalone-storage/Cargo.toml | 3 +- engine-standalone-storage/src/error.rs | 2 +- engine-standalone-storage/src/promise.rs | 4 + .../src/relayer_db/mod.rs | 7 +- engine-standalone-storage/src/sync/mod.rs | 239 +- engine-standalone-storage/src/sync/types.rs | 244 +- engine-test-doubles/src/promise.rs | 18 +- engine-tests-connector/Cargo.toml | 31 + .../src/connector.rs | 1968 +++++++++-------- engine-tests-connector/src/lib.rs | 6 + engine-tests-connector/src/rust.rs | 15 + engine-tests-connector/src/utils.rs | 354 +++ engine-tests/Cargo.toml | 7 +- .../src/benches/eth_standard_precompiles.rs | 2 +- engine-tests/src/prelude.rs | 2 - .../tests/{relayer_keys.rs => access_keys.rs} | 60 +- engine-tests/src/tests/contract_call.rs | 4 +- engine-tests/src/tests/erc20.rs | 22 +- engine-tests/src/tests/erc20_connector.rs | 85 +- engine-tests/src/tests/erc20_mirror.rs | 262 +++ engine-tests/src/tests/hashchain.rs | 14 +- engine-tests/src/tests/mod.rs | 5 +- engine-tests/src/tests/one_inch.rs | 10 +- .../src/tests/pausable_precompiles.rs | 21 +- engine-tests/src/tests/repro.rs | 6 +- engine-tests/src/tests/sanity.rs | 15 +- engine-tests/src/tests/silo.rs | 1095 +++++++++ engine-tests/src/tests/standalone/sanity.rs | 1 + engine-tests/src/tests/standalone/sync.rs | 28 +- engine-tests/src/tests/standalone/tracing.rs | 2 +- .../src/tests/standard_precompiles.rs | 4 +- engine-tests/src/tests/uniswap.rs | 2 +- engine-tests/src/tests/xcc.rs | 9 +- engine-tests/src/utils/mocked_external.rs | 2 +- engine-tests/src/utils/mod.rs | 211 +- .../src/utils/one_inch/liquidity_protocol.rs | 13 +- engine-tests/src/utils/one_inch/mod.rs | 31 +- .../src/utils/standalone/mocks/mod.rs | 85 +- engine-tests/src/utils/standalone/mod.rs | 13 +- engine-tests/src/utils/workspace.rs | 75 +- engine-types/src/account_id.rs | 8 +- engine-types/src/lib.rs | 4 + engine-types/src/parameters/connector.rs | 66 +- engine-types/src/parameters/engine.rs | 55 +- engine-types/src/parameters/mod.rs | 1 + engine-types/src/parameters/silo.rs | 117 + engine-types/src/public_key.rs | 32 +- engine-types/src/storage.rs | 11 + engine-types/src/types/address.rs | 8 +- engine-types/src/types/balance.rs | 41 +- engine-types/src/types/wei.rs | 49 +- engine-workspace/Cargo.toml | 5 + engine-workspace/src/account.rs | 3 +- engine-workspace/src/contract.rs | 124 +- engine-workspace/src/lib.rs | 23 +- engine-workspace/src/operation.rs | 68 +- engine-workspace/src/result.rs | 14 + engine/Cargo.toml | 5 +- engine/src/contract_methods/admin.rs | 92 +- engine/src/contract_methods/connector.rs | 476 ---- .../connector}/admin_controlled.rs | 4 +- .../connector}/deposit_event.rs | 136 +- .../src/contract_methods/connector/errors.rs | 336 +++ .../contract_methods/connector/external.rs | 614 +++++ .../connector}/fungible_token.rs | 160 +- .../connector/internal.rs} | 881 ++++---- engine/src/contract_methods/connector/mod.rs | 652 ++++++ .../src/contract_methods/evm_transactions.rs | 2 +- engine/src/contract_methods/mod.rs | 1 + engine/src/contract_methods/silo/mod.rs | 254 +++ engine/src/contract_methods/silo/whitelist.rs | 147 ++ engine/src/engine.rs | 429 +++- engine/src/errors.rs | 9 + engine/src/lib.rs | 424 ++-- engine/src/state.rs | 20 +- etc/eth-contracts/yarn.lock | 6 +- 94 files changed, 8058 insertions(+), 2967 deletions(-) create mode 100644 .env/mainnet-silo.env create mode 100644 .env/testnet-silo.env create mode 100644 engine-tests-connector/Cargo.toml rename engine-tests/src/tests/eth_connector.rs => engine-tests-connector/src/connector.rs (53%) create mode 100644 engine-tests-connector/src/lib.rs create mode 100644 engine-tests-connector/src/rust.rs create mode 100644 engine-tests-connector/src/utils.rs rename engine-tests/src/tests/{relayer_keys.rs => access_keys.rs} (82%) create mode 100644 engine-tests/src/tests/erc20_mirror.rs create mode 100644 engine-tests/src/tests/silo.rs create mode 100644 engine-types/src/parameters/silo.rs delete mode 100644 engine/src/contract_methods/connector.rs rename engine/src/{ => contract_methods/connector}/admin_controlled.rs (98%) rename engine/src/{ => contract_methods/connector}/deposit_event.rs (80%) create mode 100644 engine/src/contract_methods/connector/errors.rs create mode 100644 engine/src/contract_methods/connector/external.rs rename engine/src/{ => contract_methods/connector}/fungible_token.rs (77%) rename engine/src/{connector.rs => contract_methods/connector/internal.rs} (54%) create mode 100644 engine/src/contract_methods/connector/mod.rs create mode 100644 engine/src/contract_methods/silo/mod.rs create mode 100644 engine/src/contract_methods/silo/whitelist.rs diff --git a/.env/mainnet-silo.env b/.env/mainnet-silo.env new file mode 100644 index 000000000..6bc235a54 --- /dev/null +++ b/.env/mainnet-silo.env @@ -0,0 +1,10 @@ +CARGO_FEATURES_BUILD="mainnet,ext-connector" +CARGO_FEATURES_BUILD_TEST="mainnet,integration-test,ext-connector" +CARGO_FEATURES_TEST="mainnet-test,ext-connector" +RUSTC_FLAGS_BUILD="-C link-arg=-s" +NEAR_EVM_ACCOUNT="aurora" +WASM_FILE="aurora-mainnet-silo.wasm" +WASM_FILE_TEST="aurora-mainnet-silo-test.wasm" +NEAR_CLI="near" +PROFILE="mainnet-silo" +IS_PROD=true diff --git a/.env/testnet-silo.env b/.env/testnet-silo.env new file mode 100644 index 000000000..ce6ba2862 --- /dev/null +++ b/.env/testnet-silo.env @@ -0,0 +1,10 @@ +CARGO_FEATURES_BUILD="testnet,ext-connector" +CARGO_FEATURES_BUILD_TEST="testnet,integration-test,ext-connector" +CARGO_FEATURES_TEST="testnet-test,ext-connector" +RUSTC_FLAGS_BUILD="-C link-arg=-s" +NEAR_EVM_ACCOUNT="aurora" +WASM_FILE="aurora-testnet-silo.wasm" +WASM_FILE_TEST="aurora-testnet-silo-test.wasm" +NEAR_CLI="near" +PROFILE="testnet-silo" +IS_PROD=false diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index 1abaa359a..fd663336a 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -1,9 +1,12 @@ -* @joshuajbouw -/engine/ @aleksuss -/engine-precompiles/ @mandreyel -/engine-sdk/ @aleksuss -/engine-standalone-storage/ @RomanHodulak -/engine-standalone-tracing/ @RomanHodulak -/engine-test-doubles/ @joshuajbouw -/engine-tests/ @joshuajbouw -/engine-transactions/ @aleksuss +* @aleksuss +/engine/ @birchmd +/engine-hashchain/ @birchmd +/engine-modexp/ @birchmd +/engine-precompiles/ @birchmd +/engine-sdk/ @mrLSD +/engine-standalone-storage/ @birchmd +/engine-standalone-tracing/ @birchmd +/engine-tests-connector/ @mrLSD +/engine-test-doubles/ @mrLSD +/engine-tests/ @mrLSD +/engine-workspace/ @mrLSD diff --git a/.github/workflows/builds.yml b/.github/workflows/builds.yml index 15f889f39..ae5a40b72 100644 --- a/.github/workflows/builds.yml +++ b/.github/workflows/builds.yml @@ -10,7 +10,7 @@ jobs: runs-on: [self-hosted, heavy] strategy: matrix: - profile: [mainnet, testnet] + profile: [mainnet, mainnet-silo, testnet, testnet-silo] steps: - name: Potential broken submodules fix run: | diff --git a/.github/workflows/scheduled_lints.yml b/.github/workflows/scheduled_lints.yml index b1cb9dc8e..4f7b01186 100644 --- a/.github/workflows/scheduled_lints.yml +++ b/.github/workflows/scheduled_lints.yml @@ -12,11 +12,16 @@ jobs: run: | git checkout -f $(git -c user.name=x -c user.email=x@x commit-tree $(git hash-object -t tree /dev/null) < /dev/null) || : - name: Clone the repository - uses: actions/checkout@v3 + uses: actions/checkout@v4 - name: Test mainnet - run: cargo make --profile mainnet test + run: cargo make --profile mainnet test-flow - name: Test testnet - run: cargo make --profile testnet test + run: cargo make --profile testnet test-flow + - name: Test mainnet silo + run: cargo make --profile mainnet-silo test-flow + - name: Test testnet silo + run: cargo make --profile testnet-silo test-flow + checks: name: Run checks runs-on: [self-hosted, heavy] @@ -25,5 +30,5 @@ jobs: run: | git checkout -f $(git -c user.name=x -c user.email=x@x commit-tree $(git hash-object -t tree /dev/null) < /dev/null) || : - name: Clone the repository - uses: actions/checkout@v3 + uses: actions/checkout@v4 - run: cargo make check diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 6e0448ba1..9f20672cf 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -9,56 +9,80 @@ on: name: Tests jobs: test: - name: Test suite (mainnet, testnet) - runs-on: [self-hosted, heavy] + name: Test suite ${{ matrix.profile }} + runs-on: github-hosted-heavy-runner + strategy: + fail-fast: false + matrix: + profile: [ mainnet, testnet, mainnet-silo, testnet-silo ] steps: - name: Potential broken submodules fix run: | git checkout -f $(git -c user.name=x -c user.email=x@x commit-tree $(git hash-object -t tree /dev/null) < /dev/null) || : - name: Clone the repository - uses: actions/checkout@v3 - - name: Restore cache - run: cache-util restore cargo_git cargo_registry yarn_cache - - name: Build contracts - run: cargo make build-contracts - - name: Test contracts - run: cargo make test-contracts - - name: Build mainnet test WASM - run: cargo make --profile mainnet build-test - - name: List mainnet WASM directory and root directory - run: ls -la target/wasm32-unknown-unknown/release && ls -la - - name: Test mainnet - run: cargo make --profile mainnet test-workspace - - name: Build testnet test WASM - run: cargo make --profile testnet build-test - - name: List testnet WASM directory and root directory - run: ls -la target/wasm32-unknown-unknown/release && ls -la - - name: Test testnet - run: cargo make --profile testnet test-workspace - - name: Save cache - run: cache-util save cargo_git cargo_registry yarn_cache + uses: actions/checkout@v4 + - name: Cargo Cache + uses: actions/cache@v3 + with: + path: | + ~/.cargo/bin/ + ~/.cargo/registry/index/ + ~/.cargo/registry/cache/ + ~/.cargo/git/db/ + target/ + key: ${{ matrix.profile }}-cargo-test + - name: Setup Node and cache + uses: actions/setup-node@v3 + with: + node-version: 16 + cache: 'yarn' + cache-dependency-path: | + etc/eth-contracts + etc/tests/uniswap + - name: Install dependencies + run: cargo make -V || cargo install cargo-make + - name: Build main contract + run: | + case ${{ matrix.profile }} in + mainnet-silo) + cargo make --profile mainnet build-test + ;; + testnet-silo) + cargo make --profile testnet build-test + ;; + *) + ;; + esac + - name: Tests ${{ matrix.profile }} + run: cargo make --profile ${{ matrix.profile }} test-flow test_modexp: - name: Test modexp suite (mainnet, testnet) - runs-on: [ self-hosted, heavy ] + name: Test modexp suite ${{ matrix.profile }} + runs-on: github-hosted-heavy-runner + strategy: + fail-fast: false + matrix: + profile: [ mainnet, testnet ] steps: - name: Potential broken submodules fix run: | git checkout -f $(git -c user.name=x -c user.email=x@x commit-tree $(git hash-object -t tree /dev/null) < /dev/null) || : - name: Clone the repository - uses: actions/checkout@v3 - - name: Restore cache - run: | - cache-util restore cargo_git cargo_registry yarn_cache - cache-util restore aurora-engine-target@generic@${{ hashFiles('**/Cargo.lock') }}:target - - name: Test mainnet bench-modexp - run: cargo make --profile mainnet bench-modexp - - name: Test testnet bench-modexp - run: cargo make --profile testnet bench-modexp - - name: Save cache - run: | - cache-util save cargo_git cargo_registry yarn_cache - cache-util msave aurora-engine-target@generic@${{ hashFiles('**/Cargo.lock') }}:target + uses: actions/checkout@v4 + - name: Cargo Cache + uses: actions/cache@v3 + with: + path: | + ~/.cargo/bin/ + ~/.cargo/registry/index/ + ~/.cargo/registry/cache/ + ~/.cargo/git/db/ + target/ + key: ${{ matrix.profile }}-cargo-modexp-test + - name: Install dependencies + run: cargo make -V || cargo install cargo-make + - name: Test ${{ matrix.profile }} bench-modexp + run: cargo make --profile ${{ matrix.profile }} bench-modexp env: CARGO_TERM_COLOR: always diff --git a/CHANGES.md b/CHANGES.md index a176f1050..9c0ec25a0 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -7,8 +7,36 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] +## [3.2.0] 2023-10-17 + +### Changes + +- Changed structure `SetEthConnectorContractAccountArgs` for setting eth connector account. It was extended with + additional field: `withdraw_serialize_type` for defining serialization type for withdraw arguments by [@aleksuss]. ([#834]) + +- Updated rocksdb up to 0.21.0 by [@aleksuss]. ([#840]) + +### Additions + +- Added a possibility of mirroring deployed ERC-20 contracts in the main Aurora contract in Silo mode by [@aleksuss]. ([#844]) + +- Allow to initialize hashchain directly with the `new` method by [@birchmd]. ([#846]) + +- Added a silo logic which allows to set fixed gas costs per transaction by [@aleksuss]. ([#746]) + +- Added a new type of transaction which allows to add full access key into account of the smart contract by [@aleksuss]. ([#847]) + +[#746]: https://github.com/aurora-is-near/aurora-engine/pull/746 +[#834]: https://github.com/aurora-is-near/aurora-engine/pull/834 +[#840]: https://github.com/aurora-is-near/aurora-engine/pull/840 +[#844]: https://github.com/aurora-is-near/aurora-engine/pull/844 +[#846]: https://github.com/aurora-is-near/aurora-engine/pull/846 +[#847]: https://github.com/aurora-is-near/aurora-engine/pull/847 + ## [3.1.0] 2023-09-25 +### Additions + - Added the possibility to use native NEAR instead of wNEAR on Aurora by [@karim-en]. ([#750]) - Added hashchain integration by [@birchmd]. ([#831]) @@ -24,15 +52,18 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Fixes -- Updated sputnikvm dependency with bugfix in the `returndatacopy` implementation and a performance improvement in accessing EVM memory. [@birchmd] ([#826]) +- Updated [SputnikVM](https://github.com/aurora-is-near/sputnikvm) dependency with bugfix in the `returndatacopy` + implementation and a performance improvement in accessing EVM memory by [@birchmd]. ([#826]) ### Changes -- BREAKING: `engine-standalone-storage` no loonger automatically writes to the DB when `consume_message` is called. It is up to downstream users of the library to commit the diff (after doing any validation for correctness). [@birchmd] ([#825]) +- BREAKING: `engine-standalone-storage` no longer automatically writes to the DB when `consume_message` is called. + It is up to downstream users of the library to commit the diff (after doing any validation for correctness) by [@birchmd]. ([#825]) ### Additions -- New crate for the so-called "hashchain" implementation. It will enable verification of Aurora blocks by light clients in the future. [@birchmd] ([#816]) +- New crate for the so-called "hashchain" implementation. It will enable verification of Aurora blocks by light clients + in the future by [@birchmd]. ([#816]) [#816]: https://github.com/aurora-is-near/aurora-engine/pull/816 [#825]: https://github.com/aurora-is-near/aurora-engine/pull/825 @@ -495,8 +526,10 @@ struct SubmitResult { ## [1.0.0] - 2021-05-12 -[Unreleased]: https://github.com/aurora-is-near/aurora-engine/compare/2.10.2...develop -[3.0.0]: https://github.com/aurora-is-near/aurora-engine/compare/2.10.2...3.0.0 +[Unreleased]: https://github.com/aurora-is-near/aurora-engine/compare/3.2.0...develop +[3.2.0]: https://github.com/aurora-is-near/aurora-engine/compare/3.1.0...3.2.0 +[3.1.0]: https://github.com/aurora-is-near/aurora-engine/compare/3.0.0...3.1.0 +[3.0.0]: https://github.com/aurora-is-near/aurora-engine/compare/3.0.0...2.10.2 [2.10.2]: https://github.com/aurora-is-near/aurora-engine/compare/2.10.1...2.10.2 [2.10.1]: https://github.com/aurora-is-near/aurora-engine/compare/2.10.0...2.10.1 [2.10.0]: https://github.com/aurora-is-near/aurora-engine/compare/2.9.3...2.10.0 diff --git a/Cargo.lock b/Cargo.lock index a7a2c4213..d927cb31c 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -288,7 +288,7 @@ checksum = "16e62a023e7c117e27523144c5d2459f4397fcc3cab0085af8e2224f643a0193" dependencies = [ "proc-macro2", "quote", - "syn 2.0.26", + "syn 2.0.28", ] [[package]] @@ -305,7 +305,7 @@ checksum = "cc6dde6e4ed435a4c1ee4e73592f5ba9da2151af10076cc04858746af9352d09" dependencies = [ "proc-macro2", "quote", - "syn 2.0.26", + "syn 2.0.28", ] [[package]] @@ -327,7 +327,7 @@ dependencies = [ [[package]] name = "aurora-engine" -version = "2.10.1" +version = "3.2.0" dependencies = [ "aurora-engine-hashchain", "aurora-engine-modexp", @@ -441,10 +441,12 @@ dependencies = [ "near-crypto 0.17.0", "near-primitives 0.17.0", "near-primitives-core 0.17.0", + "near-sdk", "near-vm-errors 0.17.0", "near-vm-logic 0.17.0", "near-vm-runner", "rand 0.8.5", + "reqwest", "rlp", "serde", "serde_json", @@ -454,6 +456,24 @@ dependencies = [ "walrus", ] +[[package]] +name = "aurora-engine-tests-connector" +version = "1.0.0" +dependencies = [ + "anyhow", + "aurora-engine", + "aurora-engine-types", + "byte-slice-cast", + "ethabi", + "hex 0.4.3", + "near-sdk", + "near-units", + "rlp", + "serde", + "tokio", + "workspaces", +] + [[package]] name = "aurora-engine-transactions" version = "1.0.0" @@ -578,9 +598,9 @@ dependencies = [ [[package]] name = "bindgen" -version = "0.64.0" +version = "0.65.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c4243e6031260db77ede97ad86c27e501d646a27ab57b59a574f725d98ab1fb4" +checksum = "cfdf7b466f9a4903edc73f95d6d2bcd5baf8ae620638762244d3f60143643cc5" dependencies = [ "bitflags 1.3.2", "cexpr", @@ -588,12 +608,13 @@ dependencies = [ "lazy_static", "lazycell", "peeking_take_while", + "prettyplease", "proc-macro2", "quote", "regex", "rustc-hash", "shlex", - "syn 1.0.109", + "syn 2.0.28", ] [[package]] @@ -800,7 +821,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6798148dccfbff0fae41c7574d2fa8f1ef3492fba0face179de5d8d447d67b05" dependencies = [ "memchr", - "regex-automata 0.3.3", + "regex-automata 0.3.4", "serde", ] @@ -898,11 +919,12 @@ checksum = "37b2a672a2cb129a2e41c10b1224bb368f9f37a2b16b612598138befd7b37eb5" [[package]] name = "cc" -version = "1.0.79" +version = "1.0.80" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "50d30906286121d95be3d479533b458f87493b30a4b5f79a607db8f5d11aa91f" +checksum = "51f1226cd9da55587234753d1245dd5b132343ea240f26b6a9003d68706141ba" dependencies = [ "jobserver", + "libc", ] [[package]] @@ -991,9 +1013,9 @@ dependencies = [ [[package]] name = "clap" -version = "4.3.12" +version = "4.3.19" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3eab9e8ceb9afdade1ab3f0fd8dbce5b1b2f468ad653baf10e771781b2b67b73" +checksum = "5fd304a20bff958a57f04c4e96a2e7594cc4490a0e809cbd48bb6437edaa452d" dependencies = [ "clap_builder", "clap_derive", @@ -1002,9 +1024,9 @@ dependencies = [ [[package]] name = "clap_builder" -version = "4.3.12" +version = "4.3.19" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9f2763db829349bf00cfc06251268865ed4363b93a943174f638daf3ecdba2cd" +checksum = "01c6a3f08f1fe5662a35cfe393aec09c4df95f60ee93b7556505260f75eee9e1" dependencies = [ "anstream", "anstyle", @@ -1021,7 +1043,7 @@ dependencies = [ "heck 0.4.1", "proc-macro2", "quote", - "syn 2.0.26", + "syn 2.0.28", ] [[package]] @@ -1069,9 +1091,9 @@ dependencies = [ [[package]] name = "core-foundation-sys" -version = "0.8.5" +version = "0.8.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a385f5d34e5eff161df2369056a3fd194fcabd8a64ce0eed02de09fcb3203434" +checksum = "e496a50fda8aacccc86d7529e2c1e0892dbd0f898a6b5645b5561b89c3210efa" [[package]] name = "cpp_demangle" @@ -1335,9 +1357,9 @@ dependencies = [ [[package]] name = "curl-sys" -version = "0.4.63+curl-8.1.2" +version = "0.4.65+curl-8.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "aeb0fef7046022a1e2ad67a004978f0e3cacb9e3123dc62ce768f92197b771dc" +checksum = "961ba061c9ef2fe34bbd12b807152d96f0badd2bebe7b90ce6c8c8b7572a0986" dependencies = [ "cc", "libc", @@ -1382,7 +1404,7 @@ dependencies = [ "proc-macro2", "quote", "strsim", - "syn 2.0.26", + "syn 2.0.28", ] [[package]] @@ -1393,7 +1415,16 @@ checksum = "836a9bbc7ad63342d6d6e7b815ccab164bc77a2d95d84bc3117a8c0d5c98e2d5" dependencies = [ "darling_core", "quote", - "syn 2.0.26", + "syn 2.0.28", +] + +[[package]] +name = "deranged" +version = "0.3.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8810e7e2cf385b1e9b50d68264908ec367ba642c96d02edfe61c39e88e2a3c01" +dependencies = [ + "serde", ] [[package]] @@ -1404,7 +1435,7 @@ checksum = "53e0efad4403bfc52dc201159c4b842a246a14b98c64b55dfd0f2d89729dfeb8" dependencies = [ "proc-macro2", "quote", - "syn 2.0.26", + "syn 2.0.28", ] [[package]] @@ -1587,22 +1618,22 @@ dependencies = [ [[package]] name = "enum-map" -version = "2.6.0" +version = "2.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "017b207acb4cc917f4c31758ed95c0bc63ddb0f358b22eb38f80a2b2a43f6b1f" +checksum = "9705d8de4776df900a4a0b2384f8b0ab42f775e93b083b42f8ce71bdc32a47e3" dependencies = [ "enum-map-derive", ] [[package]] name = "enum-map-derive" -version = "0.12.0" +version = "0.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8560b409800a72d2d7860f8e5f4e0b0bd22bea6a352ea2a9ce30ccdef7f16d2f" +checksum = "ccb14d927583dd5c2eac0f2cf264fc4762aefe1ae14c47a8a20fc1939d3a5fc0" dependencies = [ "proc-macro2", "quote", - "syn 2.0.26", + "syn 2.0.28", ] [[package]] @@ -1623,7 +1654,7 @@ dependencies = [ "darling", "proc-macro2", "quote", - "syn 2.0.26", + "syn 2.0.28", ] [[package]] @@ -1640,9 +1671,9 @@ checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5" [[package]] name = "errno" -version = "0.3.1" +version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4bcfec3a70f97c962c307b2d2c56e358cf1d00b558d74262b5f929ee8cc7e73a" +checksum = "6b30f669a7961ef1631673d2766cc92f52d64f7ef354d4fe0ddfd30ed52f0f4f" dependencies = [ "errno-dragonfly", "libc", @@ -2033,7 +2064,7 @@ checksum = "89ca545a94061b6365f2c7355b4b32bd20df3ff95f02da9329b34ccc3bd6ee72" dependencies = [ "proc-macro2", "quote", - "syn 2.0.26", + "syn 2.0.28", ] [[package]] @@ -2656,9 +2687,9 @@ dependencies = [ [[package]] name = "librocksdb-sys" -version = "0.8.3+7.4.4" +version = "0.11.0+8.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "557b255ff04123fcc176162f56ed0c9cd42d8f357cf55b3fabeb60f7413741b3" +checksum = "d3386f101bcb4bd252d8e9d2fb41ec3b0862a15a62b478c355b2982efa469e3e" dependencies = [ "bindgen", "bzip2-sys", @@ -2666,6 +2697,7 @@ dependencies = [ "glob", "libc", "libz-sys", + "lz4-sys", "zstd-sys", ] @@ -2733,9 +2765,9 @@ dependencies = [ [[package]] name = "libz-sys" -version = "1.1.10" +version = "1.1.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "24e6ab01971eb092ffe6a7d42f49f9ff42662f17604681e2843ad65077ba47dc" +checksum = "d97137b25e321a73eef1418d1d5d2eda4d77e12813f8e6dead84bc52c5870a7b" dependencies = [ "cc", "libc", @@ -2757,9 +2789,9 @@ checksum = "ef53942eb7bf7ff43a617b3e2c1c4a5ecf5944a7c1bc12d7ee39bbb15e5c1519" [[package]] name = "linux-raw-sys" -version = "0.4.3" +version = "0.4.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "09fc20d2ca12cb9f044c93e3bd6d32d523e6e2ec3db4f7b2939cd99026ecd3f0" +checksum = "57bcfdad1b858c2db7c38303a6d2ad4dfaf5eb53dfeb0910128b2c26d6158503" [[package]] name = "lock_api" @@ -2806,6 +2838,16 @@ dependencies = [ "hashbrown 0.12.3", ] +[[package]] +name = "lz4-sys" +version = "1.9.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "57d27b317e207b10f69f5e75494119e391a96f48861ae870d1da6edac98ca900" +dependencies = [ + "cc", + "libc", +] + [[package]] name = "mach" version = "0.3.2" @@ -3265,7 +3307,7 @@ dependencies = [ "smart-default", "strum 0.24.1", "thiserror", - "time 0.3.23", + "time 0.3.24", "tracing", ] @@ -3356,7 +3398,7 @@ checksum = "84c1eda300e2e78f4f945ae58117d49e806899f4a51ee2faa09eda5ebc2e6571" dependencies = [ "quote", "serde", - "syn 2.0.26", + "syn 2.0.28", ] [[package]] @@ -3390,7 +3432,7 @@ dependencies = [ "fs2", "near-rpc-error-core 0.17.0", "serde", - "syn 2.0.26", + "syn 2.0.28", ] [[package]] @@ -3812,7 +3854,7 @@ checksum = "a948666b637a0f465e8564c73e89d4dde00d72d4d473cc972f390fc3dcee7d9c" dependencies = [ "proc-macro2", "quote", - "syn 2.0.26", + "syn 2.0.28", ] [[package]] @@ -4055,7 +4097,7 @@ checksum = "ec2e072ecce94ec471b13398d5402c188e76ac03cf74dd1a975161b23a3f6d9c" dependencies = [ "proc-macro2", "quote", - "syn 2.0.26", + "syn 2.0.28", ] [[package]] @@ -4184,6 +4226,16 @@ version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "aa06bd51638b6e76ac9ba9b6afb4164fa647bd2916d722f2623fbb6d1ed8bdba" +[[package]] +name = "prettyplease" +version = "0.2.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6c64d9ba0963cdcea2e1b2230fbae2bab30eb25a174be395c41e764bfb65dd62" +dependencies = [ + "proc-macro2", + "syn 2.0.28", +] + [[package]] name = "primitive-types" version = "0.10.1" @@ -4377,9 +4429,9 @@ dependencies = [ [[package]] name = "quote" -version = "1.0.31" +version = "1.0.32" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5fe8a65d69dd0808184ebb5f836ab526bb259db23c657efa38711b1072ee47f0" +checksum = "50f3b39ccfb720540debaa0164757101c08ecb8d326b15358ce76a62c7e85965" dependencies = [ "proc-macro2", ] @@ -4564,7 +4616,7 @@ checksum = "b2eae68fc220f7cf2532e4494aded17545fce192d59cd996e0fe7887f4ceb575" dependencies = [ "aho-corasick", "memchr", - "regex-automata 0.3.3", + "regex-automata 0.3.4", "regex-syntax 0.7.4", ] @@ -4579,9 +4631,9 @@ dependencies = [ [[package]] name = "regex-automata" -version = "0.3.3" +version = "0.3.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "39354c10dd07468c2e73926b23bb9c2caca74c5501e38a35da70406f1d923310" +checksum = "b7b6d6190b7594385f61bd3911cd1be99dfddcfc365a4160cc2ab5bff4aed294" dependencies = [ "aho-corasick", "memchr", @@ -4719,9 +4771,9 @@ dependencies = [ [[package]] name = "rocksdb" -version = "0.19.0" +version = "0.21.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7e9562ea1d70c0cc63a34a22d977753b50cca91cc6b6527750463bd5dd8697bc" +checksum = "bb6f170a4041d50a0ce04b0d2e14916d6ca863ea2e422689a5b694395d299ffe" dependencies = [ "libc", "librocksdb-sys", @@ -4803,7 +4855,7 @@ dependencies = [ "bitflags 2.3.3", "errno", "libc", - "linux-raw-sys 0.4.3", + "linux-raw-sys 0.4.5", "windows-sys 0.48.0", ] @@ -4967,22 +5019,22 @@ checksum = "b0293b4b29daaf487284529cc2f5675b8e57c61f70167ba415a463651fd6a918" [[package]] name = "serde" -version = "1.0.175" +version = "1.0.180" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5d25439cd7397d044e2748a6fe2432b5e85db703d6d097bd014b3c0ad1ebff0b" +checksum = "0ea67f183f058fe88a4e3ec6e2788e003840893b91bac4559cabedd00863b3ed" dependencies = [ "serde_derive", ] [[package]] name = "serde_derive" -version = "1.0.175" +version = "1.0.180" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b23f7ade6f110613c0d63858ddb8b94c1041f550eab58a16b371bdf2c9c80ab4" +checksum = "24e744d7782b686ab3b73267ef05697159cc0e5abbed3f47f9933165e5219036" dependencies = [ "proc-macro2", "quote", - "syn 2.0.26", + "syn 2.0.28", ] [[package]] @@ -4998,9 +5050,9 @@ dependencies = [ [[package]] name = "serde_json" -version = "1.0.103" +version = "1.0.104" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d03b412469450d4404fe8499a268edd7f8b79fecb074b0d812ad64ca21f4031b" +checksum = "076066c5f1078eac5b722a31827a8832fe108bed65dfa75e233c89f8206e976c" dependencies = [ "itoa", "ryu", @@ -5009,13 +5061,13 @@ dependencies = [ [[package]] name = "serde_repr" -version = "0.1.15" +version = "0.1.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e168eaaf71e8f9bd6037feb05190485708e019f4fd87d161b3c0a0d37daf85e5" +checksum = "8725e1dfadb3a50f7e5ce0b1a540466f6ed3fe7a0fca2ac2b8b831d31316bd00" dependencies = [ "proc-macro2", "quote", - "syn 2.0.26", + "syn 2.0.28", ] [[package]] @@ -5043,7 +5095,7 @@ dependencies = [ "serde", "serde_json", "serde_with_macros", - "time 0.3.23", + "time 0.3.24", ] [[package]] @@ -5055,14 +5107,14 @@ dependencies = [ "darling", "proc-macro2", "quote", - "syn 2.0.26", + "syn 2.0.28", ] [[package]] name = "serde_yaml" -version = "0.9.23" +version = "0.9.25" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "da6075b41c7e3b079e5f246eb6094a44850d3a4c25a67c581c80796c80134012" +checksum = "1a49e178e4452f45cb61d0cd8cebc1b0fafd3e41929e996cef79aa3aca91f574" dependencies = [ "indexmap 2.0.0", "itoa", @@ -5122,9 +5174,9 @@ checksum = "43b2853a4d09f215c24cc5489c992ce46052d359b5109343cbafbf26bc62f8a3" [[package]] name = "signal-hook" -version = "0.3.16" +version = "0.3.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b824b6e687aff278cdbf3b36f07aa52d4bd4099699324d5da86a2ebce3aa00b3" +checksum = "8621587d4798caf8eb44879d42e56b9a93ea5dcd315a6487c357130095b62801" dependencies = [ "libc", "signal-hook-registry", @@ -5290,7 +5342,7 @@ dependencies = [ "proc-macro2", "quote", "rustversion", - "syn 2.0.26", + "syn 2.0.28", ] [[package]] @@ -5312,9 +5364,9 @@ dependencies = [ [[package]] name = "syn" -version = "2.0.26" +version = "2.0.28" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "45c3457aacde3c65315de5031ec191ce46604304d2446e803d71ade03308d970" +checksum = "04361975b3f5e348b2189d8dc55bc942f278b2d482a6a0365de5bdd62d351567" dependencies = [ "proc-macro2", "quote", @@ -5352,9 +5404,9 @@ dependencies = [ [[package]] name = "target-lexicon" -version = "0.12.10" +version = "0.12.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1d2faeef5759ab89935255b1a4cd98e0baf99d1085e37d36599c625dac49ae8e" +checksum = "9d0e916b1148c8e263850e1ebcbd046f333e0683c724876bb0da63ea4373dc8a" [[package]] name = "tempfile" @@ -5421,7 +5473,7 @@ checksum = "090198534930841fab3a5d1bb637cde49e339654e606195f8d9c76eeb081dc96" dependencies = [ "proc-macro2", "quote", - "syn 2.0.26", + "syn 2.0.28", ] [[package]] @@ -5447,10 +5499,11 @@ dependencies = [ [[package]] name = "time" -version = "0.3.23" +version = "0.3.24" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "59e399c068f43a5d116fedaf73b203fa4f9c519f17e2b34f63221d3792f81446" +checksum = "b79eabcd964882a646b3584543ccabeae7869e9ac32a46f6f22b7a5bd405308b" dependencies = [ + "deranged", "itoa", "serde", "time-core", @@ -5465,9 +5518,9 @@ checksum = "7300fbefb4dadc1af235a9cef3737cea692a9d97e1b9cbcd4ebdae6f8868e6fb" [[package]] name = "time-macros" -version = "0.2.10" +version = "0.2.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "96ba15a897f3c86766b757e5ac7221554c6750054d74d5b28844fce5fb36a6c4" +checksum = "eb71511c991639bb078fd5bf97757e03914361c48100d52878b8e52b46fb92cd" dependencies = [ "time-core", ] @@ -5543,7 +5596,7 @@ checksum = "630bdcf245f78637c13ec01ffae6187cca34625e8c63150d424b59e55af2675e" dependencies = [ "proc-macro2", "quote", - "syn 2.0.26", + "syn 2.0.28", ] [[package]] @@ -5751,7 +5804,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "09d48f71a791638519505cefafe162606f706c25592e4bde4d97600c0195312e" dependencies = [ "crossbeam-channel", - "time 0.3.23", + "time 0.3.24", "tracing-subscriber", ] @@ -5763,7 +5816,7 @@ checksum = "5f4f31f56159e98206da9efd823404b79b6ef3143b4a7ab76e67b1751b25a4ab" dependencies = [ "proc-macro2", "quote", - "syn 2.0.26", + "syn 2.0.28", ] [[package]] @@ -6039,7 +6092,7 @@ dependencies = [ "once_cell", "proc-macro2", "quote", - "syn 2.0.26", + "syn 2.0.28", "wasm-bindgen-shared", ] @@ -6073,7 +6126,7 @@ checksum = "54681b18a46765f095758388f2d0cf16eb8d4169b639ab575a8f5693af210c7b" dependencies = [ "proc-macro2", "quote", - "syn 2.0.26", + "syn 2.0.28", "wasm-bindgen-backend", "wasm-bindgen-shared", ] @@ -6624,9 +6677,9 @@ checksum = "1a515f5799fe4961cb532f983ce2b23082366b898e52ffbce459c86f67c8378a" [[package]] name = "winnow" -version = "0.5.0" +version = "0.5.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "81fac9742fd1ad1bd9643b991319f72dd031016d44b77039a26977eb667141e7" +checksum = "f46aab759304e4d7b2075a9aecba26228bb073ee8c50db796b2c72c676b5d807" dependencies = [ "memchr", ] @@ -6714,7 +6767,7 @@ checksum = "ce36e65b0d2999d2aafac989fb249189a141aee1f53c612c1f37d72631959f69" dependencies = [ "proc-macro2", "quote", - "syn 2.0.26", + "syn 2.0.28", ] [[package]] diff --git a/Cargo.toml b/Cargo.toml index b315b9b34..9547aaac0 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -41,7 +41,7 @@ function_name = "0.3.0" git2 = "0.18" hex = { version = "0.4", default-features = false, features = ["alloc"] } ibig = { version = "0.3", default-features = false, features = ["num-traits"] } -impl-serde = { version = "0.4.0", default-features = false} +impl-serde = { version = "0.4", default-features = false} lazy_static = "1" libsecp256k1 = { version = "0.7", default-features = false } near-crypto = "0.17" @@ -56,9 +56,10 @@ num = { version = "0.4", default-features = false, features = ["alloc"] } postgres = "0.19" primitive-types = { version = "0.12", default-features = false, features = ["rlp", "serde_no_std"] } rand = "0.8" +reqwest = "0.11" ripemd = { version = "0.1", default-features = false } rlp = { version = "0.5", default-features = false } -rocksdb = { version = "0.19", default-features = false } +rocksdb = { version = "0.21", default-features = false } serde = { version = "1", default-features = false, features = ["alloc", "derive"] } serde_json = { version = "1", default-features = false, features = ["alloc"] } sha2 = { version = "0.10", default-features = false } @@ -83,6 +84,7 @@ members = [ "engine-standalone-storage", "engine-standalone-tracing", "engine-tests", + "engine-tests-connector", "engine-transactions", "engine-types", "engine-workspace", diff --git a/Makefile.toml b/Makefile.toml index a125a1ee6..367ba8263 100644 --- a/Makefile.toml +++ b/Makefile.toml @@ -1,6 +1,8 @@ env_files = [ { path = ".env/mainnet.env", profile = "mainnet" }, + { path = ".env/mainnet-silo.env", profile = "mainnet-silo" }, { path = ".env/testnet.env", profile = "testnet" }, + { path = ".env/testnet-silo.env", profile = "testnet-silo" }, { path = ".env/local.env", profile = "local" }, { path = ".env/custom.env", profile = "custom" }, { path = ".env/local.env", profile = "development" }, @@ -70,14 +72,14 @@ args = [ [tasks.udeps] category = "Check" env = { "CARGO_MAKE_RUST_CHANNEL" = "nightly", "CARGO_MAKE_CRATE_INSTALLATION_LOCKED" = "true" } -install_crate = { crate_name = "cargo-udeps", binary = "cargo", min_version = "0.1.34", test_arg = ["udeps", "-h"], force = true } +install_crate = { crate_name = "cargo-udeps", binary = "cargo", min_version = "0.1.41", test_arg = ["udeps", "-h"], force = true } command = "${CARGO}" args = [ "udeps", "--all-targets", ] -[tasks.clippy] +[tasks.clippy-base] category = "Check" command = "${CARGO}" args = [ @@ -90,9 +92,22 @@ args = [ "clippy::as_conversions", ] +[tasks.clippy-ext-connector] +extend = "clippy-base" +args = [ + "clippy", + "--all-targets", + "--features", + "ext-connector", + "--", + "-D", + "warnings", + "-D", + "clippy::as_conversions", +] + [tasks.clippy-contract] -category = "Check" -command = "${CARGO}" +extend = "clippy-base" args = [ "clippy", "--all-targets", @@ -106,8 +121,7 @@ args = [ ] [tasks.clippy-contract-refund] -category = "Check" -command = "${CARGO}" +extend = "clippy-base" args = [ "clippy", "--all-targets", @@ -121,8 +135,7 @@ args = [ ] [tasks.clippy-borsh-compat] -category = "Check" -command = "${CARGO}" +extend = "clippy-base" args = [ "clippy", "-p", @@ -137,6 +150,15 @@ args = [ "clippy::as_conversions", ] +[tasks.clippy] +dependencies = [ + "clippy-base", + "clippy-ext-connector", + "clippy-contract", + "clippy-contract-refund", + "clippy-borsh-compat", +] + [tasks.check-fmt] category = "Check" command = "${CARGO}" @@ -160,9 +182,6 @@ dependencies = [ "check-contracts", "check-fmt", "clippy", - "clippy-contract", - "clippy-contract-refund", - "clippy-borsh-compat", "udeps", ] @@ -174,7 +193,7 @@ echo " CARGO_MAKE_PROFILE: ${CARGO_MAKE_PROFILE}" echo " IS_PROD: ${IS_PROD}" echo " CARGO_FEATURES: ${CARGO_FEATURES}" echo " WASM_FILE: ${WASM_FILE}" -echo " SIZE_WASM_FILE: $(du -h bin/${WASM_FILE} | cut -f1)" +echo " SIZE_WASM_FILE: $(wc -c bin/${WASM_FILE} | awk '{print $1}')" echo " TARGET_DIR: ${TARGET_DIR}" echo " RUSTFLAGS: ${RUSTFLAGS}" echo " Extra build args: ${RELEASE} ${@}" @@ -188,7 +207,7 @@ echo " CARGO_MAKE_PROFILE: ${CARGO_MAKE_PROFILE}" echo " IS_PROD: ${IS_PROD}" echo " CARGO_FEATURES: ${CARGO_FEATURES}" echo " WASM_FILE: ${XCC_ROUTER_WASM_FILE}" -echo " SIZE_WASM_FILE: $(du -h bin/${XCC_ROUTER_WASM_FILE} | cut -f1)" +echo " SIZE_WASM_FILE: $(wc -c bin/${XCC_ROUTER_WASM_FILE} | awk '{print $1}')" echo " TARGET_DIR: ${TARGET_DIR}" echo " RUSTFLAGS: ${RUSTFLAGS}" echo " Extra build args: ${RELEASE} ${@}" @@ -280,19 +299,19 @@ dependencies = [ ] [tasks.build-test] -condition = { profiles = ["mainnet", "testnet", "custom"] } +condition = { profiles = ["mainnet", "mainnet-silo", "testnet", "testnet-silo", "custom"] } env = { "RUSTFLAGS" = "${RUSTC_FLAGS_BUILD}", "CARGO_FEATURES" = "${CARGO_FEATURES_BUILD_TEST}", "WASM_FILE" = "${WASM_FILE_TEST}", "RELEASE" = "--release", "TARGET_DIR" = "release" } category = "Build" run_task = "build-engine-flow" [tasks.build] -condition = { profiles = ["mainnet", "testnet", "localnet", "development", "custom"] } +condition = { profiles = ["mainnet", "mainnet-silo", "testnet", "testnet-silo", "localnet", "development", "custom"] } env = { "RUSTFLAGS" = "-C strip=symbols --remap-path-prefix ${HOME}=/path/to/home/ --remap-path-prefix ${PWD}=/path/to/source/", "CARGO_FEATURES" = "${CARGO_FEATURES_BUILD}", "RELEASE" = "--release", "TARGET_DIR" = "release" } category = "Build" run_task = "build-engine-flow" [tasks.build-docker-inner] -condition = { profiles = ["mainnet", "testnet", "localnet", "development", "custom"] } +condition = { profiles = ["mainnet", "mainnet-silo", "testnet", "testnet-silo", "localnet", "development", "custom"] } env = { "RUSTFLAGS" = "-C strip=symbols --remap-path-prefix ${HOME}=/path/to/home/ --remap-path-prefix ${PWD}=/path/to/source/", "CARGO_FEATURES" = "${CARGO_FEATURES_BUILD}", "RELEASE" = "--release", "TARGET_DIR" = "release" } category = "Build" run_task = "build-engine-flow-docker" @@ -300,7 +319,7 @@ run_task = "build-engine-flow-docker" [tasks.build-docker] category = "Build" script = ''' -docker run --volume $PWD:/host -w /host -i --rm nearprotocol/contract-builder:master-f033430c4fc619aaf8b88eb2fcb976bb8d24d65d-amd64 ./scripts/docker-entrypoint.sh ${PROFILE} +docker run --volume $PWD:/host -w /host -i --rm nearprotocol/contract-builder:master-15bfb0e6d54ff386478d137074027c2cb863df03-amd64 ./scripts/docker-entrypoint.sh ${PROFILE} ''' [tasks.build-xcc-router-docker-inner] @@ -310,13 +329,15 @@ category = "Build" run_task = "build-xcc-router-flow-docker" [tasks.build-xcc-docker] +condition = { profiles = ["mainnet", "testnet"] } category = "Build" script = ''' -docker run --volume $PWD:/host -w /host -i --rm nearprotocol/contract-builder:master-f033430c4fc619aaf8b88eb2fcb976bb8d24d65d-amd64 ./scripts/docker-xcc-router-entrypoint.sh ${PROFILE} +docker run --volume $PWD:/host -w /host -i --rm nearprotocol/contract-builder:master-15bfb0e6d54ff386478d137074027c2cb863df03-amd64 ./scripts/docker-xcc-router-entrypoint.sh ${PROFILE} ''' [tasks.test-contracts] category = "Test" +dependencies = ["build-contracts"] script = ''' cd etc/eth-contracts yarn @@ -324,11 +345,27 @@ yarn test ''' [tasks.test-workspace] -condition = { profiles = ["mainnet", "testnet", "custom"] } +condition = { profiles = ["mainnet", "mainnet-silo", "testnet", "testnet-silo", "custom"] } +category = "Test" +command = "${CARGO}" +dependencies = ["build-test", "build-aurora-eth-connector"] +args = [ + "test", + "--workspace", + "--exclude", + "aurora-engine-tests-connector", + "--features", + "${CARGO_FEATURES_TEST}", +] + +[tasks.test-connector] +condition = { profiles = ["mainnet-silo", "testnet-silo"] } category = "Test" command = "${CARGO}" args = [ "test", + "-p", + "aurora-engine-tests-connector", "--features", "${CARGO_FEATURES_TEST}", ] @@ -347,12 +384,34 @@ args = [ "--ignored", ] +[tasks.build-aurora-eth-connector] +category = "Build" +condition = { profiles = ["mainnet-silo", "testnet-silo"] } +script = ''' +AURORA_ETH_CONNECTOR_DIR="engine-tests-connector/etc/aurora-eth-connector/" +AURORA_ETH_CONNECTOR_ETC="engine-tests-connector/etc/" +AURORA_ETH_CONNECTOR_REPO_NAME="aurora-eth-connector" +AURORA_ETH_CONNECTOR_REPO="https://github.com/aurora-is-near/"$AURORA_ETH_CONNECTOR_REPO_NAME +if [ -d $AURORA_ETH_CONNECTOR_DIR ]; then + cd $AURORA_ETH_CONNECTOR_DIR + echo "Pull Aurora Eth-Connector repo" + git pull origin master +else + mkdir $AURORA_ETH_CONNECTOR_ETC || true + cd $AURORA_ETH_CONNECTOR_ETC + echo "Clone Aurora Eth-Connector repo: "$AURORA_ETH_CONNECTOR_REPO + git clone --depth 1 $AURORA_ETH_CONNECTOR_REPO + cd $AURORA_ETH_CONNECTOR_REPO_NAME +fi +cargo make --profile mainnet build-test +''' + [tasks.test-flow] category = "Test" dependencies = [ - "build-test", "test-contracts", "test-workspace", + "test-connector", ] [tasks.bench-modexp] @@ -370,10 +429,10 @@ dependencies = [ ] [tasks.default] -condition = { profiles = ["mainnet", "testnet", "localnet", "development", "custom"] } +condition = { profiles = ["mainnet", "mainnet-silo", "testnet", "testnet-silo", "localnet", "development", "custom"] } run_task = "build" [tasks.deploy] -condition = { profiles = ["mainnet", "testnet", "localnet", "development", "custom"] } +condition = { profiles = ["mainnet", "mainnet-silo", "testnet", "testnet-silo", "localnet", "development", "custom"] } category = "Deploy" script = "${NEAR_CLI} deploy --acount-id${NEAR_EVM_ACCOUNT} --wasm-file=bin/${WASM_FILE}" diff --git a/README.md b/README.md index 44d271120..d95093c52 100644 --- a/README.md +++ b/README.md @@ -43,7 +43,7 @@ cargo install --force cargo-make ### Branches - [`master`] is the current stable branch. - It must be ready, anytime, to deployed on chain at a moment's notice. + It must be ready, at all times, to be deployed on chain at a moment's notice. - [`develop`] is our bleeding-edge development branch. In general, kindly target all pull requests to this branch. @@ -53,35 +53,38 @@ cargo install --force cargo-make Every task with `cargo make` must have a `--profile` argument. The current available `profile`s are: -- `mainnet`, suitable for mainnet. -- `testnet`, suitable for testnet. -- `local`, suitable for local development. -- `custom`, suitable for custom environments, see note below. - -A custom environment may be required depending on the circumstances. This can +- `mainnet`: suitable for mainnet. +- `mainnet-silo`: silo contract suitable for mainnet. +- `testnet`: suitable for testnet. +- `testnet-silo`: silo contract suitable for testnet. +- `local`: suitable for local development. +- `custom`: suitable for custom environments, see note below. + +In some circumstances, you may require a custom environment. This can be created in the `.env` folder as `custom.env` following the structure of the other `.env` files. See `bin/local-custom.env` for more details. -Every make most follow the following pattern, though `--profile` is not required -for all such as cleanup: +Every `make` invocation must follow the following pattern, though `--profile` is +not required in all cases (such as cleanup): + ```sh cargo make [--profile ] ``` #### Building the engine and contracts -To build the binaries there are a few commands to do such following the format. +There are several commands that can be used to build the binaries. The currently supported parameters +for the `task` field are listed below: -The current available build `task`s are: -- `default`, does not need to be specified, runs `build`. Requires a `--profile` +- `default`: does not need to be specified, runs `build`. Requires a `--profile` argument. -- `build`, builds all engine smart contract and produces the +- `build`: builds all engine smart contract and produces the `aurora--test.wasm` in the `bin` folder. Requires `build-contracts`. Requires a `--profile` argument. -- `build-test`, builds all the below using test features. Requires a `--profile` +- `build-test`: builds all the below using test features. Requires a `--profile` argument. -- `build-contracts`, builds all the ETH contracts. -- `build-docker`, builds the `aurora--test.wasm` in the `bin` folder using docker build environment. The purpose of this task is to produce reproducible binaries. +- `build-contracts`: builds all the ETH contracts. +- `build-docker`: builds the `aurora--test.wasm` in the `bin` folder using docker build environment. The purpose of this task is to produce reproducible binaries. For example, the following will build the mainnet debug binary: ```sh @@ -90,7 +93,7 @@ cargo make --profile mainnet build #### Verifying binary hash -To verify that a deployed binary matches the source code, you may want build it reproducibly and then check that their hashes match. The motivation behind that is to prevent malicious code from being deployed. +To verify that a deployed binary matches the source code, you may want build it reproducibly and then verify that the SHA256 hash matches that of the deployed binary. The motivation behind this is to prevent malicious code from being deployed. Run these commands to produce the binary hash: ```sh @@ -101,10 +104,12 @@ shasum -a 256 bin/aurora-.wasm #### Running unit & integration tests To run tests, there are a few cargo make tasks we can run: -- `test`, tests the whole cargo workspace and ETH contracts. Requires a - `--profile` argument. -- `test-workspace`, tests only the cargo workspace. -- `test-contracts`, tests only the contracts. +- `test`: tests the whole cargo workspace and ETH contracts. Requires a `--profile` argument. +- `test-workspace`: tests only the cargo workspace. +- `test-contracts`: tests only the contracts. +- `test`: tests the whole cargo workspace, ETH contracts and runs modexp benchmarks. Requires a `--profile` argument. +- `test-flow`: tests the whole cargo workspace and ETH contracts. Requires a `--profile` argument. +- `bench-modexp`: runs modexp benchmarks. Requires a `--profile` argument. For example, the following will test the whole workspace and ETH contracts: ```sh @@ -113,13 +118,14 @@ cargo make --profile mainnet test #### Running checks & lints -To run lints and checks, the following tasks are available: -- `check`, checks the format, clippy and ETH contracts. -- `check-contracts`, runs yarn lints on the ETH contracts. -- `check-fmt`, checks the workspace Rust format only. -- `check-clippy`, checks the Rust workspace with clippy only. +The following tasks are available to run lints and checks: + +- `check`: checks the format, clippy and ETH contracts. +- `check-contracts` runs yarn lints on the ETH contracts. +- `check-fmt`: checks the workspace Rust format only. +- `clippy`: checks the Rust workspace with clippy only. -For example the following command will run the checks. `profile` is not required +For example, the following command will run the checks. `profile` is not required here: ``` cargo make check @@ -127,14 +133,16 @@ cargo make check #### Cleanup -To clean up the workspace, the following tasks are available: -- `clean`, cleans all built binaries and ETH contracts. -- `clean-cargo`, cleans with cargo. -- `clean-contracts`, cleans the ETH contracts. -- `clean-bin`, cleans the binaries. +The following tasks are available to clean up the workspace: + +- `clean`: cleans all built binaries and ETH contracts. +- `clean-cargo`: cleans with cargo. +- `clean-contracts`: cleans the ETH contracts. +- `clean-bin`: cleans the binaries. Additionally, there is also but not included in the `clean` task: -- `sweep`, sweeps the set amount of days in the ENV, default at 30 days. + +- `sweep`: sweeps the set amount of days in the ENV, default at 30 days. For example, the following command will clean everything. `profile` is not required: @@ -147,5 +155,5 @@ cargo make clean ## License **aurora-engine** has multiple licenses: -* all crates except `engine-test` has **CCO-1.0** license +* All crates except `engine-test` has **CCO-1.0** license * `engine-test` has **GPL-v3** license diff --git a/VERSION b/VERSION index fd2a01863..944880fa1 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -3.1.0 +3.2.0 diff --git a/doc/eth-connector.md b/doc/eth-connector.md index c519c647f..8e0897a69 100644 --- a/doc/eth-connector.md +++ b/doc/eth-connector.md @@ -29,7 +29,6 @@ For more details see: [NEP-141](https://nomicon.io/Standards/Tokens/FungibleToke * ft_total_supply (view) * ft_total_eth_supply_on_near (view) -* ft_total_eth_supply_on_aurora (view) * ft_balance_of (view) * ft_balance_of_eth (view) * ft_transfer (mutable, payable) diff --git a/engine-precompiles/Cargo.toml b/engine-precompiles/Cargo.toml index 77f812527..fd01fd0eb 100644 --- a/engine-precompiles/Cargo.toml +++ b/engine-precompiles/Cargo.toml @@ -37,3 +37,4 @@ borsh-compat = ["aurora-engine-types/borsh-compat", "aurora-engine-sdk/borsh-com contract = [] log = [] error_refund = [] +ext-connector = [] diff --git a/engine-precompiles/src/lib.rs b/engine-precompiles/src/lib.rs index 325d8d5c7..9ade5549d 100644 --- a/engine-precompiles/src/lib.rs +++ b/engine-precompiles/src/lib.rs @@ -359,7 +359,10 @@ impl<'a, I: IO + Copy, E: Env, H: ReadOnlyPromiseHandler> Precompiles<'a, I, E, ctx: PrecompileConstructorContext<'a, I, E, H, M>, ) -> Self { let near_exit = ExitToNear::new(ctx.current_account_id.clone(), ctx.io); + #[cfg(not(feature = "ext-connector"))] let ethereum_exit = ExitToEthereum::new(ctx.current_account_id.clone(), ctx.io); + #[cfg(feature = "ext-connector")] + let ethereum_exit = ExitToEthereum::new(ctx.io); let cross_contract_call = CrossContractCall::new(ctx.current_account_id, ctx.io); let predecessor_account_id = PredecessorAccount::new(ctx.env); let prepaid_gas = PrepaidGas::new(ctx.env); diff --git a/engine-precompiles/src/native.rs b/engine-precompiles/src/native.rs index 8f08de6a2..dc3a677dd 100644 --- a/engine-precompiles/src/native.rs +++ b/engine-precompiles/src/native.rs @@ -1,21 +1,21 @@ use super::{EvmPrecompileResult, Precompile}; +use crate::prelude::types::EthGas; +use crate::prelude::{ + format, + parameters::{PromiseArgs, PromiseCreateArgs}, + sdk::io::{StorageIntermediate, IO}, + storage::{bytes_to_key, KeyPrefix}, + str, + types::{Address, Yocto}, + vec, BorshSerialize, Cow, ToString, Vec, U256, +}; #[cfg(feature = "error_refund")] use crate::prelude::{parameters::RefundCallArgs, types}; -use crate::{ - prelude::{ - format, - parameters::{PromiseArgs, PromiseCreateArgs, WithdrawCallArgs}, - sdk::io::{StorageIntermediate, IO}, - storage::{bytes_to_key, KeyPrefix}, - str, - types::{Address, Yocto}, - vec, BorshSerialize, Cow, String, ToString, Vec, U256, - }, - xcc::state::get_wnear_address, -}; - -use crate::prelude::types::EthGas; +use crate::xcc::state::get_wnear_address; use crate::PrecompileOutput; +use aurora_engine_types::parameters::connector::WithdrawSerializeType; +use aurora_engine_types::parameters::WithdrawCallArgs; +use aurora_engine_types::storage::EthConnectorStorageId; use aurora_engine_types::{ account_id::AccountId, parameters::{ @@ -76,7 +76,7 @@ pub mod events { /// uint amount /// ) /// Note: in the ERC-20 exit case `sender` == `erc20_address` because it is - /// the ERC-20 contract which calls the exit precompile. However in the case + /// the ERC-20 contract which calls the exit precompile. However, in the case /// of ETH exit the sender will give the true sender (and the `erc20_address` /// will not be meaningful because ETH is not an ERC-20 token). pub struct ExitToNear { @@ -241,6 +241,33 @@ fn get_nep141_from_erc20(erc20_token: &[u8], io: &I) -> Result(io: &I) -> Result { + io.read_storage(&construct_contract_key( + EthConnectorStorageId::EthConnectorAccount, + )) + .ok_or(ExitError::Other(Cow::Borrowed("ERR_KEY_NOT_FOUND"))) + .and_then(|x| { + x.to_value() + .map_err(|_| ExitError::Other(Cow::Borrowed("ERR_DESERIALIZE"))) + }) +} + +fn get_withdraw_serialize_type(io: &I) -> Result { + io.read_storage(&construct_contract_key( + EthConnectorStorageId::WithdrawSerializationType, + )) + .map_or(Ok(WithdrawSerializeType::Borsh), |value| { + value + .to_value() + .map_err(|_| ExitError::Other(Cow::Borrowed("ERR_DESERIALIZE"))) + }) +} + +fn construct_contract_key(suffix: EthConnectorStorageId) -> Vec { + bytes_to_key(KeyPrefix::EthConnector, &[u8::from(suffix)]) +} + fn validate_amount(amount: U256) -> Result<(), ExitError> { if amount > U256::from(u128::MAX) { return Err(ExitError::Other(Cow::from("ERR_INVALID_AMOUNT"))); @@ -327,18 +354,21 @@ impl Precompile for ExitToNear { let (refund_address, mut input) = parse_input(input)?; #[cfg(not(feature = "error_refund"))] let mut input = parse_input(input)?; - let current_account_id = self.current_account_id.clone(); + #[cfg(not(feature = "ext-connector"))] + let eth_connector_account_id = self.current_account_id.clone(); + #[cfg(feature = "ext-connector")] + let eth_connector_account_id = get_eth_connector_contract_account(&self.io)?; let (nep141_address, args, exit_event, method, transfer_near_args) = match flag { 0x0 => { // ETH transfer // // Input slice format: - // recipient_account_id (bytes) - the NEAR recipient account which will receive NEP-141 ETH tokens + // recipient_account_id (bytes) - the NEAR recipient account which will receive NEP-141 ETH tokens if let Ok(dest_account) = AccountId::try_from(input) { ( - current_account_id, + eth_connector_account_id, // There is no way to inject json, given the encoding of both arguments // as decimal and valid account id respectively. format!( @@ -362,7 +392,7 @@ impl Precompile for ExitToNear { } } 0x1 => { - // ERC20 transfer + // ERC-20 transfer // // This precompile branch is expected to be called from the ERC20 burn function. // @@ -491,8 +521,9 @@ impl Precompile for ExitToNear { } pub struct ExitToEthereum { - current_account_id: AccountId, io: I, + #[cfg(not(feature = "ext-connector"))] + current_account_id: AccountId, } pub mod exit_to_ethereum { @@ -506,12 +537,18 @@ pub mod exit_to_ethereum { } impl ExitToEthereum { + #[cfg(not(feature = "ext-connector"))] pub const fn new(current_account_id: AccountId, io: I) -> Self { Self { - current_account_id, io, + current_account_id, } } + + #[cfg(feature = "ext-connector")] + pub const fn new(io: I) -> Self { + Self { io } + } } impl Precompile for ExitToEthereum { @@ -519,6 +556,7 @@ impl Precompile for ExitToEthereum { Ok(costs::EXIT_TO_ETHEREUM_GAS) } + #[allow(clippy::too_many_lines)] fn run( &self, input: &[u8], @@ -553,6 +591,10 @@ impl Precompile for ExitToEthereum { let mut input = input; let flag = input[0]; input = &input[1..]; + #[cfg(not(feature = "ext-connector"))] + let eth_connector_account_id = self.current_account_id.clone(); + #[cfg(feature = "ext-connector")] + let eth_connector_account_id = get_eth_connector_contract_account(&self.io)?; let (nep141_address, serialized_args, exit_event) = match flag { 0x0 => { @@ -563,16 +605,15 @@ impl Precompile for ExitToEthereum { let recipient_address: Address = input .try_into() .map_err(|_| ExitError::Other(Cow::from("ERR_INVALID_RECIPIENT_ADDRESS")))?; + let serialize_fn = match get_withdraw_serialize_type(&self.io)? { + WithdrawSerializeType::Json => json_args, + WithdrawSerializeType::Borsh => borsh_args, + }; ( - self.current_account_id.clone(), + eth_connector_account_id, // There is no way to inject json, given the encoding of both arguments // as decimal and hexadecimal respectively. - WithdrawCallArgs { - recipient_address, - amount: NEP141Wei::new(context.apparent_value.as_u128()), - } - .try_to_vec() - .map_err(|_| ExitError::Other(Cow::from("ERR_INVALID_AMOUNT")))?, + serialize_fn(recipient_address, context.apparent_value)?, events::ExitToEth { sender: Address::new(context.caller), erc20_address: events::ETH_ADDRESS, @@ -607,7 +648,7 @@ impl Precompile for ExitToEthereum { if input.len() == 20 { // Parse ethereum address in hex - let eth_recipient: String = hex::encode(input); + let eth_recipient = hex::encode(input); // unwrap cannot fail since we checked the length already let recipient_address = Address::try_from_slice(input).map_err(|_| { ExitError::Other(crate::prelude::Cow::from("ERR_WRONG_ADDRESS")) @@ -670,6 +711,25 @@ impl Precompile for ExitToEthereum { } } +#[allow(clippy::unnecessary_wraps)] +fn json_args(address: Address, amount: U256) -> Result, ExitError> { + Ok(format!( + r#"{{"amount": "{}", "recipient": "{}"}}"#, + amount.as_u128(), + address.encode(), + ) + .into_bytes()) +} + +fn borsh_args(address: Address, amount: U256) -> Result, ExitError> { + WithdrawCallArgs { + recipient_address: address, + amount: NEP141Wei::new(amount.as_u128()), + } + .try_to_vec() + .map_err(|_| ExitError::Other(Cow::from("ERR_BORSH_SERIALIZE"))) +} + #[cfg(test)] mod tests { use super::{ diff --git a/engine-sdk/src/near_runtime.rs b/engine-sdk/src/near_runtime.rs index 3600a55b9..b9a9f3f38 100644 --- a/engine-sdk/src/near_runtime.rs +++ b/engine-sdk/src/near_runtime.rs @@ -1,5 +1,5 @@ use crate::io::StorageIntermediate; -use crate::prelude::NearGas; +use crate::prelude::{NearGas, Vec}; use crate::promise::PromiseId; use aurora_engine_types::account_id::AccountId; use aurora_engine_types::parameters::{PromiseAction, PromiseBatchAction, PromiseCreateArgs}; @@ -8,7 +8,7 @@ use aurora_engine_types::types::PromiseResult; use aurora_engine_types::H256; #[cfg(all(feature = "mainnet", not(feature = "testnet")))] -/// The mainnet eth_custodian address 0x6BFaD42cFC4EfC96f529D786D643Ff4A8B89FA52 +/// The mainnet `eth_custodian` address 0x6BFaD42cFC4EfC96f529D786D643Ff4A8B89FA52 const CUSTODIAN_ADDRESS: &[u8] = &[ 107, 250, 212, 44, 252, 78, 252, 150, 245, 41, 215, 134, 214, 67, 255, 74, 139, 137, 250, 82, ]; @@ -312,6 +312,16 @@ impl crate::promise::PromiseHandler for Runtime { PromiseId::new(id) } + unsafe fn promise_create_and_combine(&mut self, args: &[PromiseCreateArgs]) -> PromiseId { + let ids = args + .iter() + .map(|args| self.promise_create_call(args)) + .collect::>(); + let id = exports::promise_and(ids.as_ptr() as _, ids.len() as _); + + PromiseId::new(id) + } + unsafe fn promise_attach_callback( &mut self, base: PromiseId, @@ -399,16 +409,14 @@ impl crate::promise::PromiseHandler for Runtime { }); } PromiseAction::AddFullAccessKey { public_key, nonce } => { - feature_gated!("all-promise-actions", { - let pk: RawPublicKey = public_key.into(); - let pk_bytes = pk.as_bytes(); - exports::promise_batch_action_add_key_with_full_access( - id, - pk_bytes.len() as _, - pk_bytes.as_ptr() as _, - *nonce, - ); - }); + let pk: RawPublicKey = public_key.into(); + let pk_bytes = pk.as_bytes(); + exports::promise_batch_action_add_key_with_full_access( + id, + pk_bytes.len() as _, + pk_bytes.as_ptr() as _, + *nonce, + ); } PromiseAction::AddFunctionCallKey { public_key, @@ -648,7 +656,7 @@ pub(crate) mod exports { amount_ptr: u64, gas: u64, ) -> u64; - fn promise_and(promise_idx_ptr: u64, promise_idx_count: u64) -> u64; + pub(crate) fn promise_and(promise_idx_ptr: u64, promise_idx_count: u64) -> u64; pub(crate) fn promise_batch_create(account_id_len: u64, account_id_ptr: u64) -> u64; fn promise_batch_then(promise_index: u64, account_id_len: u64, account_id_ptr: u64) -> u64; // ####################### diff --git a/engine-sdk/src/promise.rs b/engine-sdk/src/promise.rs index d75612ce1..67a516348 100644 --- a/engine-sdk/src/promise.rs +++ b/engine-sdk/src/promise.rs @@ -32,6 +32,11 @@ pub trait PromiseHandler { /// arbitrary calls using the Engine. unsafe fn promise_create_call(&mut self, args: &PromiseCreateArgs) -> PromiseId; + /// Combine more than one promise into one. + /// # Safety + /// Safe because of use `promise_create_call` function under the hood. + unsafe fn promise_create_and_combine(&mut self, args: &[PromiseCreateArgs]) -> PromiseId; + /// # Safety /// See note on `promise_create_call`. unsafe fn promise_attach_callback( @@ -111,6 +116,10 @@ impl PromiseHandler for Noop { PromiseId::new(0) } + unsafe fn promise_create_and_combine(&mut self, _args: &[PromiseCreateArgs]) -> PromiseId { + PromiseId::new(0) + } + unsafe fn promise_attach_callback( &mut self, _base: PromiseId, diff --git a/engine-standalone-storage/Cargo.toml b/engine-standalone-storage/Cargo.toml index c0cc78c1e..57b89ac43 100644 --- a/engine-standalone-storage/Cargo.toml +++ b/engine-standalone-storage/Cargo.toml @@ -15,11 +15,11 @@ crate-type = ["lib"] [dependencies] aurora-engine = { workspace = true, features = ["std"] } -aurora-engine-types = { workspace = true, features = ["std"] } aurora-engine-modexp = { workspace = true, features = ["std"] } aurora-engine-precompiles = { workspace = true, features = ["std"] } aurora-engine-sdk = { workspace = true, features = ["std"] } aurora-engine-transactions = { workspace = true, features = ["std"] } +aurora-engine-types = { workspace = true, features = ["std"] } evm-core.workspace = true hex = { workspace = true, features = ["std"] } rocksdb.workspace = true @@ -33,6 +33,7 @@ default = ["snappy", "lz4", "zstd", "zlib"] borsh-compat = ["aurora-engine-types/borsh-compat", "aurora-engine-sdk/borsh-compat", "aurora-engine-precompiles/borsh-compat", "aurora-engine/borsh-compat"] mainnet = [] testnet = [] +ext-connector = ["aurora-engine/ext-connector", "aurora-engine-precompiles/ext-connector"] snappy = ["rocksdb/snappy"] lz4 = ["rocksdb/lz4"] zstd = ["rocksdb/zstd"] diff --git a/engine-standalone-storage/src/error.rs b/engine-standalone-storage/src/error.rs index 75481cb75..e7bc9fa42 100644 --- a/engine-standalone-storage/src/error.rs +++ b/engine-standalone-storage/src/error.rs @@ -2,7 +2,7 @@ use crate::{sync::types::TransactionKindTag, TransactionIncluded}; use aurora_engine_types::H256; use std::fmt; -#[derive(Debug, PartialEq, Clone)] +#[derive(Debug, PartialEq, Eq, Clone)] pub enum Error { BlockNotFound(H256), Borsh(String), diff --git a/engine-standalone-storage/src/promise.rs b/engine-standalone-storage/src/promise.rs index d19b5b9e2..35478668e 100644 --- a/engine-standalone-storage/src/promise.rs +++ b/engine-standalone-storage/src/promise.rs @@ -37,6 +37,10 @@ impl<'a> PromiseHandler for NoScheduler<'a> { PromiseId::new(0) } + unsafe fn promise_create_and_combine(&mut self, _args: &[PromiseCreateArgs]) -> PromiseId { + PromiseId::new(0) + } + unsafe fn promise_attach_callback( &mut self, _base: PromiseId, diff --git a/engine-standalone-storage/src/relayer_db/mod.rs b/engine-standalone-storage/src/relayer_db/mod.rs index e64dfb030..d36355438 100644 --- a/engine-standalone-storage/src/relayer_db/mod.rs +++ b/engine-standalone-storage/src/relayer_db/mod.rs @@ -202,8 +202,9 @@ mod test { use super::FallibleIterator; use crate::relayer_db::types::ConnectionParams; use crate::sync::types::{TransactionKind, TransactionMessage}; - use aurora_engine::fungible_token::FungibleTokenMetadata; - use aurora_engine::{connector, parameters, state}; + use aurora_engine::contract_methods::connector::EthConnectorContract; + use aurora_engine::{parameters, state}; + use aurora_engine_types::parameters::connector::FungibleTokenMetadata; use aurora_engine_types::H256; #[allow(clippy::doc_markdown)] @@ -239,7 +240,7 @@ mod test { let result = storage.with_engine_access(block_height, 0, &[], |io| { let mut local_io = io; state::set_state(&mut local_io, &engine_state).unwrap(); - connector::EthConnectorContract::create_contract( + EthConnectorContract::create_contract( io, &engine_state.owner_id, parameters::InitCallArgs { diff --git a/engine-standalone-storage/src/sync/mod.rs b/engine-standalone-storage/src/sync/mod.rs index 6ac1502fc..d8942fe18 100644 --- a/engine-standalone-storage/src/sync/mod.rs +++ b/engine-standalone-storage/src/sync/mod.rs @@ -1,4 +1,5 @@ use crate::engine_state::EngineStateAccess; +use aurora_engine::contract_methods::silo; use aurora_engine::{ contract_methods, engine, parameters::{self, SubmitResult}, @@ -10,8 +11,11 @@ use aurora_engine_sdk::{ }; use aurora_engine_transactions::EthTransactionKind; use aurora_engine_types::{ - account_id::AccountId, borsh::BorshDeserialize, parameters::PromiseWithCallbackArgs, - types::Address, H256, + account_id::AccountId, + borsh::BorshDeserialize, + parameters::{silo as silo_params, xcc, PromiseWithCallbackArgs}, + types::Address, + H256, }; use std::{io, str::FromStr}; @@ -176,8 +180,7 @@ pub fn parse_transaction_kind( } TransactionKindTag::FactoryUpdate => TransactionKind::FactoryUpdate(bytes), TransactionKindTag::FactoryUpdateAddressVersion => { - let args = - aurora_engine::xcc::AddressVersionUpdateArgs::try_from_slice(&bytes).map_err(f)?; + let args = xcc::AddressVersionUpdateArgs::try_from_slice(&bytes).map_err(f)?; TransactionKind::FactoryUpdateAddressVersion(args) } TransactionKindTag::FactorySetWNearAddress => { @@ -187,13 +190,12 @@ pub fn parse_transaction_kind( TransactionKind::FactorySetWNearAddress(address) } TransactionKindTag::SetUpgradeDelayBlocks => { - let args = aurora_engine::parameters::SetUpgradeDelayBlocksArgs::try_from_slice(&bytes) - .map_err(f)?; + let args = parameters::SetUpgradeDelayBlocksArgs::try_from_slice(&bytes).map_err(f)?; TransactionKind::SetUpgradeDelayBlocks(args) } - TransactionKindTag::FundXccSubAccound => { - let args = aurora_engine::xcc::FundXccArgs::try_from_slice(&bytes).map_err(f)?; - TransactionKind::FundXccSubAccound(args) + TransactionKindTag::FundXccSubAccount => { + let args = xcc::FundXccArgs::try_from_slice(&bytes).map_err(f)?; + TransactionKind::FundXccSubAccount(args) } TransactionKindTag::PauseContract => TransactionKind::PauseContract, TransactionKindTag::ResumeContract => TransactionKind::ResumeContract, @@ -223,6 +225,41 @@ pub fn parse_transaction_kind( })?; TransactionKind::SetErc20Metadata(args) } + TransactionKindTag::SetFixedGasCost => { + let args = silo_params::FixedGasCostArgs::try_from_slice(&bytes).map_err(f)?; + TransactionKind::SetFixedGasCost(args) + } + TransactionKindTag::SetSiloParams => { + let args: Option = + BorshDeserialize::try_from_slice(&bytes).map_err(f)?; + TransactionKind::SetSiloParams(args) + } + TransactionKindTag::SetWhitelistStatus => { + let args = silo_params::WhitelistStatusArgs::try_from_slice(&bytes).map_err(f)?; + TransactionKind::SetWhitelistStatus(args) + } + TransactionKindTag::AddEntryToWhitelist => { + let args = silo_params::WhitelistArgs::try_from_slice(&bytes).map_err(f)?; + TransactionKind::AddEntryToWhitelist(args) + } + TransactionKindTag::AddEntryToWhitelistBatch => { + let args: Vec = + BorshDeserialize::try_from_slice(&bytes).map_err(f)?; + TransactionKind::AddEntryToWhitelistBatch(args) + } + TransactionKindTag::RemoveEntryFromWhitelist => { + let args = silo_params::WhitelistArgs::try_from_slice(&bytes).map_err(f)?; + TransactionKind::RemoveEntryFromWhitelist(args) + } + TransactionKindTag::SetEthConnectorContractAccount => { + let args = parameters::SetEthConnectorContractAccountArgs::try_from_slice(&bytes) + .map_err(f)?; + TransactionKind::SetEthConnectorContractAccount(args) + } + TransactionKindTag::MirrorErc20TokenCallback => { + let args = parameters::MirrorErc20TokenArgs::try_from_slice(&bytes).map_err(f)?; + TransactionKind::MirrorErc20TokenCallback(args) + } TransactionKindTag::Unknown => { return Err(ParseTransactionKindError::UnknownMethodName { name: method_name.into(), @@ -401,10 +438,14 @@ where /// Handles all transaction kinds other than `submit`. /// The `submit` transaction kind is special because it is the only one where the transaction hash /// differs from the NEAR receipt hash. -#[allow(clippy::too_many_lines)] +#[allow( + clippy::too_many_lines, + clippy::match_same_arms, + clippy::cognitive_complexity +)] fn non_submit_execute( transaction: &TransactionKind, - io: I, + mut io: I, env: &env::Fixed, promise_data: &[Option>], ) -> Result, error::Error> { @@ -424,7 +465,6 @@ fn non_submit_execute( Some(TransactionExecutionResult::Submit(Ok(result))) } - TransactionKind::DeployErc20(_) => { // No promises can be created by `deploy_erc20_token` let mut handler = crate::promise::NoScheduler { promise_data }; @@ -432,7 +472,6 @@ fn non_submit_execute( Some(TransactionExecutionResult::DeployErc20(result)) } - TransactionKind::FtOnTransfer(_) => { // No promises can be created by `ft_on_transfer` let mut handler = crate::promise::NoScheduler { promise_data }; @@ -440,81 +479,102 @@ fn non_submit_execute( None } - TransactionKind::FtTransferCall(_) => { - let mut handler = crate::promise::NoScheduler { promise_data }; - let promise_args = - contract_methods::connector::ft_transfer_call(io, env, &mut handler)?; + #[cfg(feature = "ext-connector")] + return Ok(None); - Some(TransactionExecutionResult::Promise(promise_args)) - } + #[cfg(not(feature = "ext-connector"))] + { + let mut handler = crate::promise::NoScheduler { promise_data }; + let maybe_promise_args = + contract_methods::connector::ft_transfer_call(io, env, &mut handler)?; + maybe_promise_args.map(TransactionExecutionResult::Promise) + } + } TransactionKind::ResolveTransfer(_, _) => { - let handler = crate::promise::NoScheduler { promise_data }; - contract_methods::connector::ft_resolve_transfer(io, env, &handler)?; + #[cfg(not(feature = "ext-connector"))] + { + let handler = crate::promise::NoScheduler { promise_data }; + contract_methods::connector::ft_resolve_transfer(io, env, &handler)?; + } None } - TransactionKind::FtTransfer(_) => { + #[cfg(not(feature = "ext-connector"))] contract_methods::connector::ft_transfer(io, env)?; None } - TransactionKind::Withdraw(_) => { + #[cfg(not(feature = "ext-connector"))] contract_methods::connector::withdraw(io, env)?; None } - TransactionKind::Deposit(_) => { - let mut handler = crate::promise::NoScheduler { promise_data }; - let promise_args = contract_methods::connector::deposit(io, env, &mut handler)?; - - Some(TransactionExecutionResult::Promise(promise_args)) + #[cfg(feature = "ext-connector")] + return Ok(None); + + #[cfg(not(feature = "ext-connector"))] + { + let mut handler = crate::promise::NoScheduler { promise_data }; + let maybe_promise_args = + contract_methods::connector::deposit(io, env, &mut handler)?; + maybe_promise_args.map(TransactionExecutionResult::Promise) + } } TransactionKind::FinishDeposit(_) => { - let mut handler = crate::promise::NoScheduler { promise_data }; - let maybe_promise_args = - contract_methods::connector::finish_deposit(io, env, &mut handler)?; + #[cfg(feature = "ext-connector")] + return Ok(None); + + #[cfg(not(feature = "ext-connector"))] + { + let mut handler = crate::promise::NoScheduler { promise_data }; + let maybe_promise_args = + contract_methods::connector::finish_deposit(io, env, &mut handler)?; - maybe_promise_args.map(TransactionExecutionResult::Promise) + maybe_promise_args.map(TransactionExecutionResult::Promise) + } } TransactionKind::StorageDeposit(_) => { - let mut handler = crate::promise::NoScheduler { promise_data }; - contract_methods::connector::storage_deposit(io, env, &mut handler)?; + #[cfg(not(feature = "ext-connector"))] + { + let mut handler = crate::promise::NoScheduler { promise_data }; + contract_methods::connector::storage_deposit(io, env, &mut handler)?; + } None } - TransactionKind::StorageUnregister(_) => { - let mut handler = crate::promise::NoScheduler { promise_data }; - contract_methods::connector::storage_unregister(io, env, &mut handler)?; + #[cfg(not(feature = "ext-connector"))] + { + let mut handler = crate::promise::NoScheduler { promise_data }; + contract_methods::connector::storage_unregister(io, env, &mut handler)?; + } None } - TransactionKind::StorageWithdraw(_) => { + #[cfg(not(feature = "ext-connector"))] contract_methods::connector::storage_withdraw(io, env)?; None } - TransactionKind::SetPausedFlags(_) => { + #[cfg(not(feature = "ext-connector"))] contract_methods::connector::set_paused_flags(io, env)?; None } - TransactionKind::RegisterRelayer(_) => { contract_methods::admin::register_relayer(io, env)?; None } - TransactionKind::ExitToNear(_) => { let mut handler = crate::promise::NoScheduler { promise_data }; let maybe_result = contract_methods::connector::exit_to_near_precompile_callback( @@ -525,14 +585,14 @@ fn non_submit_execute( maybe_result.map(|submit_result| TransactionExecutionResult::Submit(Ok(submit_result))) } - TransactionKind::SetConnectorData(_) => { + #[cfg(not(feature = "ext-connector"))] contract_methods::connector::set_eth_connector_contract_data(io, env)?; None } - TransactionKind::NewConnector(_) => { + #[cfg(not(feature = "ext-connector"))] contract_methods::connector::new_eth_connector(io, env)?; None @@ -542,6 +602,12 @@ fn non_submit_execute( None } + TransactionKind::SetEthConnectorContractAccount(_) => { + #[cfg(feature = "ext-connector")] + contract_methods::connector::set_eth_connector_contract_account(io, env)?; + + None + } TransactionKind::FactoryUpdate(_) => { contract_methods::xcc::factory_update(io, env)?; @@ -558,7 +624,7 @@ fn non_submit_execute( None } - TransactionKind::FundXccSubAccound(_) => { + TransactionKind::FundXccSubAccount(_) => { let mut handler = crate::promise::NoScheduler { promise_data }; contract_methods::xcc::fund_xcc_sub_account(io, env, &mut handler)?; @@ -623,6 +689,36 @@ fn non_submit_execute( let mut handler = crate::promise::NoScheduler { promise_data }; contract_methods::connector::set_erc20_metadata(io, env, &mut handler)?; + None + } + TransactionKind::SetFixedGasCost(args) => { + silo::set_fixed_gas_cost(&mut io, args.cost); + None + } + TransactionKind::SetSiloParams(args) => { + silo::set_silo_params(&mut io, args.clone()); + None + } + TransactionKind::AddEntryToWhitelist(args) => { + silo::add_entry_to_whitelist(&io, args); + None + } + TransactionKind::AddEntryToWhitelistBatch(args) => { + silo::add_entry_to_whitelist_batch(&io, args.clone()); + None + } + TransactionKind::RemoveEntryFromWhitelist(args) => { + silo::remove_entry_from_whitelist(&io, args); + None + } + TransactionKind::SetWhitelistStatus(args) => { + silo::set_whitelist_status(&io, args); + None + } + TransactionKind::MirrorErc20TokenCallback(_) => { + let mut handler = crate::promise::NoScheduler { promise_data }; + contract_methods::connector::mirror_erc20_token_callback(io, env, &mut handler)?; + None } }; @@ -648,9 +744,9 @@ impl ConsumeMessageOutcome { #[derive(Debug)] pub struct TransactionIncludedOutcome { - pub hash: aurora_engine_types::H256, + pub hash: H256, pub info: TransactionMessage, - pub diff: crate::Diff, + pub diff: Diff, pub maybe_result: Result, error::Error>, } @@ -672,22 +768,23 @@ pub enum TransactionExecutionResult { } pub mod error { - use aurora_engine::{connector, contract_methods, engine, fungible_token, state, xcc}; + use aurora_engine::contract_methods::connector::errors; + use aurora_engine::{contract_methods, engine, state, xcc}; #[derive(Debug)] pub enum Error { EngineState(state::EngineStateError), Engine(engine::EngineError), DeployErc20(engine::DeployErc20Error), - FtOnTransfer(connector::error::FtTransferCallError), - Deposit(connector::error::DepositError), - FinishDeposit(connector::error::FinishDepositError), - FtTransfer(fungible_token::error::TransferError), - FtWithdraw(connector::error::WithdrawError), - FtStorageFunding(fungible_token::error::StorageFundingError), + FtOnTransfer(errors::FtTransferCallError), + Deposit(errors::DepositError), + FinishDeposit(errors::FinishDepositError), + FtTransfer(errors::TransferError), + FtWithdraw(errors::WithdrawError), + FtStorageFunding(errors::StorageFundingError), InvalidAddress(aurora_engine_types::types::address::error::AddressError), - ConnectorInit(connector::error::InitContractError), - ConnectorStorage(connector::error::StorageReadError), + ConnectorInit(errors::InitContractError), + ConnectorStorage(errors::StorageReadError), FundXccError(xcc::FundXccError), ContractError(contract_methods::ContractError), } @@ -710,38 +807,38 @@ pub mod error { } } - impl From for Error { - fn from(e: connector::error::FtTransferCallError) -> Self { + impl From for Error { + fn from(e: errors::FtTransferCallError) -> Self { Self::FtOnTransfer(e) } } - impl From for Error { - fn from(e: connector::error::DepositError) -> Self { + impl From for Error { + fn from(e: errors::DepositError) -> Self { Self::Deposit(e) } } - impl From for Error { - fn from(e: connector::error::FinishDepositError) -> Self { + impl From for Error { + fn from(e: errors::FinishDepositError) -> Self { Self::FinishDeposit(e) } } - impl From for Error { - fn from(e: fungible_token::error::TransferError) -> Self { + impl From for Error { + fn from(e: errors::TransferError) -> Self { Self::FtTransfer(e) } } - impl From for Error { - fn from(e: connector::error::WithdrawError) -> Self { + impl From for Error { + fn from(e: errors::WithdrawError) -> Self { Self::FtWithdraw(e) } } - impl From for Error { - fn from(e: fungible_token::error::StorageFundingError) -> Self { + impl From for Error { + fn from(e: errors::StorageFundingError) -> Self { Self::FtStorageFunding(e) } } @@ -752,14 +849,14 @@ pub mod error { } } - impl From for Error { - fn from(e: connector::error::InitContractError) -> Self { + impl From for Error { + fn from(e: errors::InitContractError) -> Self { Self::ConnectorInit(e) } } - impl From for Error { - fn from(e: connector::error::StorageReadError) -> Self { + impl From for Error { + fn from(e: errors::StorageReadError) -> Self { Self::ConnectorStorage(e) } } diff --git a/engine-standalone-storage/src/sync/types.rs b/engine-standalone-storage/src/sync/types.rs index 54b876aad..82cfc569f 100644 --- a/engine-standalone-storage/src/sync/types.rs +++ b/engine-standalone-storage/src/sync/types.rs @@ -1,8 +1,10 @@ use crate::Storage; +use aurora_engine::contract_methods::connector::deposit_event; use aurora_engine::parameters; use aurora_engine::xcc::{AddressVersionUpdateArgs, FundXccArgs}; use aurora_engine_transactions::{EthTransactionKind, NormalizedEthTransaction}; use aurora_engine_types::account_id::AccountId; +use aurora_engine_types::parameters::silo; use aurora_engine_types::types::Address; use aurora_engine_types::{ borsh::{self, BorshDeserialize, BorshSerialize}, @@ -116,7 +118,7 @@ pub enum TransactionKind { SetOwner(parameters::SetOwnerArgs), /// Admin only method; used to change upgrade delay blocks SetUpgradeDelayBlocks(parameters::SetUpgradeDelayBlocksArgs), - /// Admin only method + /// Set pause flags to eth-connector SetPausedFlags(parameters::PauseEthConnectorCallArgs), /// Ad entry mapping from address to relayer NEAR account RegisterRelayer(Address), @@ -126,6 +128,8 @@ pub enum TransactionKind { SetConnectorData(parameters::SetContractDataCallArgs), /// Initialize eth-connector NewConnector(parameters::InitCallArgs), + /// Set account id of the external eth-connector. + SetEthConnectorContractAccount(parameters::SetEthConnectorContractAccountArgs), /// Initialize Engine NewEngine(parameters::NewCallArgs), /// Update xcc-router bytecode @@ -133,7 +137,7 @@ pub enum TransactionKind { /// Update the version of a deployed xcc-router contract FactoryUpdateAddressVersion(AddressVersionUpdateArgs), FactorySetWNearAddress(Address), - FundXccSubAccound(FundXccArgs), + FundXccSubAccount(FundXccArgs), /// Pause the contract PauseContract, /// Resume the contract @@ -147,6 +151,15 @@ pub enum TransactionKind { StartHashchain(parameters::StartHashchainArgs), /// Set metadata of ERC-20 contract. SetErc20Metadata(parameters::SetErc20MetadataArgs), + /// Silo operations + SetFixedGasCost(silo::FixedGasCostArgs), + SetSiloParams(Option), + AddEntryToWhitelist(silo::WhitelistArgs), + AddEntryToWhitelistBatch(Vec), + RemoveEntryFromWhitelist(silo::WhitelistArgs), + SetWhitelistStatus(silo::WhitelistStatusArgs), + /// Callback which mirrors existed ERC-20 contract deployed on the main contract. + MirrorErc20TokenCallback(parameters::MirrorErc20TokenArgs), /// Sentinel kind for cases where a NEAR receipt caused a /// change in Aurora state, but we failed to parse the Action. Unknown, @@ -217,7 +230,7 @@ impl TransactionKind { let from = Self::get_implicit_address(caller); let nonce = Self::get_implicit_nonce(&from, block_height, transaction_position, storage); - let data = aurora_engine::engine::setup_deploy_erc20_input(engine_account); + let data = aurora_engine::engine::setup_deploy_erc20_input(engine_account, None); NormalizedEthTransaction { address: from, chain_id: None, @@ -233,11 +246,14 @@ impl TransactionKind { } Self::FtOnTransfer(args) => { if engine_account == caller { - let recipient = aurora_engine::deposit_event::FtTransferMessageData::parse_on_transfer_message(&args.msg).map(|data| data.recipient).unwrap_or_default(); + let recipient = + deposit_event::FtTransferMessageData::parse_on_transfer_message(&args.msg) + .map(|data| data.recipient) + .unwrap_or_default(); let value = Wei::new(U256::from(args.amount.as_u128())); // This transaction mints new ETH, so we'll say it comes from the zero address. NormalizedEthTransaction { - address: types::Address::default(), + address: Address::default(), chain_id: None, nonce: U256::zero(), gas_limit: U256::from(u64::MAX), @@ -262,11 +278,11 @@ impl TransactionKind { }) .result .ok() - .and_then(|bytes| types::Address::try_from_slice(&bytes).ok()) + .and_then(|bytes| Address::try_from_slice(&bytes).ok()) .unwrap_or_default(); let erc20_recipient = hex::decode(&args.msg.as_bytes()[0..40]) .ok() - .and_then(|bytes| types::Address::try_from_slice(&bytes).ok()) + .and_then(|bytes| Address::try_from_slice(&bytes).ok()) .unwrap_or_default(); let data = aurora_engine::engine::setup_receive_erc20_tokens_input( &args, @@ -295,56 +311,56 @@ impl TransactionKind { || Self::no_evm_execution(method_name), |args| { args.erc20_address.map_or_else(|| { - // ETH refund - let value = Wei::new(U256::from_big_endian(&args.amount)); - let from = aurora_engine_precompiles::native::exit_to_near::ADDRESS; - let nonce = Self::get_implicit_nonce( - &from, - block_height, - transaction_position, - storage, - ); - NormalizedEthTransaction { - address: from, - chain_id: None, - nonce, - gas_limit: U256::from(u64::MAX), - max_priority_fee_per_gas: U256::zero(), - max_fee_per_gas: U256::zero(), - to: Some(args.recipient_address), - value, - data: Vec::new(), - access_list: Vec::new(), - } - }, - |erc20_address| { - // ERC-20 refund - let from = Self::get_implicit_address(engine_account); - let nonce = Self::get_implicit_nonce( - &from, - block_height, - transaction_position, - storage, - ); - let to = erc20_address; - let data = aurora_engine::engine::setup_refund_on_error_input( - U256::from_big_endian(&args.amount), - args.recipient_address, - ); - NormalizedEthTransaction { - address: from, - chain_id: None, - nonce, - gas_limit: U256::from(u64::MAX), - max_priority_fee_per_gas: U256::zero(), - max_fee_per_gas: U256::zero(), - to: Some(to), - value: Wei::zero(), - data, - access_list: Vec::new(), - } - }, - ) + // ETH refund + let value = Wei::new(U256::from_big_endian(&args.amount)); + let from = aurora_engine_precompiles::native::exit_to_near::ADDRESS; + let nonce = Self::get_implicit_nonce( + &from, + block_height, + transaction_position, + storage, + ); + NormalizedEthTransaction { + address: from, + chain_id: None, + nonce, + gas_limit: U256::from(u64::MAX), + max_priority_fee_per_gas: U256::zero(), + max_fee_per_gas: U256::zero(), + to: Some(args.recipient_address), + value, + data: Vec::new(), + access_list: Vec::new(), + } + }, + |erc20_address| { + // ERC-20 refund + let from = Self::get_implicit_address(engine_account); + let nonce = Self::get_implicit_nonce( + &from, + block_height, + transaction_position, + storage, + ); + let to = erc20_address; + let data = aurora_engine::engine::setup_refund_on_error_input( + U256::from_big_endian(&args.amount), + args.recipient_address, + ); + NormalizedEthTransaction { + address: from, + chain_id: None, + nonce, + gas_limit: U256::from(u64::MAX), + max_priority_fee_per_gas: U256::zero(), + max_fee_per_gas: U256::zero(), + to: Some(to), + value: Wei::zero(), + data, + access_list: Vec::new(), + } + }, + ) }, ) }, @@ -363,6 +379,9 @@ impl TransactionKind { Self::RegisterRelayer(_) => Self::no_evm_execution("register_relayer"), Self::SetConnectorData(_) => Self::no_evm_execution("set_connector_data"), Self::NewConnector(_) => Self::no_evm_execution("new_connector"), + Self::SetEthConnectorContractAccount(_) => { + Self::no_evm_execution("set_eth_connector_contract_account") + } Self::NewEngine(_) => Self::no_evm_execution("new_engine"), Self::FactoryUpdate(_) => Self::no_evm_execution("factory_update"), Self::FactoryUpdateAddressVersion(_) => { @@ -374,7 +393,7 @@ impl TransactionKind { Self::ResumePrecompiles(_) => Self::no_evm_execution("resume_precompiles"), Self::SetOwner(_) => Self::no_evm_execution("set_owner"), Self::SetUpgradeDelayBlocks(_) => Self::no_evm_execution("set_upgrade_delay_blocks"), - Self::FundXccSubAccound(_) => Self::no_evm_execution("fund_xcc_sub_account"), + Self::FundXccSubAccount(_) => Self::no_evm_execution("fund_xcc_sub_account"), Self::PauseContract => Self::no_evm_execution("pause_contract"), Self::ResumeContract => Self::no_evm_execution("resume_contract"), Self::SetKeyManager(_) => Self::no_evm_execution("set_key_manager"), @@ -382,6 +401,19 @@ impl TransactionKind { Self::RemoveRelayerKey(_) => Self::no_evm_execution("remove_relayer_key"), Self::StartHashchain(_) => Self::no_evm_execution("start_hashchain"), Self::SetErc20Metadata(_) => Self::no_evm_execution("set_erc20_metadata"), + Self::SetFixedGasCost(_) => Self::no_evm_execution("set_fixed_gas_cost"), + Self::SetSiloParams(_) => Self::no_evm_execution("set_silo_params"), + Self::AddEntryToWhitelist(_) => Self::no_evm_execution("add_entry_to_whitelist"), + Self::AddEntryToWhitelistBatch(_) => { + Self::no_evm_execution("add_entry_to_whitelist_batch") + } + Self::RemoveEntryFromWhitelist(_) => { + Self::no_evm_execution("remove_entry_from_whitelist") + } + Self::SetWhitelistStatus(_) => Self::no_evm_execution("set_whitelist_status"), + Self::MirrorErc20TokenCallback(_) => { + Self::no_evm_execution("mirror_erc20_token_callback") + } } } @@ -479,7 +511,7 @@ pub enum TransactionKindTag { #[strum(serialize = "set_upgrade_delay_blocks")] SetUpgradeDelayBlocks, #[strum(serialize = "fund_xcc_sub_account")] - FundXccSubAccound, + FundXccSubAccount, #[strum(serialize = "pause_contract")] PauseContract, #[strum(serialize = "resume_contract")] @@ -494,6 +526,22 @@ pub enum TransactionKindTag { StartHashchain, #[strum(serialize = "set_erc20_metadata")] SetErc20Metadata, + #[strum(serialize = "set_eth_connector_contract_account")] + SetEthConnectorContractAccount, + #[strum(serialize = "set_fixed_gas_cost")] + SetFixedGasCost, + #[strum(serialize = "set_silo_params")] + SetSiloParams, + #[strum(serialize = "set_whitelist_status")] + SetWhitelistStatus, + #[strum(serialize = "add_entry_to_whitelist")] + AddEntryToWhitelist, + #[strum(serialize = "add_entry_to_whitelist_batch")] + AddEntryToWhitelistBatch, + #[strum(serialize = "remove_entry_from_whitelist")] + RemoveEntryFromWhitelist, + #[strum(serialize = "mirror_erc20_token_callback")] + MirrorErc20TokenCallback, Unknown, } @@ -535,7 +583,7 @@ impl TransactionKind { } Self::NewEngine(args) => args.try_to_vec().unwrap_or_default(), Self::FactoryUpdateAddressVersion(args) => args.try_to_vec().unwrap_or_default(), - Self::FundXccSubAccound(args) => args.try_to_vec().unwrap_or_default(), + Self::FundXccSubAccount(args) => args.try_to_vec().unwrap_or_default(), Self::PauseContract | Self::ResumeContract | Self::Unknown => Vec::new(), Self::SetKeyManager(args) => args.try_to_vec().unwrap_or_default(), Self::AddRelayerKey(args) | Self::RemoveRelayerKey(args) => { @@ -543,6 +591,15 @@ impl TransactionKind { } Self::StartHashchain(args) => args.try_to_vec().unwrap_or_default(), Self::SetErc20Metadata(args) => serde_json::to_vec(args).unwrap_or_default(), + Self::SetFixedGasCost(args) => args.try_to_vec().unwrap_or_default(), + Self::SetSiloParams(args) => args.try_to_vec().unwrap_or_default(), + Self::AddEntryToWhitelist(args) | Self::RemoveEntryFromWhitelist(args) => { + args.try_to_vec().unwrap_or_default() + } + Self::AddEntryToWhitelistBatch(args) => args.try_to_vec().unwrap_or_default(), + Self::SetWhitelistStatus(args) => args.try_to_vec().unwrap_or_default(), + Self::SetEthConnectorContractAccount(args) => args.try_to_vec().unwrap_or_default(), + Self::MirrorErc20TokenCallback(args) => args.try_to_vec().unwrap_or_default(), } } } @@ -579,7 +636,7 @@ impl From<&TransactionKind> for TransactionKindTag { TransactionKind::SetOwner(_) => Self::SetOwner, TransactionKind::SubmitWithArgs(_) => Self::SubmitWithArgs, TransactionKind::SetUpgradeDelayBlocks(_) => Self::SetUpgradeDelayBlocks, - TransactionKind::FundXccSubAccound(_) => Self::FundXccSubAccound, + TransactionKind::FundXccSubAccount(_) => Self::FundXccSubAccount, TransactionKind::PauseContract => Self::PauseContract, TransactionKind::ResumeContract => Self::ResumeContract, TransactionKind::SetKeyManager(_) => Self::SetKeyManager, @@ -587,7 +644,17 @@ impl From<&TransactionKind> for TransactionKindTag { TransactionKind::RemoveRelayerKey(_) => Self::RemoveRelayerKey, TransactionKind::StartHashchain(_) => Self::StartHashchain, TransactionKind::SetErc20Metadata(_) => Self::SetErc20Metadata, + TransactionKind::SetEthConnectorContractAccount(_) => { + Self::SetEthConnectorContractAccount + } + TransactionKind::SetFixedGasCost(_) => Self::SetFixedGasCost, + TransactionKind::SetSiloParams(_) => Self::SetSiloParams, + TransactionKind::AddEntryToWhitelist(_) => Self::AddEntryToWhitelist, + TransactionKind::AddEntryToWhitelistBatch(_) => Self::AddEntryToWhitelistBatch, + TransactionKind::RemoveEntryFromWhitelist(_) => Self::RemoveEntryFromWhitelist, + TransactionKind::SetWhitelistStatus(_) => Self::SetWhitelistStatus, TransactionKind::Unknown => Self::Unknown, + TransactionKind::MirrorErc20TokenCallback(_) => Self::MirrorErc20TokenCallback, } } } @@ -726,6 +793,7 @@ impl<'a> TryFrom> for TransactionMessage { /// Same as `TransactionKind`, but with `Submit` variant replaced with raw bytes /// so that it can derive the Borsh traits. All non-copy elements are `Cow` also /// so that this type can be cheaply created from a `TransactionKind` reference. +/// !!!!! New types of transactions must be added at the end of the enum. !!!!!! #[derive(BorshDeserialize, BorshSerialize, Clone)] enum BorshableTransactionKind<'a> { Submit(Cow<'a, Vec>), @@ -761,7 +829,7 @@ enum BorshableTransactionKind<'a> { Unknown, SetOwner(Cow<'a, parameters::SetOwnerArgs>), SubmitWithArgs(Cow<'a, parameters::SubmitArgs>), - FundXccSubAccound(Cow<'a, FundXccArgs>), + FundXccSubAccount(Cow<'a, FundXccArgs>), SetUpgradeDelayBlocks(Cow<'a, parameters::SetUpgradeDelayBlocksArgs>), PauseContract, ResumeContract, @@ -770,6 +838,14 @@ enum BorshableTransactionKind<'a> { RemoveRelayerKey(Cow<'a, parameters::RelayerKeyArgs>), StartHashchain(Cow<'a, parameters::StartHashchainArgs>), SetErc20Metadata(Cow<'a, parameters::SetErc20MetadataArgs>), + SetFixedGasCost(Cow<'a, silo::FixedGasCostArgs>), + SetSiloParams(Cow<'a, Option>), + AddEntryToWhitelist(Cow<'a, silo::WhitelistArgs>), + AddEntryToWhitelistBatch(Cow<'a, Vec>), + RemoveEntryFromWhitelist(Cow<'a, silo::WhitelistArgs>), + SetWhitelistStatus(Cow<'a, silo::WhitelistStatusArgs>), + SetEthConnectorContractAccount(Cow<'a, parameters::SetEthConnectorContractAccountArgs>), + MirrorErc20TokenCallback(Cow<'a, parameters::MirrorErc20TokenArgs>), } impl<'a> From<&'a TransactionKind> for BorshableTransactionKind<'a> { @@ -811,8 +887,11 @@ impl<'a> From<&'a TransactionKind> for BorshableTransactionKind<'a> { TransactionKind::Unknown => Self::Unknown, TransactionKind::PausePrecompiles(x) => Self::PausePrecompiles(Cow::Borrowed(x)), TransactionKind::ResumePrecompiles(x) => Self::ResumePrecompiles(Cow::Borrowed(x)), + TransactionKind::SetEthConnectorContractAccount(x) => { + Self::SetEthConnectorContractAccount(Cow::Borrowed(x)) + } TransactionKind::SetOwner(x) => Self::SetOwner(Cow::Borrowed(x)), - TransactionKind::FundXccSubAccound(x) => Self::FundXccSubAccound(Cow::Borrowed(x)), + TransactionKind::FundXccSubAccount(x) => Self::FundXccSubAccount(Cow::Borrowed(x)), TransactionKind::SetUpgradeDelayBlocks(x) => { Self::SetUpgradeDelayBlocks(Cow::Borrowed(x)) } @@ -823,6 +902,19 @@ impl<'a> From<&'a TransactionKind> for BorshableTransactionKind<'a> { TransactionKind::RemoveRelayerKey(x) => Self::RemoveRelayerKey(Cow::Borrowed(x)), TransactionKind::StartHashchain(x) => Self::StartHashchain(Cow::Borrowed(x)), TransactionKind::SetErc20Metadata(x) => Self::SetErc20Metadata(Cow::Borrowed(x)), + TransactionKind::SetFixedGasCost(x) => Self::SetFixedGasCost(Cow::Borrowed(x)), + TransactionKind::SetSiloParams(x) => Self::SetSiloParams(Cow::Borrowed(x)), + TransactionKind::AddEntryToWhitelist(x) => Self::AddEntryToWhitelist(Cow::Borrowed(x)), + TransactionKind::AddEntryToWhitelistBatch(x) => { + Self::AddEntryToWhitelistBatch(Cow::Borrowed(x)) + } + TransactionKind::RemoveEntryFromWhitelist(x) => { + Self::RemoveEntryFromWhitelist(Cow::Borrowed(x)) + } + TransactionKind::SetWhitelistStatus(x) => Self::SetWhitelistStatus(Cow::Borrowed(x)), + TransactionKind::MirrorErc20TokenCallback(x) => { + Self::MirrorErc20TokenCallback(Cow::Borrowed(x)) + } } } } @@ -881,9 +973,12 @@ impl<'a> TryFrom> for TransactionKind { BorshableTransactionKind::ResumePrecompiles(x) => { Ok(Self::ResumePrecompiles(x.into_owned())) } + BorshableTransactionKind::SetEthConnectorContractAccount(x) => { + Ok(Self::SetEthConnectorContractAccount(x.into_owned())) + } BorshableTransactionKind::SetOwner(x) => Ok(Self::SetOwner(x.into_owned())), - BorshableTransactionKind::FundXccSubAccound(x) => { - Ok(Self::FundXccSubAccound(x.into_owned())) + BorshableTransactionKind::FundXccSubAccount(x) => { + Ok(Self::FundXccSubAccount(x.into_owned())) } BorshableTransactionKind::SetUpgradeDelayBlocks(x) => { Ok(Self::SetUpgradeDelayBlocks(x.into_owned())) @@ -899,6 +994,25 @@ impl<'a> TryFrom> for TransactionKind { BorshableTransactionKind::SetErc20Metadata(x) => { Ok(Self::SetErc20Metadata(x.into_owned())) } + BorshableTransactionKind::SetFixedGasCost(x) => { + Ok(Self::SetFixedGasCost(x.into_owned())) + } + BorshableTransactionKind::SetSiloParams(x) => Ok(Self::SetSiloParams(x.into_owned())), + BorshableTransactionKind::AddEntryToWhitelist(x) => { + Ok(Self::AddEntryToWhitelist(x.into_owned())) + } + BorshableTransactionKind::AddEntryToWhitelistBatch(x) => { + Ok(Self::AddEntryToWhitelistBatch(x.into_owned())) + } + BorshableTransactionKind::RemoveEntryFromWhitelist(x) => { + Ok(Self::RemoveEntryFromWhitelist(x.into_owned())) + } + BorshableTransactionKind::SetWhitelistStatus(x) => { + Ok(Self::SetWhitelistStatus(x.into_owned())) + } + BorshableTransactionKind::MirrorErc20TokenCallback(x) => { + Ok(Self::MirrorErc20TokenCallback(x.into_owned())) + } } } } diff --git a/engine-test-doubles/src/promise.rs b/engine-test-doubles/src/promise.rs index 20c0b17d4..7da0e1a26 100644 --- a/engine-test-doubles/src/promise.rs +++ b/engine-test-doubles/src/promise.rs @@ -1,6 +1,8 @@ use aurora_engine_sdk::promise::PromiseHandler; use aurora_engine_sdk::promise::PromiseId; -use aurora_engine_types::parameters::{PromiseBatchAction, PromiseCreateArgs}; +use aurora_engine_types::parameters::{ + NearPromise, PromiseBatchAction, PromiseCreateArgs, SimpleNearPromise, +}; use aurora_engine_types::types::PromiseResult; use std::collections::HashMap; @@ -13,6 +15,7 @@ pub enum PromiseArgs { callback: PromiseCreateArgs, }, Batch(PromiseBatchAction), + Recursive(NearPromise), } /// Doesn't actually schedule any promises, only tracks what promises should be scheduled @@ -52,6 +55,19 @@ impl PromiseHandler for PromiseTracker { PromiseId::new(id) } + unsafe fn promise_create_and_combine(&mut self, args: &[PromiseCreateArgs]) -> PromiseId { + let id = self.take_id(); + self.scheduled_promises.insert( + id, + PromiseArgs::Recursive(NearPromise::And( + args.iter() + .map(|p| NearPromise::Simple(SimpleNearPromise::Create(p.clone()))) + .collect(), + )), + ); + PromiseId::new(id) + } + unsafe fn promise_attach_callback( &mut self, base: PromiseId, diff --git a/engine-tests-connector/Cargo.toml b/engine-tests-connector/Cargo.toml new file mode 100644 index 000000000..67566e6d0 --- /dev/null +++ b/engine-tests-connector/Cargo.toml @@ -0,0 +1,31 @@ +[package] +name = "aurora-engine-tests-connector" +version = "1.0.0" +authors.workspace = true +edition.workspace = true +description = "ETH connector tests" +homepage.workspace = true +repository.workspace = true +license.workspace = true +publish.workspace = true +autobenches = false + +[dev-dependencies] +aurora-engine = { workspace = true, features = ["std", "tracing", "impl-serde", "borsh-compat"] } +aurora-engine-types = { workspace = true, features = ["std", "impl-serde", "borsh-compat"] } + +anyhow.workspace = true +byte-slice-cast.workspace = true +near-sdk.workspace = true +near-units.workspace = true +tokio = { workspace = true, features = ["macros"] } +hex.workspace = true +ethabi.workspace = true +rlp.workspace = true +serde = { workspace = true, features = ["derive"] } +workspaces = "0.7.0" + +[features] +mainnet-test = [] +testnet-test = [] +ext-connector = [] diff --git a/engine-tests/src/tests/eth_connector.rs b/engine-tests-connector/src/connector.rs similarity index 53% rename from engine-tests/src/tests/eth_connector.rs rename to engine-tests-connector/src/connector.rs index e873be8a2..6296ed323 100644 --- a/engine-tests/src/tests/eth_connector.rs +++ b/engine-tests-connector/src/connector.rs @@ -1,373 +1,444 @@ -use crate::prelude::{Fee, NEP141Wei, H256, U256}; -use crate::utils::address_from_hex; -use crate::utils::workspace::create_sub_account; -use aurora_engine::connector::{PAUSE_DEPOSIT, PAUSE_WITHDRAW, UNPAUSE_ALL}; -use aurora_engine_types::parameters::connector::Proof; -use aurora_engine_types::parameters::WithdrawCallArgs; -use aurora_engine_types::types::Address; -use aurora_engine_workspace::types::ExecutionOutcome; -use aurora_engine_workspace::{parse_near, EngineContract, EngineContractBuilder}; -use serde_json::json; -use std::fmt::Debug; - -const CONTRACT_ACC: &str = "eth_connector.root"; -const PROOF_DATA_NEAR: &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: &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: &str = "eth_recipient.root"; -const PROVER_ACCOUNT: &str = "eth_connector.root"; -const CUSTODIAN_ADDRESS: &str = "096DE9C2B8A5B8c22cEe3289B101f6960d68E51E"; -const DEPOSITED_AMOUNT: u128 = 800400; -const DEPOSITED_FEE: u128 = 400; -const RECIPIENT_ETH_ADDRESS: &str = "891b2749238b27ff58e951088e55b04de71dc374"; -const EVM_CUSTODIAN_ADDRESS: &str = "096DE9C2B8A5B8c22cEe3289B101f6960d68E51E"; -const DEPOSITED_EVM_AMOUNT: u128 = 10200; -const DEPOSITED_EVM_FEE: u128 = 200; +use crate::utils::*; +use aurora_engine::contract_methods::connector::deposit_event::{ + DepositedEvent, TokenMessageData, DEPOSITED_EVENT, +}; +use aurora_engine_types::parameters::connector::{Proof, WithdrawResult}; +use aurora_engine_types::{ + types::{Address, Fee, NEP141Wei}, + H256, U256, +}; +use byte_slice_cast::AsByteSlice; +use near_sdk::serde_json::json; +use near_sdk::{json_types::U128, serde, ONE_YOCTO}; +use std::str::FromStr; +use workspaces::AccountId; -#[tokio::test] -async fn test_deposit_eth_to_near_balance_total_supply() { - let contract = init(CUSTODIAN_ADDRESS).await.unwrap(); - let result = call_deposit_eth_to_near(&contract).await.unwrap(); - assert!(result.is_success()); - - let balance = get_eth_on_near_balance(&contract, DEPOSITED_RECIPIENT).await; - assert_eq!(balance, DEPOSITED_AMOUNT - DEPOSITED_FEE); - - let balance = get_eth_on_near_balance(&contract, CONTRACT_ACC).await; - assert_eq!(balance, DEPOSITED_FEE); - - let balance = total_supply(&contract).await; - assert_eq!(balance, DEPOSITED_AMOUNT); - - let balance = total_eth_supply_on_near(&contract).await; - assert_eq!(balance, DEPOSITED_AMOUNT); - - let balance = total_eth_supply_on_aurora(&contract).await; - assert_eq!(balance, 0); +/// Bytes for a NEAR smart contract implementing `ft_on_transfer` +fn dummy_ft_receiver_bytes() -> Vec { + let base_path = std::path::Path::new("../etc") + .join("tests") + .join("ft-receiver"); + let output_path = base_path.join("target/wasm32-unknown-unknown/release/ft_receiver.wasm"); + crate::rust::compile(base_path); + std::fs::read(output_path).unwrap() } #[tokio::test] -async fn test_deposit_eth_to_aurora_balance_total_supply() { - let contract = init(EVM_CUSTODIAN_ADDRESS).await.unwrap(); - let custodian_address = address_from_hex(CUSTODIAN_ADDRESS); +async fn test_aurora_ft_transfer() -> anyhow::Result<()> { + let contract = TestContract::new().await?; + let proof = contract.get_proof(PROOF_DATA_NEAR); let res = contract - .register_relayer(custodian_address) + .engine_contract + .call("deposit") + .args_borsh(proof) + .gas(DEFAULT_GAS) .transact() - .await - .unwrap(); + .await?; assert!(res.is_success()); - call_deposit_eth_to_aurora(&contract).await; - assert_proof_was_used(&contract, PROOF_DATA_ETH).await; - - let balance = get_eth_balance(&contract, address_from_hex(RECIPIENT_ETH_ADDRESS)).await; - assert_eq!(balance, DEPOSITED_EVM_AMOUNT - DEPOSITED_EVM_FEE); - - let balance = get_eth_balance(&contract, custodian_address).await; - assert_eq!(balance, DEPOSITED_EVM_FEE); - - let balance = total_supply(&contract).await; - assert_eq!(balance, DEPOSITED_EVM_AMOUNT); + let user_acc = contract + .create_sub_account(DEPOSITED_RECIPIENT_NAME) + .await?; + let transfer_amount: U128 = 70.into(); + let receiver_id = contract.engine_contract.id(); + let res = user_acc + .call(contract.engine_contract.id(), "ft_transfer") + .args_json(json!({ + "receiver_id": &receiver_id, + "amount": transfer_amount, + "memo": "transfer memo" + })) + .gas(DEFAULT_GAS) + .deposit(ONE_YOCTO) + .transact() + .await?; + assert!(res.is_success()); - let balance = total_eth_supply_on_near(&contract).await; - assert_eq!(balance, DEPOSITED_EVM_AMOUNT); + let balance = contract + .eth_connector_contract + .call("ft_balance_of") + .args_json((&receiver_id,)) + .view() + .await? + .json::() + .unwrap(); + assert_eq!(balance.0, transfer_amount.0); + + let balance = contract + .eth_connector_contract + .call("ft_balance_of") + .args_json((user_acc.id(),)) + .view() + .await? + .json::() + .unwrap(); + assert_eq!(balance.0, DEPOSITED_AMOUNT - transfer_amount.0); + + let balance = contract + .eth_connector_contract + .call("ft_total_supply") + .view() + .await? + .json::() + .unwrap(); + assert_eq!(balance.0, DEPOSITED_AMOUNT); - let balance = total_eth_supply_on_aurora(&contract).await; - assert_eq!(balance, DEPOSITED_EVM_AMOUNT); + Ok(()) } #[tokio::test] -async fn test_withdraw_eth_from_near() { - let contract = init(CUSTODIAN_ADDRESS).await.unwrap(); - let result = call_deposit_eth_to_near(&contract).await.unwrap(); - assert!(result.is_success()); - - let withdraw_amount = 100; - let recipient_addr = address_from_hex(RECIPIENT_ETH_ADDRESS); - let res = contract - .withdraw(recipient_addr, withdraw_amount) - .deposit(1) +async fn test_ft_transfer() -> anyhow::Result<()> { + let contract = TestContract::new().await?; + contract.call_deposit_eth_to_near().await?; + + let user_acc = contract + .create_sub_account(DEPOSITED_RECIPIENT_NAME) + .await?; + let transfer_amount: U128 = 70.into(); + let receiver_id = contract.engine_contract.id(); + let res = user_acc + .call(contract.engine_contract.id(), "ft_transfer") + .args_json(json!({ + "receiver_id": &receiver_id, + "amount": transfer_amount, + "memo": "transfer memo" + })) + .gas(DEFAULT_GAS) + .deposit(ONE_YOCTO) .transact() - .await - .unwrap(); + .await?; assert!(res.is_success()); - let withdraw_result = res.into_value(); - assert_eq!(withdraw_result.amount.as_u128(), withdraw_amount); assert_eq!( - withdraw_result.recipient_id.encode(), - recipient_addr.encode() + contract.get_eth_on_near_balance(user_acc.id()).await?.0, + DEPOSITED_AMOUNT - transfer_amount.0, ); assert_eq!( - withdraw_result.eth_custodian_address.encode(), - CUSTODIAN_ADDRESS.to_lowercase() + contract.get_eth_on_near_balance(receiver_id).await?.0, + transfer_amount.0, ); - - let balance = get_eth_on_near_balance(&contract, CONTRACT_ACC).await; - assert_eq!(balance, DEPOSITED_FEE - withdraw_amount); - - let balance = get_eth_on_near_balance(&contract, DEPOSITED_RECIPIENT).await; - assert_eq!(balance, DEPOSITED_AMOUNT - DEPOSITED_FEE); - - let balance = total_supply(&contract).await; - assert_eq!(balance, DEPOSITED_AMOUNT - withdraw_amount); + assert_eq!(DEPOSITED_AMOUNT, contract.total_supply().await?); + Ok(()) } #[tokio::test] -async fn test_ft_transfer() { - let contract = init(CUSTODIAN_ADDRESS).await.unwrap(); - let result = call_deposit_eth_to_near(&contract).await.unwrap(); - assert!(result.is_success()); - let transfer_amount = 70; - let res = contract - .ft_transfer( - &DEPOSITED_RECIPIENT.parse().unwrap(), - transfer_amount.into(), - Some("transfer memo".to_string()), - ) - .deposit(1) +async fn test_withdraw_eth_from_near() -> anyhow::Result<()> { + let contract = TestContract::new().await?; + contract.call_deposit_eth_to_near().await?; + + let user_acc = contract + .create_sub_account(DEPOSITED_RECIPIENT_NAME) + .await?; + + let withdraw_amount = NEP141Wei::new(100); + let recipient_addr = validate_eth_address(RECIPIENT_ETH_ADDRESS); + let res = user_acc + .call(contract.engine_contract.id(), "withdraw") + .args_borsh((recipient_addr, withdraw_amount)) + .gas(DEFAULT_GAS) + .deposit(ONE_YOCTO) .transact() - .await - .unwrap(); + .await?; assert!(res.is_success()); - let balance = get_eth_on_near_balance(&contract, DEPOSITED_RECIPIENT).await; - assert_eq!(balance, DEPOSITED_AMOUNT - DEPOSITED_FEE + transfer_amount); - - let balance = get_eth_on_near_balance(&contract, CONTRACT_ACC).await; - assert_eq!(balance, DEPOSITED_FEE - transfer_amount); + let data: WithdrawResult = res.borsh()?; + let custodian_addr = validate_eth_address(CUSTODIAN_ADDRESS); + assert_eq!(data.recipient_id, recipient_addr); + assert_eq!(data.amount, withdraw_amount); + assert_eq!(data.eth_custodian_address, custodian_addr); - let balance = total_supply(&contract).await; - assert_eq!(balance, DEPOSITED_AMOUNT); + assert_eq!( + contract.get_eth_on_near_balance(user_acc.id()).await?.0, + DEPOSITED_AMOUNT - withdraw_amount.as_u128(), + ); + assert_eq!( + contract.total_supply().await?, + DEPOSITED_AMOUNT - withdraw_amount.as_u128(), + ); + Ok(()) +} - let balance = total_eth_supply_on_aurora(&contract).await; - assert_eq!(balance, 0); +#[tokio::test] +async fn test_deposit_eth_to_near_balance_total_supply() -> anyhow::Result<()> { + let contract = TestContract::new().await?; + contract.call_deposit_eth_to_near().await?; + assert!( + contract.call_is_used_proof(PROOF_DATA_NEAR).await?, + "Expected not to fail because the proof should have been already used", + ); - let balance = total_eth_supply_on_near(&contract).await; - assert_eq!(balance, DEPOSITED_AMOUNT); + let user_acc = contract + .create_sub_account(DEPOSITED_RECIPIENT_NAME) + .await?; + assert_eq!( + contract.get_eth_on_near_balance(user_acc.id()).await?.0, + DEPOSITED_AMOUNT + ); + assert_eq!(contract.total_supply().await?, DEPOSITED_AMOUNT); + Ok(()) } +// NOTE: We don't test relayer fee #[tokio::test] -async fn test_ft_transfer_call_eth() { - let contract = init(CUSTODIAN_ADDRESS).await.unwrap(); - let result = call_deposit_eth_to_near(&contract).await.unwrap(); - assert!(result.is_success()); +async fn test_deposit_eth_to_aurora_balance_total_supply() -> anyhow::Result<()> { + let contract = TestContract::new().await?; + contract.call_deposit_eth_to_aurora().await?; + assert!( + contract.call_is_used_proof(PROOF_DATA_ETH).await?, + "Expected not to fail because the proof should have been already used", + ); - let balance = get_eth_on_near_balance(&contract, DEPOSITED_RECIPIENT).await; - assert_eq!(balance, DEPOSITED_AMOUNT - DEPOSITED_FEE); + assert_eq!( + contract + .get_eth_balance(&validate_eth_address(RECIPIENT_ETH_ADDRESS)) + .await?, + DEPOSITED_EVM_AMOUNT + ); + assert_eq!(contract.total_supply().await?, DEPOSITED_EVM_AMOUNT); + Ok(()) +} - let balance = get_eth_on_near_balance(&contract, CONTRACT_ACC).await; - assert_eq!(balance, DEPOSITED_FEE); +#[tokio::test] +async fn test_ft_transfer_call_eth() -> anyhow::Result<()> { + let contract = TestContract::new().await?; + contract.call_deposit_eth_to_near().await?; - let res = contract - .register_relayer(address_from_hex(CUSTODIAN_ADDRESS)) - .transact() - .await - .unwrap(); - assert!(res.is_success()); + let user_acc = contract + .create_sub_account(DEPOSITED_RECIPIENT_NAME) + .await?; + assert_eq!( + contract.get_eth_on_near_balance(user_acc.id()).await?.0, + DEPOSITED_AMOUNT, + ); + assert_eq!( + contract + .get_eth_on_near_balance(contract.engine_contract.id()) + .await? + .0, + 0, + ); - let transfer_amount = 50; + let transfer_amount: U128 = 50.into(); let fee: u128 = 30; - let message = create_message(CONTRACT_ACC, RECIPIENT_ETH_ADDRESS, fee); - let res = contract - .ft_transfer_call( - &CONTRACT_ACC.parse().unwrap(), - transfer_amount.into(), - None, - message, - ) - .deposit(1) - .max_gas() + let mut msg = U256::from(fee).as_byte_slice().to_vec(); + msg.append( + &mut validate_eth_address(RECIPIENT_ETH_ADDRESS) + .as_bytes() + .to_vec(), + ); + + let message = [CONTRACT_ACC, hex::encode(msg).as_str()].join(":"); + let memo: Option = None; + let res = user_acc + .call(contract.engine_contract.id(), "ft_transfer_call") + .args_json(json!({ + "receiver_id": contract.engine_contract.id(), + "amount": transfer_amount, + "memo": memo, + "msg": message, + })) + .gas(DEFAULT_GAS) + .deposit(ONE_YOCTO) .transact() - .await - .unwrap(); + .await?; assert!(res.is_success()); - let balance = get_eth_on_near_balance(&contract, DEPOSITED_RECIPIENT).await; - assert_eq!(balance, DEPOSITED_AMOUNT - DEPOSITED_FEE); - - let balance = get_eth_on_near_balance(&contract, CONTRACT_ACC).await; - assert_eq!(balance, DEPOSITED_FEE); - - let balance = get_eth_balance(&contract, address_from_hex(RECIPIENT_ETH_ADDRESS)).await; - assert_eq!(balance, transfer_amount - fee); - - let balance = get_eth_balance(&contract, address_from_hex(CUSTODIAN_ADDRESS)).await; - assert_eq!(balance, fee); - - let balance = total_supply(&contract).await; - assert_eq!(balance, DEPOSITED_AMOUNT); - - let balance = total_eth_supply_on_near(&contract).await; - assert_eq!(balance, DEPOSITED_AMOUNT); - - let balance = total_eth_supply_on_aurora(&contract).await; - assert_eq!(balance, transfer_amount); + assert_eq!( + contract.get_eth_on_near_balance(user_acc.id()).await?.0, + DEPOSITED_AMOUNT - transfer_amount.0, + ); + assert_eq!( + contract + .get_eth_on_near_balance(contract.engine_contract.id()) + .await? + .0, + transfer_amount.0, + ); + assert_eq!( + contract + .get_eth_balance(&validate_eth_address(RECIPIENT_ETH_ADDRESS),) + .await?, + transfer_amount.0, + ); + assert_eq!(contract.total_supply().await?, DEPOSITED_AMOUNT); + Ok(()) } #[tokio::test] -#[allow(clippy::too_many_lines)] -async fn test_ft_transfer_call_without_message() { - let contract = init(CUSTODIAN_ADDRESS).await.unwrap(); - let contract_account_id = &contract.id(); - let recipient_account = - create_sub_account(&contract.root(), "eth_recipient", parse_near!("50 N")) - .await - .unwrap(); - let result = call_deposit_eth_to_near(&contract).await.unwrap(); - assert!(result.is_success()); - - let balance = get_eth_on_near_balance(&contract, DEPOSITED_RECIPIENT).await; - assert_eq!(balance, DEPOSITED_AMOUNT - DEPOSITED_FEE); +async fn test_ft_transfer_call_without_message() -> anyhow::Result<()> { + let contract = TestContract::new().await?; + contract.call_deposit_eth_to_near().await?; - let balance = get_eth_on_near_balance(&contract, CONTRACT_ACC).await; - assert_eq!(balance, DEPOSITED_FEE); - - let res = contract - .register_relayer(address_from_hex(CUSTODIAN_ADDRESS)) - .transact() - .await - .unwrap(); - assert!(res.is_success()); - - // An attempt to send a message with wrong message format. - let res = contract - .ft_transfer_call( - &CONTRACT_ACC.parse().unwrap(), - 50.into(), - None, - String::new(), - ) - .deposit(1) - .transact() - .await - .err() - .unwrap(); - assert_error_message(&res, "ERR_INVALID_ON_TRANSFER_MESSAGE_FORMAT"); + let user_acc = contract + .create_sub_account(DEPOSITED_RECIPIENT_NAME) + .await?; + assert_eq!( + contract.get_eth_on_near_balance(user_acc.id()).await?.0, + DEPOSITED_AMOUNT, + ); + assert_eq!( + contract + .get_eth_on_near_balance(contract.engine_contract.id()) + .await? + .0, + 0, + ); + assert_eq!( + contract + .get_eth_on_near_balance(contract.eth_connector_contract.id()) + .await? + .0, + 0, + ); - // Assert balances remain unchanged - let balance = get_eth_on_near_balance(&contract, DEPOSITED_RECIPIENT).await; - assert_eq!(balance, DEPOSITED_AMOUNT - DEPOSITED_FEE); - let balance = get_eth_on_near_balance(&contract, CONTRACT_ACC).await; - assert_eq!(balance, DEPOSITED_FEE); - - // Should revert with `NotEnoughBalance` error while sending amount > balance if - // sender_id == receiver_id - let res = recipient_account - .call(contract_account_id, "ft_transfer_call") + let transfer_amount: U128 = 50.into(); + let memo: Option = None; + // Send to Aurora contract with wrong message should failed + let res = user_acc + .call(contract.engine_contract.id(), "ft_transfer_call") .args_json(json!({ - "receiver_id": recipient_account.id(), - "amount": "1000000000", + "receiver_id": contract.engine_contract.id(), + "amount": transfer_amount, + "memo": &memo, "msg": "", })) - .deposit(1) + .gas(DEFAULT_GAS) + .deposit(ONE_YOCTO) .transact() - .await - .unwrap(); - assert!(res.is_failure()); - assert_error_message(&res.into_result().err().unwrap(), "ERR_NOT_ENOUGH_BALANCE"); + .await?; + assert!(contract.check_error_message(res, "ERR_INVALID_ON_TRANSFER_MESSAGE_FORMAT")); - // Shouldn't revert with `NotEnoughBalance` error while sending amount < balance when - // sender_id == receiver_id - let res = recipient_account - .call(contract_account_id, "ft_transfer_call") - .args_json(json!({ - "receiver_id": recipient_account.id(), - "amount": "1", - "msg": "", - })) - .deposit(1) - .max_gas() - .transact() - .await - .unwrap(); - assert!(res.is_success()); + // Assert balances remain unchanged + assert_eq!( + contract.get_eth_on_near_balance(user_acc.id()).await?.0, + DEPOSITED_AMOUNT + ); + assert_eq!( + contract + .get_eth_on_near_balance(contract.engine_contract.id()) + .await? + .0, + 0 + ); + assert_eq!( + contract + .get_eth_on_near_balance(contract.eth_connector_contract.id()) + .await? + .0, + 0 + ); // Sending to random account should not change balances - let transfer_amount = 22; - let res = recipient_account - .call(contract_account_id, "ft_transfer_call") + let some_acc = AccountId::from_str("some-test-acc").unwrap(); + let res = user_acc + .call(contract.engine_contract.id(), "ft_transfer_call") .args_json(json!({ - "receiver_id": "some-test-acc", - "amount": transfer_amount.to_string(), - "msg": "", + "receiver_id": &some_acc, + "amount": transfer_amount, + "memo": &memo, + "msg": "" })) - .max_gas() - .deposit(1) + .gas(DEFAULT_GAS) + .deposit(ONE_YOCTO) .transact() - .await - .unwrap(); + .await?; assert!(res.is_success()); // some-test-acc does not implement `ft_on_transfer` therefore the call fails and the transfer is reverted. - let balance = get_eth_on_near_balance(&contract, DEPOSITED_RECIPIENT).await; - assert_eq!(balance, DEPOSITED_AMOUNT - DEPOSITED_FEE); - let balance = get_eth_on_near_balance(&contract, "some-test-acc").await; - assert_eq!(balance, 0); - let balance = get_eth_on_near_balance(&contract, CONTRACT_ACC).await; - assert_eq!(balance, DEPOSITED_FEE); + assert_eq!( + contract.get_eth_on_near_balance(user_acc.id()).await?.0, + DEPOSITED_AMOUNT + ); + assert_eq!(contract.get_eth_on_near_balance(&some_acc).await?.0, 0); + assert_eq!( + contract + .get_eth_on_near_balance(contract.engine_contract.id()) + .await? + .0, + 0 + ); + assert_eq!( + contract + .get_eth_on_near_balance(contract.eth_connector_contract.id()) + .await? + .0, + 0 + ); - // Sending to external receiver with empty message should be success - let dummy_ft = create_sub_account(&contract.root(), "ft-rec", parse_near!("50 N")) - .await - .unwrap(); - let _result = dummy_ft.deploy(&dummy_ft_receiver_bytes()).await.unwrap(); + let dummy_contract = contract + .create_sub_account("ft-rec") + .await? + .deploy(&dummy_ft_receiver_bytes()) + .await? + .into_result()?; - let res = recipient_account - .call(contract_account_id, "ft_transfer_call") + // Sending to external receiver with empty message should be success + let res = user_acc + .call(contract.engine_contract.id(), "ft_transfer_call") .args_json(json!({ - "receiver_id": dummy_ft.id(), - "amount": transfer_amount.to_string(), - "msg": "", + "receiver_id": dummy_contract.id(), + "amount": transfer_amount, + "memo": &memo, + "msg": "" })) - .max_gas() - .deposit(1) + .gas(DEFAULT_GAS) + .deposit(ONE_YOCTO) .transact() - .await - .unwrap(); + .await?; assert!(res.is_success()); - let balance = get_eth_on_near_balance(&contract, DEPOSITED_RECIPIENT).await; - assert_eq!(balance, DEPOSITED_AMOUNT - DEPOSITED_FEE - transfer_amount); - - let balance = get_eth_on_near_balance(&contract, dummy_ft.id().as_ref()).await; - assert_eq!(balance, transfer_amount); - - let balance = get_eth_on_near_balance(&contract, CONTRACT_ACC).await; - assert_eq!(balance, DEPOSITED_FEE); - - let balance = get_eth_balance(&contract, address_from_hex(RECIPIENT_ETH_ADDRESS)).await; - assert_eq!(balance, 0); - - let balance = get_eth_balance(&contract, address_from_hex(CUSTODIAN_ADDRESS)).await; - assert_eq!(balance, 0); - - let balance = total_supply(&contract).await; - assert_eq!(balance, DEPOSITED_AMOUNT); - - let balance = total_eth_supply_on_near(&contract).await; - assert_eq!(balance, DEPOSITED_AMOUNT); - - let balance = total_eth_supply_on_aurora(&contract).await; - assert_eq!(balance, 0); + assert_eq!( + contract.get_eth_on_near_balance(user_acc.id()).await?.0, + DEPOSITED_AMOUNT - transfer_amount.0 + ); + assert_eq!( + contract + .get_eth_on_near_balance(dummy_contract.id()) + .await? + .0, + transfer_amount.0 + ); + assert_eq!( + contract + .get_eth_on_near_balance(contract.engine_contract.id()) + .await? + .0, + 0 + ); + assert_eq!( + contract + .get_eth_on_near_balance(contract.eth_connector_contract.id()) + .await? + .0, + 0 + ); + assert_eq!( + contract + .get_eth_balance(&validate_eth_address(RECIPIENT_ETH_ADDRESS)) + .await?, + 0 + ); + assert_eq!(contract.total_supply().await?, DEPOSITED_AMOUNT); + Ok(()) } #[tokio::test] -async fn test_deposit_with_0x_prefix() { - use aurora_engine::deposit_event::TokenMessageData; - let contract = init(CUSTODIAN_ADDRESS).await.unwrap(); - let eth_custodian_address = address_from_hex(CUSTODIAN_ADDRESS); +async fn test_deposit_with_0x_prefix() -> anyhow::Result<()> { + let contract = TestContract::new().await?; + + let eth_custodian_address: Address = Address::decode(CUSTODIAN_ADDRESS).unwrap(); let recipient_address = Address::from_array([10u8; 20]); let deposit_amount = 17; let recipient_address_encoded = recipient_address.encode(); // Note the 0x prefix before the deposit address. let message = [CONTRACT_ACC, ":", "0x", &recipient_address_encoded].concat(); - let fee: Fee = 0.into(); + let fee: Fee = Fee::new(NEP141Wei::new(0)); let token_message_data = TokenMessageData::parse_event_message_and_prepare_token_message_data(&message, fee) .unwrap(); - let deposit_event = aurora_engine::deposit_event::DepositedEvent { + let deposit_event = DepositedEvent { eth_custodian_address, sender: Address::zero(), token_message_data, @@ -376,8 +447,8 @@ async fn test_deposit_with_0x_prefix() { }; let event_schema = ethabi::Event { - name: aurora_engine::deposit_event::DEPOSITED_EVENT.into(), - inputs: aurora_engine::deposit_event::DepositedEvent::event_params(), + name: DEPOSITED_EVENT.into(), + inputs: DepositedEvent::event_params(), anonymous: false, }; let log_entry = aurora_engine_types::parameters::connector::LogEntry { @@ -398,866 +469,831 @@ async fn test_deposit_with_0x_prefix() { // Only this field matters for the purpose of this test log_entry_data: rlp::encode(&log_entry).to_vec(), receipt_index: 1, - ..Default::default() + receipt_data: Vec::new(), + header_data: Vec::new(), + proof: Vec::new(), }; - let res = contract.deposit(proof).max_gas().transact().await.unwrap(); + let res = contract.deposit_with_proof(&proof).await?; assert!(res.is_success()); - let aurora_balance = get_eth_on_near_balance(&contract, CONTRACT_ACC).await; - assert_eq!(aurora_balance, deposit_amount); - let address_balance = get_eth_balance(&contract, recipient_address).await; - assert_eq!(address_balance, deposit_amount); -} + let balance = contract + .get_eth_on_near_balance(contract.engine_contract.id()) + .await?; + assert_eq!(balance.0, deposit_amount); -#[tokio::test] -async fn test_deposit_eth_to_near_account() { - let contract = init(CUSTODIAN_ADDRESS).await.unwrap(); - let deposit_amount = 17; - let user_account = create_sub_account(&contract.root(), "some_user", parse_near!("50 N")) - .await - .unwrap(); - let proof = generate_dummy_proof(user_account.id().as_ref(), deposit_amount, 1); - - let res = contract.deposit(proof).max_gas().transact().await.unwrap(); - assert!(res.is_success()); - - let aurora_balance = get_eth_on_near_balance(&contract, CONTRACT_ACC).await; - assert_eq!(aurora_balance, 0); - let user_account_balance = get_eth_on_near_balance(&contract, user_account.id().as_ref()).await; - assert_eq!(user_account_balance, deposit_amount); + let balance = contract.get_eth_balance(&recipient_address).await?; + assert_eq!(balance, deposit_amount); + Ok(()) } #[tokio::test] -async fn test_deposit_eth_with_empty_custom_connector_account() { - // In this, test we make an ETH deposit using the message format for targeting - // an Aurora address, but use a different NEAR account than the Aurora Engine. - // The result is that the ETH is correctly minted to the Engine, but then an - // error occurs when it tries to transfer those funds because the listed NEAR - // account does not implement `ft_on_transfer`. - let contract = init(CUSTODIAN_ADDRESS).await.unwrap(); - let deposit_amount = 17; - let user_account = create_sub_account(&contract.root(), "some_user", parse_near!("50 N")) - .await - .unwrap(); - let recipient_address = Address::from_array([10u8; 20]); - let recipient_address_encoded = recipient_address.encode(); - let message = [ - user_account.id().as_ref(), - ":", - "0x", - &recipient_address_encoded, - ] - .concat(); - let proof = generate_dummy_proof(&message, deposit_amount, 1); - let res = contract.deposit(proof).max_gas().transact().await.unwrap(); - assert!(res.is_success()); - - let outcomes = res.outcomes(); - let failure_outcome = outcomes.get(5).unwrap(); - assert!(failure_outcome.is_failure()); - assert_error_message( - &failure_outcome, - r#"FunctionCallError(CompilationError(CodeDoesNotExist { account_id: AccountId("some_user.root") }))"#, - ); - - let user_account_balance = get_eth_on_near_balance(&contract, user_account.id().as_ref()).await; - assert_eq!(user_account_balance, 0); - let aurora_balance = get_eth_on_near_balance(&contract, CONTRACT_ACC).await; - assert_eq!(aurora_balance, deposit_amount); - let address_balance = get_eth_balance(&contract, recipient_address).await; - assert_eq!(address_balance, 0); -} +async fn test_deposit_with_same_proof() -> anyhow::Result<()> { + let contract = TestContract::new().await?; + assert!(!contract.call_is_used_proof(PROOF_DATA_NEAR).await?); + contract.call_deposit_eth_to_near().await?; + assert!(contract.call_is_used_proof(PROOF_DATA_NEAR).await?); -#[tokio::test] -async fn test_deposit_eth_with_custom_connector_account() { - // In this test, we make an ETH deposit using the message format for targeting - // an Aurora address, but use a different NEAR account than the Aurora Engine. - // Additionally, the target account implements `ft_on_transfer` so that it can - // receive the ETH and perform some action with it. This is safe because the ETH is - // minted in the Engine first, then transferred to the target account using - // `ft_transfer_call`. - let contract = init(CUSTODIAN_ADDRESS).await.unwrap(); - let deposit_amount = 17; - let user_account = create_sub_account(&contract.root(), "some_user", parse_near!("50 N")) - .await - .unwrap(); - let _result = user_account - .deploy(&dummy_ft_receiver_bytes()) - .await - .unwrap(); - - let recipient_address = Address::from_array([10u8; 20]); - let recipient_address_str = recipient_address.encode(); - let message = [ - user_account.id().as_ref(), - ":", - "0x", - &recipient_address_str, - ] - .concat(); - let proof = generate_dummy_proof(&message, deposit_amount, 1); - let res = contract.deposit(proof).max_gas().transact().await.unwrap(); - assert!(res.is_success()); - let outcomes = res.outcomes(); - let outcome = outcomes - .iter() - .find(|o| o.executor_id.as_str() == user_account.id().as_ref()) - .unwrap(); - assert_eq!( - &outcome.logs[0], - "in 17 tokens from @eth_connector.root ft_on_transfer, msg = some_user.root:00000000000000\ - 000000000000000000000000000000000000000000000000000a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a" - ); - - let user_account_balance = get_eth_on_near_balance(&contract, user_account.id().as_ref()).await; - assert_eq!(user_account_balance, deposit_amount); - let aurora_balance = get_eth_on_near_balance(&contract, CONTRACT_ACC).await; - assert_eq!(aurora_balance, 0); - let address_balance = get_eth_balance(&contract, recipient_address).await; - assert_eq!(address_balance, 0); + let res = contract + .deposit_with_proof(&contract.get_proof(PROOF_DATA_NEAR)) + .await?; + assert!(res.is_failure()); + assert!(contract.check_error_message(res, "ERR_PROOF_EXIST")); + Ok(()) } #[tokio::test] -async fn test_deposit_with_same_proof() { - let contract = init(CUSTODIAN_ADDRESS).await.unwrap(); - - assert_proof_was_not_used(&contract, PROOF_DATA_NEAR).await; - - let result = call_deposit_eth_to_near(&contract).await.unwrap(); - assert!(result.is_success()); - - assert_proof_was_used(&contract, PROOF_DATA_NEAR).await; - - let result = call_deposit_eth_to_near(&contract).await; - assert!(result.is_err()); - assert_error_message(&result.err().unwrap(), "ERR_PROOF_EXIST"); +async fn test_deposit_wrong_custodian_address() -> anyhow::Result<()> { + let contract = + TestContract::new_with_custodian("0000000000000000000000000000000000000001").await?; + let res = contract + .deposit_with_proof(&contract.get_proof(PROOF_DATA_NEAR)) + .await?; + assert!(res.is_failure()); + assert!(contract.check_error_message(res, "ERR_WRONG_EVENT_ADDRESS")); + assert!(!contract.call_is_used_proof(PROOF_DATA_NEAR).await?); + Ok(()) } #[tokio::test] -async fn test_deposit_wrong_custodian_address() { - let wrong_custodian_address = "0000000000000000000000000000000000000001"; - let contract = init(wrong_custodian_address).await.unwrap(); - let error = call_deposit_eth_to_near(&contract).await.err().unwrap(); +async fn test_ft_transfer_call_without_relayer() -> anyhow::Result<()> { + let contract = TestContract::new().await?; + contract.call_deposit_eth_to_near().await?; - assert_error_message(&error, "ERR_WRONG_EVENT_ADDRESS"); - assert_proof_was_not_used(&contract, PROOF_DATA_NEAR).await; -} + let receiver_id = contract.engine_contract.id(); + let user_acc = contract + .create_sub_account(DEPOSITED_RECIPIENT_NAME) + .await?; -#[tokio::test] -async fn test_ft_transfer_call_without_relayer() { - let contract = init(CUSTODIAN_ADDRESS).await.unwrap(); - let res = call_deposit_eth_to_near(&contract).await.unwrap(); - assert!(res.is_success()); + assert_eq!(contract.get_eth_on_near_balance(receiver_id).await?.0, 0); + assert_eq!( + contract.get_eth_on_near_balance(user_acc.id()).await?.0, + DEPOSITED_AMOUNT + ); - let balance = get_eth_on_near_balance(&contract, DEPOSITED_RECIPIENT).await; - assert_eq!(balance, DEPOSITED_AMOUNT - DEPOSITED_FEE); + let transfer_amount: U128 = 50.into(); + let fee: u128 = 30; + let mut msg = U256::from(fee).as_byte_slice().to_vec(); + let recipient_address = validate_eth_address(RECIPIENT_ETH_ADDRESS); + msg.append(&mut recipient_address.as_bytes().to_vec()); - let balance = get_eth_on_near_balance(&contract, CONTRACT_ACC).await; - assert_eq!(balance, DEPOSITED_FEE); + let relayer_id = "relayer.root"; + let message = [relayer_id, hex::encode(msg).as_str()].join(":"); - let transfer_amount = 50; - let fee: u128 = 30; - let message = create_message("relayer.root", RECIPIENT_ETH_ADDRESS, fee); - let res = contract - .ft_transfer_call(&contract.id(), transfer_amount.into(), None, message) - .deposit(1) - .max_gas() + let memo: Option = None; + let res = user_acc + .call(contract.engine_contract.id(), "ft_transfer_call") + .args_json(json!({ + "receiver_id": receiver_id, + "amount": transfer_amount, + "memo": memo, + "msg": message, + })) + .gas(DEFAULT_GAS) + .deposit(ONE_YOCTO) .transact() - .await - .unwrap(); + .await?; assert!(res.is_success()); - let balance = get_eth_on_near_balance(&contract, DEPOSITED_RECIPIENT).await; - assert_eq!(balance, DEPOSITED_AMOUNT - DEPOSITED_FEE); - - let balance = get_eth_on_near_balance(&contract, CONTRACT_ACC).await; - assert_eq!(balance, DEPOSITED_FEE); - - let balance = get_eth_balance(&contract, address_from_hex(RECIPIENT_ETH_ADDRESS)).await; - assert_eq!(balance, transfer_amount); - - let balance = get_eth_balance(&contract, address_from_hex(CUSTODIAN_ADDRESS)).await; - assert_eq!(balance, 0); - - let balance = total_supply(&contract).await; - assert_eq!(balance, DEPOSITED_AMOUNT); - - let balance = total_eth_supply_on_near(&contract).await; - assert_eq!(balance, DEPOSITED_AMOUNT); - - let balance = total_eth_supply_on_aurora(&contract).await; - assert_eq!(balance, transfer_amount); + assert_eq!( + contract.get_eth_on_near_balance(user_acc.id()).await?.0, + DEPOSITED_AMOUNT - transfer_amount.0 + ); + assert_eq!( + contract.get_eth_on_near_balance(receiver_id).await?.0, + transfer_amount.0 + ); + assert_eq!( + contract.get_eth_balance(&recipient_address).await?, + transfer_amount.0 + ); + assert_eq!(contract.total_supply().await?, DEPOSITED_AMOUNT); + Ok(()) } #[tokio::test] -async fn test_ft_transfer_call_fee_greater_than_amount() { - let contract = init(CUSTODIAN_ADDRESS).await.unwrap(); - let res = call_deposit_eth_to_near(&contract).await.unwrap(); +async fn test_ft_transfer_call_fee_greater_than_amount() -> anyhow::Result<()> { + let contract = TestContract::new().await?; + contract.call_deposit_eth_to_near().await?; + + let transfer_amount: U128 = 10.into(); + let fee: u128 = 12; + let mut msg = U256::from(fee).as_byte_slice().to_vec(); + msg.append( + &mut validate_eth_address(RECIPIENT_ETH_ADDRESS) + .as_bytes() + .to_vec(), + ); + let relayer_id = "relayer.root"; + let message = [relayer_id, hex::encode(msg).as_str()].join(":"); + let memo: Option = None; + let user_acc = contract + .create_sub_account(DEPOSITED_RECIPIENT_NAME) + .await?; + let res = user_acc + .call(contract.engine_contract.id(), "ft_transfer_call") + .args_json(json!({ + "receiver_id": contract.engine_contract.id(), + "amount": transfer_amount, + "memo": memo, + "msg": message, + })) + .gas(DEFAULT_GAS) + .deposit(ONE_YOCTO) + .transact() + .await?; assert!(res.is_success()); - let transfer_amount = 10; - let fee: u128 = transfer_amount + 10; - let message = create_message("relayer.root", RECIPIENT_ETH_ADDRESS, fee); - let err = contract - .ft_transfer_call(&contract.id(), transfer_amount.into(), None, message) - .deposit(1) - .transact() - .await - .err() - .unwrap(); - assert_error_message( - &err, - "Smart contract panicked: ERR_NOT_ENOUGH_BALANCE_FOR_FEE", + assert_eq!( + contract.get_eth_on_near_balance(user_acc.id()).await?.0, + DEPOSITED_AMOUNT - transfer_amount.0 ); - - let balance = get_eth_on_near_balance(&contract, DEPOSITED_RECIPIENT).await; - assert_eq!(balance, DEPOSITED_AMOUNT - DEPOSITED_FEE); - - let balance = get_eth_on_near_balance(&contract, CONTRACT_ACC).await; - assert_eq!(balance, DEPOSITED_FEE); - - let balance = get_eth_balance(&contract, address_from_hex(RECIPIENT_ETH_ADDRESS)).await; - assert_eq!(balance, 0); - - let balance = get_eth_balance(&contract, address_from_hex(CUSTODIAN_ADDRESS)).await; - assert_eq!(balance, 0); - - let balance = total_supply(&contract).await; - assert_eq!(balance, DEPOSITED_AMOUNT); - - let balance = total_eth_supply_on_near(&contract).await; - assert_eq!(balance, DEPOSITED_AMOUNT); - - let balance = total_eth_supply_on_aurora(&contract).await; - assert_eq!(balance, 0); + assert_eq!( + contract + .get_eth_on_near_balance(contract.engine_contract.id()) + .await? + .0, + transfer_amount.0 + ); + assert_eq!( + contract + .get_eth_balance(&validate_eth_address(RECIPIENT_ETH_ADDRESS)) + .await?, + transfer_amount.0 + ); + assert_eq!(contract.total_supply().await?, DEPOSITED_AMOUNT); + Ok(()) } #[tokio::test] -async fn test_admin_controlled_only_admin_can_pause() { - let contract = init(CUSTODIAN_ADDRESS).await.unwrap(); - let user_account = create_sub_account(&contract.root(), "user", parse_near!("50 N")) - .await - .unwrap(); - - // Try to pause from the user - should fail - let res = user_account - .call(&contract.id(), "set_paused_flags") +async fn test_admin_controlled_only_admin_can_pause() -> anyhow::Result<()> { + let contract = TestContract::new().await?; + let user_acc = contract.create_sub_account("some-user").await?; + let res = user_acc + .call(contract.eth_connector_contract.id(), "set_paused_flags") .args_borsh(PAUSE_DEPOSIT) + .gas(DEFAULT_GAS) .transact() - .await - .unwrap(); + .await?; assert!(res.is_failure()); + assert!(contract.check_error_message(res, "ERR_ACCESS_RIGHT")); - // Try to pause from the admin - should succeed let res = contract - .set_paused_flags(PAUSE_DEPOSIT) + .eth_connector_contract + .call("set_paused_flags") + .args_borsh(PAUSE_DEPOSIT) + .gas(DEFAULT_GAS) .transact() - .await - .unwrap(); + .await?; assert!(res.is_success()); + Ok(()) } #[tokio::test] -async fn test_admin_controlled_admin_can_perform_actions_when_paused() { - let contract = init(CUSTODIAN_ADDRESS).await.unwrap(); - - // 1st deposit call when unpaused - should succeed - let proof: Proof = serde_json::from_str(PROOF_DATA_NEAR).unwrap(); - let res = contract.deposit(proof).max_gas().transact().await.unwrap(); - assert!(res.is_success()); - - let withdraw_amount = 100; - let recipient_addr = address_from_hex(RECIPIENT_ETH_ADDRESS); +async fn test_access_right() -> anyhow::Result<()> { + let acc_name = "some_user.root".parse().unwrap(); + let contract = TestContract::new_with_owner(acc_name).await?; + contract.call_deposit_eth_to_near().await?; + let user_acc = contract + .create_sub_account(DEPOSITED_RECIPIENT_NAME) + .await?; - // 1st withdraw call when unpaused - should succeed let res = contract - .withdraw(recipient_addr, withdraw_amount) - .deposit(1) - .transact() - .await + .eth_connector_contract + .call("get_account_with_access_right") + .view() + .await? + .json::() .unwrap(); - assert!(res.is_success()); + assert_eq!(&res, contract.engine_contract.id()); + + let withdraw_amount = NEP141Wei::new(100); + let recipient_addr = validate_eth_address(RECIPIENT_ETH_ADDRESS); + let res = user_acc + .call(contract.eth_connector_contract.id(), "engine_withdraw") + .args_borsh((user_acc.id(), recipient_addr, withdraw_amount)) + .gas(DEFAULT_GAS) + .deposit(ONE_YOCTO) + .transact() + .await?; + assert!(res.is_failure()); + assert!(contract.check_error_message(res, "ERR_ACCESS_RIGHT")); - // Pause deposit let res = contract - .set_paused_flags(PAUSE_DEPOSIT) + .eth_connector_contract + .call("set_access_right") + .args_json((user_acc.id(),)) + .gas(DEFAULT_GAS) .transact() - .await - .unwrap(); - assert!(res.is_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 proof = serde_json::from_str(PROOF_DATA_ETH).unwrap(); - let res = contract.deposit(proof).max_gas().transact().await.unwrap(); + .await?; assert!(res.is_success()); - // Pause withdraw let res = contract - .set_paused_flags(PAUSE_WITHDRAW) - .transact() - .await + .eth_connector_contract + .call("get_account_with_access_right") + .view() + .await? + .json::() .unwrap(); - assert!(res.is_success()); + assert_eq!(&res, user_acc.id()); - // 2nd withdraw call when paused, but the admin is calling it - should succeed - let res = contract - .withdraw(recipient_addr, withdraw_amount) - .deposit(1) + let res = user_acc + .call(contract.eth_connector_contract.id(), "engine_withdraw") + .args_borsh((user_acc.id(), recipient_addr, withdraw_amount)) + .gas(DEFAULT_GAS) + .deposit(ONE_YOCTO) .transact() - .await - .unwrap(); + .await?; assert!(res.is_success()); + + let data: WithdrawResult = res.borsh()?; + let custodian_addr = validate_eth_address(CUSTODIAN_ADDRESS); + assert_eq!(data.recipient_id, recipient_addr); + assert_eq!(data.amount, withdraw_amount); + assert_eq!(data.eth_custodian_address, custodian_addr); + + assert_eq!( + contract.get_eth_on_near_balance(user_acc.id()).await?.0, + DEPOSITED_AMOUNT - withdraw_amount.as_u128(), + ); + assert_eq!( + contract.total_supply().await?, + DEPOSITED_AMOUNT - withdraw_amount.as_u128(), + ); + + Ok(()) } #[tokio::test] -async fn test_deposit_pausability() { - let contract = init(CUSTODIAN_ADDRESS).await.unwrap(); - let user_account = create_sub_account(&contract.root(), "user", parse_near!("50 N")) - .await - .unwrap(); +async fn test_deposit_pausability_eth_connector() -> anyhow::Result<()> { + let acc_name = AccountId::try_from("some_user.root".to_string()).unwrap(); + let contract = TestContract::new_with_owner(acc_name).await?; + let user_acc = contract.create_sub_account("some_user").await?; - // 1st deposit call - should succeed - let proof: Proof = serde_json::from_str(PROOF_DATA_NEAR).unwrap(); - let res = user_account - .call(&contract.id(), "deposit") - .args_borsh(proof) - .max_gas() + // Pause deposit + let res = user_acc + .call(contract.eth_connector_contract.id(), "set_paused_flags") + .args_borsh(PAUSE_DEPOSIT) + .gas(DEFAULT_GAS) .transact() - .await - .unwrap(); + .await?; assert!(res.is_success()); - // Pause deposit + // Check is flag DEPOSIT_PAUSE let res = contract - .set_paused_flags(PAUSE_DEPOSIT) - .transact() - .await + .eth_connector_contract + .call("get_paused_flags") + .view() + .await? + .borsh::() .unwrap(); - assert!(res.is_success()); + assert_eq!(res, 1); - // 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 proof: Proof = serde_json::from_str(PROOF_DATA_ETH).unwrap(); - let res = user_account - .call(&contract.id(), "deposit") - .args_borsh(proof) - .max_gas() - .transact() - .await - .unwrap(); + // 2nd deposit call - should fail. + // Becasue `owner_id` check related to `predecessor_acount_id` + let res = contract + .user_deposit_with_proof(&user_acc, &contract.get_proof(PROOF_DATA_NEAR)) + .await?; assert!(res.is_failure()); + assert!(contract.check_error_message(res, "ERR_PAUSED")); - // Unpause all let res = contract - .set_paused_flags(UNPAUSE_ALL) + .engine_contract + .call("deposit") + .args_borsh(&contract.get_proof(PROOF_DATA_ETH)) + .gas(DEFAULT_GAS) .transact() - .await - .unwrap(); - assert!(res.is_success()); + .await?; + assert!(res.is_failure()); + assert!(contract.check_error_message(res, "ERR_PAUSED")); - // 3rd deposit call - should succeed - let proof: Proof = serde_json::from_str(PROOF_DATA_ETH).unwrap(); - let res = user_account - .call(&contract.id(), "deposit") - .args_borsh(proof) - .max_gas() + assert_eq!(contract.total_supply().await?, 0); + + let res = user_acc + .call(contract.eth_connector_contract.id(), "deposit") + .args_borsh(&contract.get_proof(PROOF_DATA_NEAR)) + .gas(DEFAULT_GAS) .transact() - .await - .unwrap(); + .await?; assert!(res.is_success()); + assert_eq!(contract.total_supply().await?, DEPOSITED_AMOUNT); + + Ok(()) } #[tokio::test] -async fn test_withdraw_from_near_pausability() { - let contract = init(CUSTODIAN_ADDRESS).await.unwrap(); - let user_account = create_sub_account(&contract.root(), "eth_recipient", parse_near!("50 N")) - .await - .unwrap(); - let res = call_deposit_eth_to_near(&contract).await.unwrap(); - assert!(res.is_success()); +async fn test_deposit_pausability() -> anyhow::Result<()> { + let acc_name = AccountId::try_from("some_user.root".to_string()).unwrap(); + let contract = TestContract::new_with_owner(acc_name).await?; + let user_acc = contract.create_sub_account("some_user").await?; - let withdraw_args = WithdrawCallArgs { - recipient_address: address_from_hex(RECIPIENT_ETH_ADDRESS), - amount: NEP141Wei::new(10), - }; - // 1st withdraw - should succeed - let res = user_account - .call(&contract.id(), "withdraw") - .args_borsh(withdraw_args.clone()) - .deposit(1) + // Pause deposit + let res = user_acc + .call(contract.eth_connector_contract.id(), "set_paused_flags") + .args_borsh(PAUSE_DEPOSIT) + .gas(DEFAULT_GAS) .transact() - .await - .unwrap(); + .await?; assert!(res.is_success()); - // Pause withdraw + // Check is flag DEPOSIT_PAUSE let res = contract - .set_paused_flags(PAUSE_WITHDRAW) - .transact() - .await + .eth_connector_contract + .call("get_paused_flags") + .view() + .await? + .borsh::() .unwrap(); - assert!(res.is_success()); + assert_eq!(res, 1); - // 2nd withdraw - should fail - let res = user_account - .call(&contract.id(), "withdraw") - .args_borsh(withdraw_args.clone()) - .deposit(1) + // 2nd deposit call - should fail. + // Becasue `owner_id` check related to `predecessor_acount_id` + let res = contract + .user_deposit_with_proof(&user_acc, &contract.get_proof(PROOF_DATA_NEAR)) + .await?; + assert!(res.is_failure()); + assert!(contract.check_error_message(res, "ERR_PAUSED")); + + let res = contract + .engine_contract + .call("deposit") + .args_borsh(&contract.get_proof(PROOF_DATA_ETH)) + .gas(DEFAULT_GAS) .transact() - .await - .unwrap(); + .await?; assert!(res.is_failure()); - assert_error_message(&res.into_result().err().unwrap(), "ERR_PAUSED"); + assert!(contract.check_error_message(res, "ERR_PAUSED")); // Unpause all let res = contract - .set_paused_flags(UNPAUSE_ALL) + .eth_connector_contract + .call("set_paused_flags") + .args_borsh(UNPAUSE_ALL) + .gas(DEFAULT_GAS) .transact() - .await - .unwrap(); + .await?; assert!(res.is_success()); - let res = user_account - .call(&contract.id(), "withdraw") - .args_borsh(withdraw_args) - .deposit(1) + // 3rd deposit call - should succeed + let res = contract + .engine_contract + .call("deposit") + .args_borsh(&contract.get_proof(PROOF_DATA_ETH)) + .gas(DEFAULT_GAS) .transact() - .await - .unwrap(); + .await?; assert!(res.is_success()); -} -#[tokio::test] -async fn test_get_accounts_counter() { - let contract = init(CUSTODIAN_ADDRESS).await.unwrap(); - let res = call_deposit_eth_to_near(&contract).await.unwrap(); + let res = contract + .user_deposit_with_proof(&user_acc, &contract.get_proof(PROOF_DATA_NEAR)) + .await?; assert!(res.is_success()); - let counter = contract.get_accounts_counter().await.unwrap(); - assert_eq!(counter.result, 2); + assert_eq!( + contract + .get_eth_balance(&validate_eth_address(RECIPIENT_ETH_ADDRESS)) + .await?, + DEPOSITED_EVM_AMOUNT + ); + assert_eq!( + contract.total_supply().await?, + DEPOSITED_AMOUNT + DEPOSITED_EVM_AMOUNT + ); + Ok(()) } #[tokio::test] -async fn test_get_accounts_counter_and_transfer() { - let contract = init(CUSTODIAN_ADDRESS).await.unwrap(); - let res = call_deposit_eth_to_near(&contract).await.unwrap(); +async fn test_withdraw_from_near_pausability() -> anyhow::Result<()> { + let acc_name = AccountId::try_from(DEPOSITED_RECIPIENT.to_string()).unwrap(); + let contract = TestContract::new_with_owner(acc_name).await?; + let user_acc = contract + .create_sub_account(DEPOSITED_RECIPIENT_NAME) + .await?; + + contract.call_deposit_eth_to_near().await?; + + let recipient_addr = validate_eth_address(RECIPIENT_ETH_ADDRESS); + let withdraw_amount = NEP141Wei::new(100); + // 1st withdraw - should succeed + let res = user_acc + .call(contract.engine_contract.id(), "withdraw") + .args_borsh((recipient_addr, withdraw_amount)) + .gas(DEFAULT_GAS) + .deposit(ONE_YOCTO) + .transact() + .await?; assert!(res.is_success()); - let counter = contract.get_accounts_counter().await.unwrap(); - assert_eq!(counter.result, 2); + let data: WithdrawResult = res.borsh()?; + let custodian_addr = validate_eth_address(CUSTODIAN_ADDRESS); + assert_eq!(data.recipient_id, recipient_addr); + assert_eq!(data.amount, withdraw_amount); + assert_eq!(data.eth_custodian_address, custodian_addr); - let transfer_amount = 70; + // Pause withdraw let res = contract - .ft_transfer( - &DEPOSITED_RECIPIENT.parse().unwrap(), - transfer_amount.into(), - Some("transfer memo".to_string()), - ) - .deposit(1) + .eth_connector_contract + .call("set_paused_flags") + .args_borsh(PAUSE_WITHDRAW) + .gas(DEFAULT_GAS) .transact() - .await - .unwrap(); + .await?; assert!(res.is_success()); - let balance = get_eth_on_near_balance(&contract, DEPOSITED_RECIPIENT).await; - assert_eq!(balance, DEPOSITED_AMOUNT - DEPOSITED_FEE + transfer_amount); + // 2nd withdraw - should be failed + let res = user_acc + .call(contract.engine_contract.id(), "withdraw") + .args_borsh((recipient_addr, withdraw_amount)) + .gas(DEFAULT_GAS) + .deposit(ONE_YOCTO) + .transact() + .await?; + assert!(res.is_failure()); + assert!(contract.check_error_message(res, "WithdrawErrorPaused")); + + // Direct call to eth-connector from owner should be success + let res = user_acc + .call(contract.eth_connector_contract.id(), "withdraw") + .args_borsh((recipient_addr, withdraw_amount)) + .gas(DEFAULT_GAS) + .deposit(ONE_YOCTO) + .transact() + .await?; + assert!(res.is_success()); - let balance = get_eth_on_near_balance(&contract, CONTRACT_ACC).await; - assert_eq!(balance, DEPOSITED_FEE - transfer_amount); + let data: WithdrawResult = res.borsh()?; + let custodian_addr = validate_eth_address(CUSTODIAN_ADDRESS); + assert_eq!(data.recipient_id, recipient_addr); + assert_eq!(data.amount, withdraw_amount); + assert_eq!(data.eth_custodian_address, custodian_addr); - let balance = total_supply(&contract).await; - assert_eq!(balance, DEPOSITED_AMOUNT); + // Unpause all + let res = contract + .eth_connector_contract + .call("set_paused_flags") + .args_borsh(UNPAUSE_ALL) + .gas(DEFAULT_GAS) + .transact() + .await?; + assert!(res.is_success()); - let balance = total_eth_supply_on_aurora(&contract).await; - assert_eq!(balance, 0); + let res = user_acc + .call(contract.engine_contract.id(), "withdraw") + .args_borsh((recipient_addr, withdraw_amount)) + .gas(DEFAULT_GAS) + .deposit(ONE_YOCTO) + .transact() + .await?; + assert!(res.is_success()); - let balance = total_eth_supply_on_near(&contract).await; - assert_eq!(balance, DEPOSITED_AMOUNT); + let data: WithdrawResult = res.borsh()?; + let custodian_addr = validate_eth_address(CUSTODIAN_ADDRESS); + assert_eq!(data.recipient_id, recipient_addr); + assert_eq!(data.amount, withdraw_amount); + assert_eq!(data.eth_custodian_address, custodian_addr); - let counter = contract.get_accounts_counter().await.unwrap(); - assert_eq!(counter.result, 2); + assert_eq!( + contract.total_supply().await?, + DEPOSITED_AMOUNT - 3 * withdraw_amount.as_u128() + ); + Ok(()) } - #[tokio::test] -async fn test_deposit_to_near_with_zero_fee() { - let contract = init(CUSTODIAN_ADDRESS).await.unwrap(); +async fn test_deposit_to_near_with_zero_fee() -> anyhow::Result<()> { let proof_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,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 = serde_json::from_str(proof_str).unwrap(); - let res = contract.deposit(proof).max_gas().transact().await.unwrap(); + let contract = TestContract::new().await?; + let res = contract + .deposit_with_proof(&contract.get_proof(proof_str)) + .await?; assert!(res.is_success()); - - assert_proof_was_used(&contract, proof_str).await; + assert!(contract.call_is_used_proof(proof_str).await?); let deposited_amount = 3000; + let receiver_id = AccountId::from_str(DEPOSITED_RECIPIENT).unwrap(); - let balance = get_eth_on_near_balance(&contract, DEPOSITED_RECIPIENT).await; - assert_eq!(balance, deposited_amount); - - let balance = get_eth_on_near_balance(&contract, CONTRACT_ACC).await; - assert_eq!(balance, 0); - - let balance = total_supply(&contract).await; - assert_eq!(balance, deposited_amount); - - let balance = total_eth_supply_on_near(&contract).await; - assert_eq!(balance, deposited_amount); + assert_eq!( + contract.get_eth_on_near_balance(&receiver_id).await?.0, + deposited_amount + ); + assert_eq!( + contract + .get_eth_on_near_balance(contract.engine_contract.id()) + .await? + .0, + 0 + ); - let balance = total_eth_supply_on_aurora(&contract).await; - assert_eq!(balance, 0); + assert_eq!(contract.total_supply().await?, deposited_amount); + Ok(()) } #[tokio::test] -async fn test_deposit_to_aurora_with_zero_fee() { - let contract = init(EVM_CUSTODIAN_ADDRESS).await.unwrap(); - let res = contract - .register_relayer(address_from_hex(CUSTODIAN_ADDRESS)) - .transact() - .await - .unwrap(); - assert!(res.is_success()); - +async fn test_deposit_to_aurora_with_zero_fee() -> anyhow::Result<()> { let proof_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,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 = serde_json::from_str(proof_str).unwrap(); - let res = contract.deposit(proof).max_gas().transact().await.unwrap(); + let contract = TestContract::new().await?; + let res = contract + .deposit_with_proof(&contract.get_proof(proof_str)) + .await?; assert!(res.is_success()); - - assert_proof_was_used(&contract, proof_str).await; + assert!(contract.call_is_used_proof(proof_str).await?); let deposited_amount = 2000; - let balance = get_eth_balance(&contract, address_from_hex(RECIPIENT_ETH_ADDRESS)).await; - assert_eq!(balance, deposited_amount); - - let balance = get_eth_balance(&contract, address_from_hex(CUSTODIAN_ADDRESS)).await; - assert_eq!(balance, 0); - - let balance = total_supply(&contract).await; - assert_eq!(balance, deposited_amount); - - let balance = total_eth_supply_on_aurora(&contract).await; - assert_eq!(balance, deposited_amount); - - let balance = total_eth_supply_on_near(&contract).await; - assert_eq!(balance, deposited_amount); + assert_eq!( + contract + .get_eth_balance(&validate_eth_address(RECIPIENT_ETH_ADDRESS)) + .await?, + deposited_amount + ); + assert_eq!(contract.total_supply().await?, deposited_amount); + Ok(()) } #[tokio::test] -async fn test_deposit_to_near_amount_less_fee() { - let custodian_address = "73c8931CA2aD746d97a59A7ABDDa0a9205F7ffF9"; - let contract = init(custodian_address).await.unwrap(); +async fn test_deposit_to_near_amount_less_fee() -> anyhow::Result<()> { + let contract = + TestContract::new_with_custodian("73c8931CA2aD746d97a59A7ABDDa0a9205F7ffF9").await?; let proof_str = 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 = serde_json::from_str(proof_str).unwrap(); - let res = contract.deposit(proof).transact().await.err().unwrap(); - - assert_error_message( - &res, - "Smart contract panicked: ERR_NOT_ENOUGH_BALANCE_FOR_FEE", - ); - - assert_proof_was_not_used(&contract, proof_str).await; + let res = contract + .deposit_with_proof(&contract.get_proof(proof_str)) + .await?; + assert!(res.is_success()); + assert!(contract.call_is_used_proof(proof_str).await?); + Ok(()) } #[tokio::test] -async fn test_deposit_to_aurora_amount_less_fee() { - let custodian_address = "73c8931CA2aD746d97a59A7ABDDa0a9205F7ffF9"; - let contract = init(custodian_address).await.unwrap(); +async fn test_deposit_to_aurora_amount_less_fee() -> anyhow::Result<()> { + let contract = + TestContract::new_with_custodian("73c8931CA2aD746d97a59A7ABDDa0a9205F7ffF9").await?; let proof_str = 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 = serde_json::from_str(proof_str).unwrap(); - let res = contract.deposit(proof).transact().await.err().unwrap(); - - assert_error_message( - &res, - "Smart contract panicked: ERR_NOT_ENOUGH_BALANCE_FOR_FEE", - ); - - assert_proof_was_not_used(&contract, proof_str).await; + let res = contract + .deposit_with_proof(&contract.get_proof(proof_str)) + .await?; + assert!(res.is_success()); + assert!(contract.call_is_used_proof(proof_str).await?); + Ok(()) } #[tokio::test] -async fn test_deposit_to_near_amount_zero_fee_non_zero() { - let custodian_address = "73c8931CA2aD746d97a59A7ABDDa0a9205F7ffF9"; - let contract = init(custodian_address).await.unwrap(); +async fn test_deposit_to_near_amount_zero_fee_non_zero() -> anyhow::Result<()> { + let contract = + TestContract::new_with_custodian("73c8931CA2aD746d97a59A7ABDDa0a9205F7ffF9").await?; let proof_str = 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 = serde_json::from_str(proof_str).unwrap(); - let res = contract.deposit(proof).transact().await.err().unwrap(); - - assert_error_message( - &res, - "Smart contract panicked: ERR_NOT_ENOUGH_BALANCE_FOR_FEE", - ); - - assert_proof_was_not_used(&contract, proof_str).await; + let res = contract + .deposit_with_proof(&contract.get_proof(proof_str)) + .await?; + assert!(res.is_success()); + assert!(contract.call_is_used_proof(proof_str).await?); + Ok(()) } #[tokio::test] -async fn test_deposit_to_aurora_amount_zero_fee_non_zero() { - let custodian_address = "73c8931CA2aD746d97a59A7ABDDa0a9205F7ffF9"; - let contract = init(custodian_address).await.unwrap(); +async fn test_deposit_to_aurora_amount_zero_fee_non_zero() -> anyhow::Result<()> { + let contract = + TestContract::new_with_custodian("73c8931CA2aD746d97a59A7ABDDa0a9205F7ffF9").await?; let proof_str = 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 = serde_json::from_str(proof_str).unwrap(); - let res = contract.deposit(proof).transact().await.err().unwrap(); - - assert_error_message( - &res, - "Smart contract panicked: ERR_NOT_ENOUGH_BALANCE_FOR_FEE", - ); - - assert_proof_was_not_used(&contract, proof_str).await; + let res = contract + .deposit_with_proof(&contract.get_proof(proof_str)) + .await?; + assert!(contract.check_error_message(res, "The amount should be a positive numbe")); + assert!(!contract.call_is_used_proof(proof_str).await?); + Ok(()) } #[tokio::test] -async fn test_deposit_to_near_amount_equal_fee_non_zero() { - let custodian_address = "73c8931CA2aD746d97a59A7ABDDa0a9205F7ffF9"; - let contract = init(custodian_address).await.unwrap(); +async fn test_deposit_to_near_amount_equal_fee_non_zero() -> anyhow::Result<()> { + let contract = + TestContract::new_with_custodian("73c8931CA2aD746d97a59A7ABDDa0a9205F7ffF9").await?; let proof_str = 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 = serde_json::from_str(proof_str).unwrap(); - let res = contract.deposit(proof).transact().await.err().unwrap(); - - assert_error_message( - &res, - "Smart contract panicked: ERR_NOT_ENOUGH_BALANCE_FOR_FEE", - ); - - assert_proof_was_not_used(&contract, proof_str).await; + let res = contract + .deposit_with_proof(&contract.get_proof(proof_str)) + .await?; + assert!(res.is_success()); + assert!(contract.call_is_used_proof(proof_str).await?); + Ok(()) } #[tokio::test] -async fn test_deposit_to_aurora_amount_equal_fee_non_zero() { - let custodian_address = "73c8931CA2aD746d97a59A7ABDDa0a9205F7ffF9"; - let contract = init(custodian_address).await.unwrap(); +async fn test_deposit_to_aurora_amount_equal_fee_non_zero() -> anyhow::Result<()> { + let contract = + TestContract::new_with_custodian("73c8931CA2aD746d97a59A7ABDDa0a9205F7ffF9").await?; let proof_str = 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 = serde_json::from_str(proof_str).unwrap(); - let res = contract.deposit(proof).transact().await.err().unwrap(); - - assert_error_message( - &res, - "Smart contract panicked: ERR_NOT_ENOUGH_BALANCE_FOR_FEE", - ); - - assert_proof_was_not_used(&contract, proof_str).await; + let res = contract + .deposit_with_proof(&contract.get_proof(proof_str)) + .await?; + assert!(res.is_success()); + assert!(contract.call_is_used_proof(proof_str).await?); + Ok(()) } #[tokio::test] -async fn test_ft_transfer_max_value() { - let contract = init(CUSTODIAN_ADDRESS).await.unwrap(); - let result = call_deposit_eth_to_near(&contract).await.unwrap(); - assert!(result.is_success()); - - let res = contract - .root() - .call(&contract.id(), "ft_transfer") +async fn test_ft_transfer_max_value() -> anyhow::Result<()> { + let contract = TestContract::new().await?; + contract.call_deposit_eth_to_near().await?; + + let transfer_amount: U128 = u128::MAX.into(); + let receiver_id = contract.engine_contract.id(); + let user_acc = contract + .create_sub_account(DEPOSITED_RECIPIENT_NAME) + .await?; + let res = user_acc + .call(contract.engine_contract.id(), "ft_transfer") .args_json(json!({ - "receiver_id": DEPOSITED_RECIPIENT, - "amount": u128::MAX.to_string(), + "receiver_id": &receiver_id, + "amount": transfer_amount, "memo": "transfer memo" })) - .deposit(1) + .gas(DEFAULT_GAS) + .deposit(ONE_YOCTO) .transact() - .await - .unwrap(); + .await?; assert!(res.is_failure()); - assert_error_message( - &res.into_result().err().unwrap(), - "Smart contract panicked: ERR_NOT_ENOUGH_BALANCE", - ); + assert!(contract.check_error_message(res, "The account doesn't have enough balance")); + Ok(()) } #[tokio::test] -async fn test_ft_transfer_empty_value() { - let contract = init(CUSTODIAN_ADDRESS).await.unwrap(); - let result = call_deposit_eth_to_near(&contract).await.unwrap(); - assert!(result.is_success()); - - let res = contract - .root() - .call(&contract.id(), "ft_transfer") +async fn test_ft_transfer_empty_value() -> anyhow::Result<()> { + let contract = TestContract::new().await?; + contract.call_deposit_eth_to_near().await?; + + let transfer_amount = ""; + let receiver_id = contract.engine_contract.id(); + let user_acc = contract + .create_sub_account(DEPOSITED_RECIPIENT_NAME) + .await?; + let res = user_acc + .call(contract.engine_contract.id(), "ft_transfer") .args_json(json!({ - "receiver_id": DEPOSITED_RECIPIENT, - "amount": "", + "receiver_id": &receiver_id, + "amount": transfer_amount, "memo": "transfer memo" })) - .deposit(1) + .gas(DEFAULT_GAS) + .deposit(ONE_YOCTO) .transact() - .await - .unwrap(); + .await?; assert!(res.is_failure()); - assert_error_message( - &res.into_result().err().unwrap(), - "Smart contract panicked: cannot parse integer from empty string", - ); + assert!(contract.check_error_message(res, "cannot parse integer from empty string")); + Ok(()) } #[tokio::test] -async fn test_ft_transfer_wrong_u128_json_type() { - let contract = init(CUSTODIAN_ADDRESS).await.unwrap(); - let res = call_deposit_eth_to_near(&contract).await.unwrap(); - assert!(res.is_success()); +async fn test_ft_transfer_wrong_u128_json_type() -> anyhow::Result<()> { + let contract = TestContract::new().await?; + contract.call_deposit_eth_to_near().await?; + let transfer_amount = 200; + let receiver_id = AccountId::from_str(DEPOSITED_RECIPIENT).unwrap(); let res = contract - .root() - .call(&contract.id().as_ref().parse().unwrap(), "ft_transfer") + .engine_contract + .call("ft_transfer") .args_json(json!({ - "receiver_id": DEPOSITED_RECIPIENT, - "amount": 200, + "receiver_id": &receiver_id, + "amount": transfer_amount, "memo": "transfer memo" })) - .deposit(1) + .gas(DEFAULT_GAS) + .deposit(ONE_YOCTO) .transact() - .await - .unwrap(); + .await?; assert!(res.is_failure()); - assert_error_message( - &res.into_result().err().unwrap(), - "Smart contract panicked: Wait for a string but got: 200 at line 1 column 13", - ); + assert!(contract.check_error_message(res, "Wait for a string")); + Ok(()) } -/// Bytes for a NEAR smart contract implementing `ft_on_transfer` -fn dummy_ft_receiver_bytes() -> Vec { - let base_path = std::path::Path::new("../etc") - .join("tests") - .join("ft-receiver"); - let output_path = base_path.join("target/wasm32-unknown-unknown/release/ft_receiver.wasm"); - crate::utils::rust::compile(base_path); - std::fs::read(output_path).unwrap() -} - -async fn init(custodian_address: &str) -> anyhow::Result { - EngineContractBuilder::new()? - .with_code(crate::utils::AuroraRunner::default().code.code().to_vec()) - .with_owner_id(CONTRACT_ACC)? - .with_prover_id(PROVER_ACCOUNT)? - .with_custodian_address(custodian_address)? - .deploy_and_init() - .await -} - -async fn call_deposit_eth_to_near(contract: &EngineContract) -> anyhow::Result { - let proof = serde_json::from_str(PROOF_DATA_NEAR).unwrap(); - let res = contract.deposit(proof).max_gas().transact().await?; - Ok(res.outcome().clone()) -} - -async fn call_is_used_proof(contract: &EngineContract, proof: &str) -> bool { - let proof = serde_json::from_str(proof).unwrap(); - contract.is_used_proof(proof).await.unwrap().result -} +#[tokio::test] +async fn test_ft_transfer_user() -> anyhow::Result<()> { + let contract = TestContract::new().await?; + contract.call_deposit_eth_to_near().await?; + + let transfer_amount: U128 = 70.into(); + let user_acc = contract + .create_sub_account(DEPOSITED_RECIPIENT_NAME) + .await?; + let receiver_id = contract.create_sub_account("some-acc").await?; + let res = user_acc + .call(contract.engine_contract.id(), "ft_transfer") + .args_json(json!({ + "receiver_id": &receiver_id.id(), + "amount": transfer_amount, + "memo": "transfer memo" + })) + .gas(DEFAULT_GAS) + .deposit(ONE_YOCTO) + .transact() + .await?; + assert!(res.is_success()); -async fn assert_proof_was_used(contract: &EngineContract, proof: &str) { - let is_used_proof = call_is_used_proof(contract, proof).await; - assert!( - is_used_proof, - "{}", - "Expected not to fail because the proof should have been already used", + assert_eq!( + contract.get_eth_on_near_balance(user_acc.id()).await?.0, + DEPOSITED_AMOUNT - transfer_amount.0, ); -} - -async fn assert_proof_was_not_used(contract: &EngineContract, proof: &str) { - let is_used_proof = call_is_used_proof(contract, proof).await; - assert!( - !is_used_proof, - "{}", - "Expected not to fail and to have an unused proof but it was already used", + assert_eq!( + contract.get_eth_on_near_balance(receiver_id.id()).await?.0, + transfer_amount.0, ); -} + assert_eq!(DEPOSITED_AMOUNT, contract.total_supply().await?); -async fn call_deposit_eth_to_aurora(contract: &EngineContract) { - let proof = serde_json::from_str(PROOF_DATA_ETH).unwrap(); - let res = contract.deposit(proof).max_gas().transact().await.unwrap(); + let transfer_amount2: U128 = 1000.into(); + let res = user_acc + .call(contract.engine_contract.id(), "ft_transfer") + .args_json(json!({ + "receiver_id": &receiver_id.id(), + "amount": transfer_amount2, + "memo": "transfer memo" + })) + .gas(DEFAULT_GAS) + .deposit(ONE_YOCTO) + .transact() + .await?; assert!(res.is_success()); + assert_eq!( + contract.get_eth_on_near_balance(receiver_id.id()).await?.0, + transfer_amount.0 + transfer_amount2.0, + ); + assert_eq!( + contract.get_eth_on_near_balance(user_acc.id()).await?.0, + DEPOSITED_AMOUNT - transfer_amount.0 - transfer_amount2.0, + ); + Ok(()) } -async fn get_eth_on_near_balance(contract: &EngineContract, account: &str) -> u128 { - contract - .ft_balance_of(&account.parse().unwrap()) - .await - .unwrap() - .result - .0 -} - -async fn get_eth_balance(contract: &EngineContract, address: Address) -> u128 { - contract.ft_balance_of_eth(address).await.unwrap().result.0 -} - -async fn total_supply(contract: &EngineContract) -> u128 { - contract.ft_total_supply().await.unwrap().result.0 -} - -async fn total_eth_supply_on_near(contract: &EngineContract) -> u128 { - contract - .ft_total_eth_supply_on_near() - .await - .unwrap() - .result - .0 -} - -async fn total_eth_supply_on_aurora(contract: &EngineContract) -> u128 { - contract - .ft_total_eth_supply_on_aurora() - .await - .unwrap() - .result - .0 -} +#[tokio::test] +async fn test_withdraw_from_user() -> anyhow::Result<()> { + let contract = TestContract::new().await?; + contract.call_deposit_eth_to_near().await?; + let user_acc = contract + .create_sub_account(DEPOSITED_RECIPIENT_NAME) + .await?; + + let withdraw_amount = NEP141Wei::new(130); + let recipient_addr = validate_eth_address(RECIPIENT_ETH_ADDRESS); + let res = user_acc + .call(contract.engine_contract.id(), "withdraw") + .args_borsh((recipient_addr, withdraw_amount)) + .gas(DEFAULT_GAS) + .deposit(ONE_YOCTO) + .transact() + .await?; + assert!(res.is_success()); -fn assert_error_message(err: &T, expected: &str) { - let err_msg = format!("{err:?}"); - assert!(err_msg.contains(expected)); + let data: WithdrawResult = res.borsh()?; + let custodian_addr = validate_eth_address(CUSTODIAN_ADDRESS); + assert_eq!(data.recipient_id, recipient_addr); + assert_eq!(data.amount, withdraw_amount); + assert_eq!(data.eth_custodian_address, custodian_addr); + assert_eq!( + contract.get_eth_on_near_balance(user_acc.id()).await?.0, + DEPOSITED_AMOUNT - withdraw_amount.as_u128() + ); + assert_eq!( + contract.total_supply().await?, + DEPOSITED_AMOUNT - withdraw_amount.as_u128(), + ); + Ok(()) } -fn generate_dummy_proof(message: &str, deposit_amount: u128, log_index: u64) -> Proof { - use aurora_engine::deposit_event::TokenMessageData; - - let eth_custodian_address = address_from_hex(CUSTODIAN_ADDRESS); - let fee: Fee = 0.into(); - let token_message_data = - TokenMessageData::parse_event_message_and_prepare_token_message_data(message, fee).unwrap(); - - let deposit_event = aurora_engine::deposit_event::DepositedEvent { - eth_custodian_address, - sender: Address::zero(), - token_message_data, - amount: NEP141Wei::new(deposit_amount), - fee, - }; - - let event_schema = ethabi::Event { - name: aurora_engine::deposit_event::DEPOSITED_EVENT.into(), - inputs: aurora_engine::deposit_event::DepositedEvent::event_params(), - anonymous: false, - }; - let log_entry = aurora_engine_types::parameters::connector::LogEntry { - address: eth_custodian_address.raw(), - topics: vec![ - event_schema.signature(), - // the sender is not important - H256::zero(), - ], - data: ethabi::encode(&[ - ethabi::Token::String(message.to_string()), - ethabi::Token::Uint(U256::from(deposit_event.amount.as_u128())), - ethabi::Token::Uint(U256::from(deposit_event.fee.as_u128())), - ]), - }; - - Proof { - log_index, - // Only this field matters for the purpose of this test - log_entry_data: rlp::encode(&log_entry).to_vec(), - receipt_index: 1, - ..Default::default() +#[tokio::test] +async fn test_ft_metadata() -> anyhow::Result<()> { + use aurora_engine_types::parameters::connector::FungibleTokenMetadata as ft_m; + use serde::Deserialize; + + #[derive(Debug, Deserialize)] + pub struct FungibleTokenMetadata { + pub spec: String, + pub name: String, + pub symbol: String, + pub icon: Option, + pub reference: Option, + pub reference_hash: Option<[u8; 32]>, + pub decimals: u8, } -} -fn create_message(account_id: &str, address: &str, fee: u128) -> String { - let mut buffer = [0; 32]; - U256::from(fee).to_little_endian(&mut buffer); - let msg = [&buffer, address_from_hex(address).as_bytes()].concat(); + let contract = TestContract::new().await?; + contract.call_deposit_eth_to_near().await?; - [account_id, hex::encode(msg).as_str()].join(":") + let metadata = contract + .engine_contract + .call("ft_metadata") + .gas(DEFAULT_GAS) + .transact() + .await? + .into_result() + .unwrap() + .json::() + .unwrap(); + let m = ft_m::default(); + let reference_hash = m.reference_hash.map(|h| { + let x: [u8; 32] = h.as_ref().try_into().unwrap(); + x + }); + assert_eq!(metadata.spec, m.spec); + assert_eq!(metadata.decimals, m.decimals); + assert_eq!(metadata.icon, m.icon); + assert_eq!(metadata.name, m.name); + assert_eq!(metadata.reference, m.reference); + assert_eq!(metadata.reference_hash, reference_hash); + assert_eq!(metadata.symbol, m.symbol); + Ok(()) } diff --git a/engine-tests-connector/src/lib.rs b/engine-tests-connector/src/lib.rs new file mode 100644 index 000000000..6c95c980d --- /dev/null +++ b/engine-tests-connector/src/lib.rs @@ -0,0 +1,6 @@ +#[cfg(test)] +mod connector; +#[cfg(test)] +pub mod rust; +#[cfg(test)] +pub mod utils; diff --git a/engine-tests-connector/src/rust.rs b/engine-tests-connector/src/rust.rs new file mode 100644 index 000000000..1e0a0c85c --- /dev/null +++ b/engine-tests-connector/src/rust.rs @@ -0,0 +1,15 @@ +use std::path::Path; +use std::process::Command; + +pub fn compile>(source_path: P) { + let output = Command::new("cargo") + .current_dir(source_path) + .env("RUSTFLAGS", "-C link-arg=-s") + .args(["build", "--target", "wasm32-unknown-unknown", "--release"]) + .output() + .unwrap(); + + if !output.status.success() { + panic!("{}", String::from_utf8(output.stderr).unwrap()); + } +} diff --git a/engine-tests-connector/src/utils.rs b/engine-tests-connector/src/utils.rs new file mode 100644 index 000000000..881537027 --- /dev/null +++ b/engine-tests-connector/src/utils.rs @@ -0,0 +1,354 @@ +use aurora_engine::parameters::{FungibleTokenMetadata, SetEthConnectorContractAccountArgs}; +use aurora_engine::proof::Proof; +use aurora_engine_types::borsh::{self, BorshDeserialize, BorshSerialize}; +use aurora_engine_types::parameters::connector::WithdrawSerializeType; +use aurora_engine_types::types::{Address, Wei}; +use near_sdk::serde_json::json; +use near_sdk::{json_types::U128, serde_json}; +use std::path::Path; +use workspaces::network::NetworkClient; +use workspaces::{result::ExecutionFinalResult, Account, AccountId, Contract, Worker}; + +pub const PROOF_DATA_NEAR: &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]]}"#; +pub const DEPOSITED_RECIPIENT: &str = "eth_recipient.root"; +pub const DEPOSITED_RECIPIENT_NAME: &str = "eth_recipient"; +pub const CUSTODIAN_ADDRESS: &str = "096DE9C2B8A5B8c22cEe3289B101f6960d68E51E"; +pub const DEFAULT_GAS: u64 = 300_000_000_000_000; +pub const DEPOSITED_AMOUNT: u128 = 800400; +pub const DEPOSITED_FEE: u128 = 400; +pub const RECIPIENT_ETH_ADDRESS: &str = "891b2749238b27ff58e951088e55b04de71dc374"; +pub const PROOF_DATA_ETH: &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]]}"#; +pub const DEPOSITED_EVM_FEE: u128 = 200; +pub const DEPOSITED_EVM_AMOUNT: u128 = 10200; +pub const CONTRACT_ACC: &str = "eth_connector.root"; + +pub type PausedMask = u8; + +/// Admin control flow flag indicates that all control flow unpause (unblocked). +pub const UNPAUSE_ALL: PausedMask = 0; +/// Admin control flow flag indicates that the deposit is paused. +pub const PAUSE_DEPOSIT: PausedMask = 1 << 0; +/// Admin control flow flag indicates that withdrawal is paused. +pub const PAUSE_WITHDRAW: PausedMask = 1 << 1; + +pub struct TestContract { + pub engine_contract: Contract, + pub eth_connector_contract: Contract, + pub root_account: Account, +} + +impl TestContract { + pub async fn deploy_aurora_contract() -> anyhow::Result<(Contract, Contract, Account)> { + use workspaces::{ + types::{KeyType, SecretKey}, + AccessKey, + }; + let worker = workspaces::sandbox() + .await + .map_err(|err| anyhow::anyhow!("Failed init sandbox: {:?}", err))?; + let testnet = workspaces::testnet() + .await + .map_err(|err| anyhow::anyhow!("Failed init testnet: {:?}", err))?; + let registrar: AccountId = "registrar".parse()?; + let sk = SecretKey::from_seed(KeyType::ED25519, registrar.as_str()); + let registrar = worker + .import_contract(®istrar, &testnet) + .transact() + .await?; + Self::waiting_account_creation(&worker, registrar.id()).await?; + + let root: AccountId = "root".parse()?; + registrar + .as_account() + .batch(&root) + .create_account() + .add_key(sk.public_key(), AccessKey::full_access()) + .transfer(near_units::parse_near!("100 N")) + .transact() + .await? + .into_result()?; + + let root_account = Account::from_secret_key(root, sk, &worker); + let eth_connector = root_account + .create_subaccount("aurora_eth_connector") + .initial_balance(near_units::parse_near!("15 N")) + .transact() + .await? + .into_result()?; + let engine = root_account + .create_subaccount("eth_connector") + .initial_balance(near_units::parse_near!("15 N")) + .transact() + .await? + .into_result()?; + let engine_contract_bytes = get_engine_contract(); + let engine_contract = engine.deploy(&engine_contract_bytes).await?.into_result()?; + let eth_connector_contract = eth_connector + .deploy(&get_eth_connector_contract()) + .await? + .into_result()?; + + Ok((engine_contract, eth_connector_contract, root_account)) + } + + pub async fn new() -> anyhow::Result { + Self::new_with_custodian(CUSTODIAN_ADDRESS).await + } + + pub async fn new_with_owner(owner: AccountId) -> anyhow::Result { + Self::new_contract(CUSTODIAN_ADDRESS, Some(owner)).await + } + + pub async fn new_with_custodian(eth_custodian_address: &str) -> anyhow::Result { + Self::new_contract(eth_custodian_address, None).await + } + + async fn new_contract( + eth_custodian_address: &str, + owner: Option, + ) -> anyhow::Result { + let (engine_contract, eth_connector_contract, root_account) = + Self::deploy_aurora_contract().await?; + + let prover_account: AccountId = eth_connector_contract.id().clone(); + let metadata = FungibleTokenMetadata::default(); + let account_with_access_right: AccountId = engine_contract.id().clone(); + // Init eth-connector + let metadata = json!({ + "spec": metadata.spec, + "name": metadata.name, + "symbol": metadata.symbol, + "icon": metadata.icon, + "reference": metadata.reference, + "decimals": metadata.decimals, + }); + let owner_id = owner.unwrap_or_else(|| account_with_access_right.clone()); + let res = eth_connector_contract + .call("new") + .args_json(json!({ + "prover_account": prover_account, + "eth_custodian_address": eth_custodian_address, + "metadata": metadata, + "account_with_access_right": account_with_access_right, + "owner_id": owner_id, + })) + .gas(DEFAULT_GAS) + .transact() + .await?; + assert!(res.is_success()); + + let chain_id = [0u8; 32]; + let res = engine_contract + .call("new") + .args_borsh((chain_id, engine_contract.id(), engine_contract.id(), 1_u64)) + .gas(DEFAULT_GAS) + .transact() + .await?; + assert!(res.is_success()); + + let metadata = FungibleTokenMetadata::default(); + let res = engine_contract + .call("new_eth_connector") + .args_borsh((prover_account, eth_custodian_address, metadata)) + .gas(DEFAULT_GAS) + .transact() + .await?; + assert!(res.is_success()); + + let acc = SetEthConnectorContractAccountArgs { + account: eth_connector_contract.id().parse().unwrap(), + withdraw_serialize_type: WithdrawSerializeType::Borsh, + }; + let res = engine_contract + .call("set_eth_connector_contract_account") + .args_borsh(acc) + .gas(DEFAULT_GAS) + .transact() + .await?; + assert!(res.is_success()); + + Ok(Self { + engine_contract, + eth_connector_contract, + root_account, + }) + } + + /// Waiting for the account creation + async fn waiting_account_creation( + worker: &Worker, + account_id: &AccountId, + ) -> anyhow::Result<()> { + let timer = std::time::Instant::now(); + // Try to get account within 30 secs + for _ in 0..60 { + if worker.view_account(account_id).await.is_err() { + tokio::time::sleep(std::time::Duration::from_millis(500)).await; + } else { + return Ok(()); + } + } + + anyhow::bail!( + "Account `{}` was not created in {:?} sec", + account_id, + timer.elapsed() + ) + } + + pub fn get_proof(&self, proof: &str) -> Proof { + serde_json::from_str(proof).unwrap() + } + + pub async fn create_sub_account(&self, name: &str) -> anyhow::Result { + Ok(self + .root_account + .create_subaccount(name) + .initial_balance(near_units::parse_near!("15 N")) + .transact() + .await? + .into_result()?) + } + + pub async fn deposit_with_proof(&self, proof: &Proof) -> anyhow::Result { + Ok(self + .engine_contract + .call("deposit") + .args_borsh(proof) + .gas(DEFAULT_GAS) + .transact() + .await?) + } + + pub async fn call_deposit_eth_to_near(&self) -> anyhow::Result<()> { + let proof: Proof = self.get_proof(PROOF_DATA_NEAR); + let res = self.deposit_with_proof(&proof).await?; + assert!(res.is_success()); + Ok(()) + } + + pub async fn call_deposit_eth_to_aurora(&self) -> anyhow::Result<()> { + let proof: Proof = serde_json::from_str(PROOF_DATA_ETH).unwrap(); + let res = self.deposit_with_proof(&proof).await?; + assert!(res.is_success()); + Ok(()) + } + + pub async fn user_deposit_with_proof( + &self, + user: &Account, + proof: &Proof, + ) -> anyhow::Result { + Ok(user + .call(self.engine_contract.id(), "deposit") + .args_borsh(proof) + .gas(DEFAULT_GAS) + .transact() + .await?) + } + + pub fn check_error_message(&self, res: ExecutionFinalResult, error_msg: &str) -> bool { + let mut is_failure = false; + for out in res.receipt_outcomes() { + is_failure = out.is_failure(); + if is_failure { + return format!("{:?}", res).contains(error_msg); + } + } + is_failure + } + + pub async fn call_is_used_proof(&self, proof: &str) -> anyhow::Result { + let proof: Proof = serde_json::from_str(proof).unwrap(); + let res = self + .engine_contract + .call("is_used_proof") + .args_borsh(proof) + .gas(DEFAULT_GAS) + .transact() + .await? + .into_result() + .unwrap() + .borsh::() + .unwrap(); + Ok(res) + } + + pub async fn get_eth_on_near_balance(&self, account: &AccountId) -> anyhow::Result { + let res = self + .engine_contract + .call("ft_balance_of") + .args_json((account,)) + .gas(DEFAULT_GAS) + .transact() + .await? + .into_result() + .unwrap() + .json::() + .unwrap(); + Ok(res) + } + + pub async fn get_eth_balance(&self, address: &Address) -> anyhow::Result { + #[derive(BorshSerialize, BorshDeserialize)] + pub struct BalanceOfEthCallArgs { + pub address: Address, + } + let args = BalanceOfEthCallArgs { address: *address }.try_to_vec()?; + let res = self + .engine_contract + .call("ft_balance_of_eth") + .args(args) + .gas(DEFAULT_GAS) + .transact() + .await?; + + res.into_result() + .unwrap() + .json::() + .map_err(Into::into) + .and_then(|res| { + res.try_into_u128() + .map_err(|e| anyhow::anyhow!(e.to_string())) + }) + } + + pub async fn total_supply(&self) -> anyhow::Result { + let res = self + .engine_contract + .call("ft_total_supply") + .gas(DEFAULT_GAS) + .transact() + .await? + .into_result() + .unwrap() + .json::() + .unwrap(); + Ok(res.0) + } +} + +pub fn print_logs(res: ExecutionFinalResult) { + for log in res.logs().iter() { + println!("\t[LOG] {}", log); + } +} + +pub fn validate_eth_address(address: &str) -> Address { + Address::decode(address).unwrap() +} + +pub fn get_eth_connector_contract() -> Vec { + let contract_path = Path::new("etc/aurora-eth-connector"); + std::fs::read(contract_path.join("bin/aurora-eth-connector-test.wasm")).unwrap() +} + +fn get_engine_contract() -> Vec { + if cfg!(feature = "mainnet-test") { + std::fs::read("../bin/aurora-mainnet-silo-test.wasm").unwrap() + } else if cfg!(feature = "testnet-test") { + std::fs::read("../bin/aurora-testnet-silo-test.wasm").unwrap() + } else { + panic!("AuroraRunner requires mainnet-test or testnet-test feature enabled.") + } +} diff --git a/engine-tests/Cargo.toml b/engine-tests/Cargo.toml index e69eb1624..c0465bee0 100644 --- a/engine-tests/Cargo.toml +++ b/engine-tests/Cargo.toml @@ -38,10 +38,12 @@ libsecp256k1.workspace = true near-crypto.workspace = true near-primitives-core.workspace = true near-primitives.workspace = true +near-sdk.workspace = true near-vm-errors.workspace = true near-vm-logic.workspace = true near-vm-runner.workspace = true rand.workspace = true +reqwest.workspace = true rlp.workspace = true serde.workspace = true serde_json.workspace = true @@ -51,6 +53,7 @@ tokio.workspace = true walrus.workspace = true [features] -mainnet-test = [] -testnet-test = [] +mainnet-test = ["aurora-engine-workspace/mainnet-test"] +testnet-test = ["aurora-engine-workspace/testnet-test"] error_refund = ["aurora-engine/error_refund", "aurora-engine-precompiles/error_refund"] +ext-connector = ["aurora-engine/ext-connector", "aurora-engine-precompiles/ext-connector", "engine-standalone-storage/ext-connector", "aurora-engine-workspace/ext-connector"] diff --git a/engine-tests/src/benches/eth_standard_precompiles.rs b/engine-tests/src/benches/eth_standard_precompiles.rs index c48cc6cab..5ee94fd03 100644 --- a/engine-tests/src/benches/eth_standard_precompiles.rs +++ b/engine-tests/src/benches/eth_standard_precompiles.rs @@ -59,7 +59,7 @@ pub fn eth_standard_precompiles_benchmark(c: &mut Criterion) { let mut group = c.benchmark_group("standard_precompiles"); // measure wall-clock time - for (tx_bytes, id) in transactions.iter().zip(bench_ids.into_iter()) { + for (tx_bytes, id) in transactions.iter().zip(bench_ids) { group.bench_function(id, |b| { b.iter_batched( || (runner.one_shot(), calling_account_id, tx_bytes.clone()), diff --git a/engine-tests/src/prelude.rs b/engine-tests/src/prelude.rs index 15525645d..e0db0162e 100644 --- a/engine-tests/src/prelude.rs +++ b/engine-tests/src/prelude.rs @@ -1,6 +1,4 @@ mod v0 { - pub use aurora_engine::connector; - pub use aurora_engine::fungible_token; #[cfg(feature = "meta-call")] pub use aurora_engine::meta_parsing; pub use aurora_engine::parameters; diff --git a/engine-tests/src/tests/relayer_keys.rs b/engine-tests/src/tests/access_keys.rs similarity index 82% rename from engine-tests/src/tests/relayer_keys.rs rename to engine-tests/src/tests/access_keys.rs index 300b3807b..2664bd3dc 100644 --- a/engine-tests/src/tests/relayer_keys.rs +++ b/engine-tests/src/tests/access_keys.rs @@ -1,5 +1,7 @@ use crate::utils::workspace::deploy_engine; -use aurora_engine_types::parameters::engine::{RelayerKeyArgs, RelayerKeyManagerArgs}; +use aurora_engine_types::parameters::engine::{ + FullAccessKeyArgs, RelayerKeyArgs, RelayerKeyManagerArgs, SetUpgradeDelayBlocksArgs, +}; use aurora_engine_types::public_key::PublicKey; use aurora_engine_types::types::Address; use aurora_engine_workspace::parse_near; @@ -275,6 +277,62 @@ async fn test_call_not_allowed_contract() { assert_error_message(&err, "unable to broadcast the transaction to the network"); } +#[tokio::test] +async fn test_attach_full_access_key() { + let aurora = deploy_engine().await; + let secret_key = SecretKey::from_random(KeyType::ED25519); + let public_key = public_key(&secret_key); + let admin = aurora.create_account(&aurora.id(), secret_key); + + let err = admin + .call(&aurora.id(), "set_upgrade_delay_blocks") + .args_borsh(SetUpgradeDelayBlocksArgs { + upgrade_delay_blocks: 5, + }) + .max_gas() + .transact() + .await; + assert_error_message(&err, "Failed to query access key"); + + let result = aurora + .attach_full_access_key(FullAccessKeyArgs { public_key }) + .max_gas() + .transact() + .await + .unwrap(); + assert!(result.is_success()); + + let result = admin + .call(&aurora.id(), "set_upgrade_delay_blocks") + .args_borsh(SetUpgradeDelayBlocksArgs { + upgrade_delay_blocks: 5, + }) + .max_gas() + .transact() + .await + .unwrap(); + assert!(result.is_success()); // because owner_account_id == current_account_id + + // Change the owner + let result = aurora + .set_owner(&"some_owner.root".parse().unwrap()) + .max_gas() + .transact() + .await + .unwrap(); + assert!(result.is_success()); + + let err = admin + .call(&aurora.id(), "set_upgrade_delay_blocks") + .args_borsh(SetUpgradeDelayBlocksArgs { + upgrade_delay_blocks: 5, + }) + .max_gas() + .transact() + .await; + assert_error_message(&err, "ERR_NOT_ALLOWED"); +} + fn public_key(sk: &SecretKey) -> PublicKey { let pk_str = serde_json::to_string(&sk.public_key()).unwrap(); PublicKey::from_str(pk_str.trim_matches('"')).unwrap() diff --git a/engine-tests/src/tests/contract_call.rs b/engine-tests/src/tests/contract_call.rs index 66de7eda9..eae6dce4f 100644 --- a/engine-tests/src/tests/contract_call.rs +++ b/engine-tests/src/tests/contract_call.rs @@ -2,7 +2,7 @@ use crate::prelude::{parameters::SubmitResult, vec, Address, Wei, H256, U256}; use crate::utils::solidity::exit_precompile::{ Tester, TesterConstructor, DEST_ACCOUNT, DEST_ADDRESS, }; -use crate::utils::{self, AuroraRunner, Signer, DEFAULT_AURORA_ACCOUNT_ID}; +use crate::utils::{self, deploy_runner, AuroraRunner, Signer, DEFAULT_AURORA_ACCOUNT_ID}; fn setup_test() -> (AuroraRunner, Signer, Address, Tester) { let mut runner = AuroraRunner::new(); @@ -44,7 +44,7 @@ fn setup_test() -> (AuroraRunner, Signer, Address, Tester) { #[test] #[should_panic] fn test_deploy_erc20_token_with_invalid_account_id() { - let mut runner = AuroraRunner::new(); + let mut runner = deploy_runner(); let invalid_nep141 = "_"; runner.deploy_erc20_token(invalid_nep141); } diff --git a/engine-tests/src/tests/erc20.rs b/engine-tests/src/tests/erc20.rs index 7f711a30a..6cd4130cc 100644 --- a/engine-tests/src/tests/erc20.rs +++ b/engine-tests/src/tests/erc20.rs @@ -8,9 +8,13 @@ use crate::utils::{ use aurora_engine::engine::EngineErrorKind; use aurora_engine::parameters::TransactionStatus; use aurora_engine_sdk as sdk; -use aurora_engine_types::parameters::connector::{Erc20Metadata, SetErc20MetadataArgs}; +use aurora_engine_types::account_id::AccountId; +use aurora_engine_types::parameters::connector::{ + Erc20Identifier, Erc20Metadata, SetErc20MetadataArgs, +}; use bstr::ByteSlice; use libsecp256k1::SecretKey; +use std::str::FromStr; const INITIAL_BALANCE: u64 = 1_000_000; const INITIAL_NONCE: u64 = 0; @@ -241,12 +245,14 @@ fn deploy_erc_20_out_of_gas() { #[test] fn test_erc20_get_and_set_metadata() { let mut runner = utils::deploy_runner(); - let erc20_address = runner.deploy_erc20_token("token"); + let token_account_id = "token"; + let erc20_address = runner.deploy_erc20_token(token_account_id); let caller = runner.aurora_account_id.clone(); + // Getting ERC-20 metadata by Address. let result = runner.one_shot().call( "get_erc20_metadata", &caller, - erc20_address.as_bytes().to_vec(), + serde_json::to_vec::(&erc20_address.into()).unwrap(), ); assert!(result.is_ok()); @@ -265,17 +271,21 @@ fn test_erc20_get_and_set_metadata() { "set_erc20_metadata", &caller, serde_json::to_vec(&SetErc20MetadataArgs { - erc20_address, - erc20_metadata: new_metadata.clone(), + erc20_identifier: erc20_address.into(), + metadata: new_metadata.clone(), }) .unwrap(), ); assert!(result.is_ok()); + // Getting ERC-20 metadata by NEP-141 account id. let result = runner.one_shot().call( "get_erc20_metadata", &caller, - erc20_address.as_bytes().to_vec(), + serde_json::to_vec::( + &AccountId::from_str(token_account_id).unwrap().into(), + ) + .unwrap(), ); assert!(result.is_ok()); diff --git a/engine-tests/src/tests/erc20_connector.rs b/engine-tests/src/tests/erc20_connector.rs index c85e2a86b..ea3151d8d 100644 --- a/engine-tests/src/tests/erc20_connector.rs +++ b/engine-tests/src/tests/erc20_connector.rs @@ -383,7 +383,7 @@ fn test_transfer_erc20_token() { ); } -mod workspace { +pub mod workspace { use super::build_input; use crate::prelude::{Address, Wei, WeiU256, U256}; use crate::utils; @@ -394,6 +394,8 @@ mod workspace { nep_141_balance_of, transfer_nep_141, transfer_nep_141_to_erc_20, }; use aurora_engine::parameters::{CallArgs, FunctionCallArgsV2}; + #[cfg(feature = "ext-connector")] + use aurora_engine::proof::Proof; use aurora_engine_types::parameters::engine::TransactionStatus; use aurora_engine_workspace::account::Account; use aurora_engine_workspace::types::ExecutionFinalResult; @@ -681,10 +683,12 @@ mod workspace { nep_141_balance_of(aurora.as_raw_contract(), &aurora.id()).await, u128::from(INITIAL_ETH_BALANCE - ETH_EXIT_AMOUNT) ); + assert_eq!( nep_141_balance_of(aurora.as_raw_contract(), &exit_account_id.parse().unwrap()).await, - u128::from(ETH_EXIT_AMOUNT) + ETH_EXIT_AMOUNT.into() ); + assert_eq!( eth_balance_of(signer_address, &aurora).await, Wei::new_u64(INITIAL_ETH_BALANCE - ETH_EXIT_AMOUNT) @@ -770,8 +774,11 @@ mod workspace { .await?; assert!(result.is_success()); - let balance = aurora.ft_balance_of(&aurora.id()).await?.result; - assert_eq!(balance.0, u128::from(INITIAL_ETH_BALANCE)); + #[cfg(feature = "ext-connector")] + deposit_balance(&aurora).await; + + let balance = nep_141_balance_of(aurora.as_raw_contract(), &aurora.id()).await; + assert_eq!(balance, u128::from(INITIAL_ETH_BALANCE)); let balance = eth_balance_of(signer_address, &aurora).await; assert_eq!(balance, Wei::new_u64(INITIAL_ETH_BALANCE)); @@ -910,7 +917,7 @@ mod workspace { }) } - async fn exit_to_near( + pub async fn exit_to_near( source: &Account, dest: &str, amount: u128, @@ -945,7 +952,7 @@ mod workspace { Wei::new(result) } - async fn erc20_balance(erc20: &ERC20, address: Address, aurora: &EngineContract) -> U256 { + pub async fn erc20_balance(erc20: &ERC20, address: Address, aurora: &EngineContract) -> U256 { let balance_tx = erc20.balance_of(address, 0.into()); let result = aurora .call(erc20.0.address, U256::zero(), balance_tx.data) @@ -960,6 +967,17 @@ mod workspace { } } + #[cfg(feature = "ext-connector")] + async fn deposit_balance(aurora: &EngineContract) { + let proof = create_test_proof( + INITIAL_ETH_BALANCE, + aurora.id().as_ref(), + "096de9c2b8a5b8c22cee3289b101f6960d68e51e", + ); + let result = aurora.deposit(proof).max_gas().transact().await.unwrap(); + assert!(result.is_success()); + } + struct TestExitToNearContext { ft_owner: Account, ft_owner_address: Address, @@ -977,4 +995,59 @@ mod workspace { tester_address: Address, aurora: EngineContract, } + + #[cfg(feature = "ext-connector")] + fn create_test_proof( + deposit_amount: u64, + recipient_id: &str, + custodian_address: &str, + ) -> Proof { + use aurora_engine::contract_methods::connector::deposit_event::{ + DepositedEvent, TokenMessageData, DEPOSITED_EVENT, + }; + use aurora_engine_types::types::{Fee, NEP141Wei}; + + let eth_custodian_address: Address = Address::decode(custodian_address).unwrap(); + + let message = recipient_id.to_string(); + let fee: Fee = Fee::new(NEP141Wei::new(0)); + let token_message_data = + TokenMessageData::parse_event_message_and_prepare_token_message_data(&message, fee) + .unwrap(); + + let deposit_event = DepositedEvent { + eth_custodian_address, + sender: Address::zero(), + token_message_data, + amount: NEP141Wei::new(deposit_amount.into()), + fee, + }; + + let event_schema = ethabi::Event { + name: DEPOSITED_EVENT.into(), + inputs: DepositedEvent::event_params(), + anonymous: false, + }; + let log_entry = aurora_engine_types::parameters::connector::LogEntry { + address: eth_custodian_address.raw(), + topics: vec![ + event_schema.signature(), + // the sender is not important + crate::prelude::H256::zero(), + ], + data: ethabi::encode(&[ + ethabi::Token::String(message), + ethabi::Token::Uint(U256::from(deposit_event.amount.as_u128())), + ethabi::Token::Uint(U256::from(deposit_event.fee.as_u128())), + ]), + }; + Proof { + log_index: 1, + log_entry_data: rlp::encode(&log_entry).to_vec(), + receipt_index: 1, + receipt_data: Vec::new(), + header_data: Vec::new(), + proof: Vec::new(), + } + } } diff --git a/engine-tests/src/tests/erc20_mirror.rs b/engine-tests/src/tests/erc20_mirror.rs new file mode 100644 index 000000000..456e1ed22 --- /dev/null +++ b/engine-tests/src/tests/erc20_mirror.rs @@ -0,0 +1,262 @@ +use crate::prelude::U256; +use crate::tests::erc20_connector::workspace::{erc20_balance, exit_to_near}; +use crate::utils::workspace::{ + deploy_engine_with_code, deploy_erc20_from_nep_141, deploy_nep_141, nep_141_balance_of, + transfer_nep_141_to_erc_20, +}; +use crate::utils::AuroraRunner; +use aurora_engine_precompiles::xcc::state::STORAGE_AMOUNT; +use aurora_engine_types::parameters::connector::{ + Erc20Identifier, Erc20Metadata, MirrorErc20TokenArgs, SetErc20MetadataArgs, + WithdrawSerializeType, +}; +use aurora_engine_types::parameters::silo::SiloParamsArgs; +use aurora_engine_types::types::RawU256; +use aurora_engine_workspace::account::Account; +use aurora_engine_workspace::{parse_near, EngineContract, RawContract}; + +const AURORA_VERSION: &str = include_str!("../../../VERSION"); +const TRANSFER_AMOUNT: u128 = 1000; + +#[tokio::test] +#[allow(clippy::too_many_lines)] +async fn test_mirroring_erc20_token() { + let main_contract = deploy_main_contract().await; + let silo_contract = deploy_silo_contract(&main_contract).await; + let (nep141, ft_owner) = deploy_nep141(&main_contract).await; + let erc20 = deploy_erc20_from_nep_141(nep141.id().as_ref(), &main_contract) + .await + .unwrap(); + let erc20_metadata = Erc20Metadata { + name: "USD Tether".to_string(), + symbol: "USDT".to_string(), + decimals: 18, + }; + + // Set ERC-20 metadata. + let result = main_contract + .set_erc20_metadata(SetErc20MetadataArgs { + erc20_identifier: Erc20Identifier::Erc20 { + address: erc20.0.address, + }, + metadata: erc20_metadata.clone(), + }) + .max_gas() + .transact() + .await + .unwrap(); + assert!(result.is_success()); + + // Try to mirror ERC-20 with silo mode off. + let result = silo_contract + .mirror_erc20_token(MirrorErc20TokenArgs { + contract_id: main_contract.id(), + nep141: nep141.id(), + }) + .max_gas() + .transact() + .await; + assert!(result.is_err()); + + // Turn on silo mode by setting default params. + let result = silo_contract + .set_silo_params(Some(SiloParamsArgs::default())) + .max_gas() + .transact() + .await + .unwrap(); + assert!(result.is_success()); + + // Should get ERC-20 address of mirrored contract. + let result = silo_contract + .mirror_erc20_token(MirrorErc20TokenArgs { + contract_id: main_contract.id(), + nep141: nep141.id(), + }) + .max_gas() + .transact() + .await; + let erc20_address = result.unwrap().into_value(); + + assert_eq!(erc20_address, erc20.0.address); + let silo_erc20_metadata = silo_contract + .get_erc20_metadata(Erc20Identifier::Erc20 { + address: erc20_address, + }) + .await + .unwrap() + .result; + assert_eq!(silo_erc20_metadata, erc20_metadata); + + // We need to storage_deposit to register account id of the silo contract in the nep-141. + let result = silo_contract + .root() + .call(&nep141.id(), "storage_deposit") + .args_json(serde_json::json!({ + "account_id": silo_contract.id(), + })) + .deposit(STORAGE_AMOUNT.as_u128()) + .transact() + .await + .unwrap(); + assert!(result.is_success()); + assert_eq!(nep_141_balance_of(&nep141, &ft_owner.id()).await, 1_000_000); + + let address = aurora_engine_sdk::types::near_account_to_evm_address(ft_owner.id().as_bytes()); + + transfer_nep_141_to_erc_20( + &nep141, + &erc20, + &ft_owner, + address, + TRANSFER_AMOUNT, + &main_contract, // main contract + ) + .await + .unwrap(); + transfer_nep_141_to_erc_20( + &nep141, + &erc20, + &ft_owner, + address, + TRANSFER_AMOUNT, + &silo_contract, // silo contract + ) + .await + .unwrap(); + + assert_eq!( + erc20_balance(&erc20, address, &main_contract).await, + TRANSFER_AMOUNT.into() + ); + assert_eq!( + erc20_balance(&erc20, address, &silo_contract).await, + TRANSFER_AMOUNT.into() + ); + assert_eq!( + nep_141_balance_of(&nep141, &ft_owner.id()).await, + 1_000_000 - TRANSFER_AMOUNT * 2 + ); + + let result = exit_to_near( + &ft_owner, + ft_owner.id().as_ref(), + TRANSFER_AMOUNT, + &erc20, + &main_contract, + ) + .await; + assert!(result.is_success()); + + let result = exit_to_near( + &ft_owner, + ft_owner.id().as_ref(), + TRANSFER_AMOUNT, + &erc20, + &silo_contract, + ) + .await; + assert!(result.is_success()); + + assert_eq!( + erc20_balance(&erc20, address, &main_contract).await, + 0.into() + ); + assert_eq!( + erc20_balance(&erc20, address, &silo_contract).await, + 0.into() + ); + assert_eq!(nep_141_balance_of(&nep141, &ft_owner.id()).await, 1_000_000); +} + +async fn deploy_main_contract() -> EngineContract { + let code = get_main_contract_code().await.unwrap(); + deploy_engine_with_code(code).await +} + +async fn deploy_silo_contract(main_contract: &EngineContract) -> EngineContract { + let silo_account = main_contract + .root() + .create_subaccount("silo", parse_near!("50 N")) + .await + .unwrap(); + let silo_bytes = AuroraRunner::get_engine_code(); + let contract = silo_account.deploy(&silo_bytes).await.unwrap(); + let silo = EngineContract::from((contract, main_contract.node.clone())); + + let result = silo + .new( + RawU256::from(U256::from(AuroraRunner::get_default_chain_id() + 1)), + silo_account.id(), + 1, + ) + .max_gas() + .transact() + .await + .unwrap(); + assert!(result.is_success()); + + let result = silo + .set_eth_connector_contract_account(main_contract.id(), WithdrawSerializeType::Borsh) + .max_gas() + .transact() + .await + .unwrap(); + assert!(result.is_success()); + + silo +} + +async fn deploy_nep141(main_contract: &EngineContract) -> (RawContract, Account) { + let ft_owner = main_contract + .root() + .create_subaccount("ft_owner", parse_near!("10 N")) + .await + .unwrap(); + let nep_141_account = main_contract + .root() + .create_subaccount("test_token", parse_near!("10 N")) + .await + .unwrap(); + + // Deploy nep141 token + let contract = deploy_nep_141(&nep_141_account, &ft_owner, 1_000_000, main_contract) + .await + .unwrap(); + + (contract, ft_owner) +} + +async fn download_main_contract_code() -> anyhow::Result> { + let version = AURORA_VERSION.trim(); + let wasm_url = + format!("https://github.com/aurora-is-near/aurora-engine/releases/download/{version}/aurora-mainnet.wasm"); + let response = reqwest::get(wasm_url).await?; + + assert!( + response.status().is_success(), + "{:?}", + response.text().await + ); + + response + .bytes() + .await + .map(|b| b.to_vec()) + .map_err(Into::into) +} + +async fn get_main_contract_code() -> anyhow::Result> { + let path = if cfg!(feature = "mainnet-test") { + "../bin/aurora-mainnet-test.wasm" + } else if cfg!(feature = "testnet-test") { + "../bin/aurora-testnet-test.wasm" + } else { + panic!("AuroraRunner requires mainnet-test or testnet-test feature enabled.") + }; + + match std::fs::read(path) { + Ok(bytes) => Ok(bytes), + Err(_) => download_main_contract_code().await, + } +} diff --git a/engine-tests/src/tests/hashchain.rs b/engine-tests/src/tests/hashchain.rs index 338d8f2bc..092f1793a 100644 --- a/engine-tests/src/tests/hashchain.rs +++ b/engine-tests/src/tests/hashchain.rs @@ -11,13 +11,15 @@ use aurora_engine_types::{ #[test] fn test_hashchain() { let (mut runner, mut signer, _) = crate::tests::sanity::initialize_transfer(); + // Re-init the hashchain so we know the first tx is `start_hashchain`. + let account_id = runner.aurora_account_id.clone(); + utils::init_hashchain(&mut runner, &account_id, None); // The tests initialize the hashchain with the default value. let hc = get_latest_hashchain(&runner); - // Hashchain starts 2 heights lower than the current context height because - // at `hc.block_height + 1` the `start_hashchain` is submitted and at - // `hc.block_height + 2` the signer address is created in the EVM state. - assert_eq!(hc.block_height, runner.context.block_height - 2); + // Hashchain starts 1 height lower than the current context height because + // at `hc.block_height + 1` the `start_hashchain` is submitted. + assert_eq!(hc.block_height, runner.context.block_height - 1); assert_eq!(hc.hashchain, hex::encode(H256::default())); // Execute a transaction and the hashchain changes @@ -57,9 +59,7 @@ fn test_hashchain() { &Bloom::default(), ) .unwrap(); - // Skip a block height because at block_height + 1 there is no "real" transaction - // (that height is skipped when the signer address is directly inserted into the EVM state) - block_height += 2; + block_height += 1; hc.move_to_block(block_height).unwrap(); // Insert the `submit` transaction we care about hc.add_block_tx(block_height, "submit", &input, &output, &Bloom::default()) diff --git a/engine-tests/src/tests/mod.rs b/engine-tests/src/tests/mod.rs index 87fdc8640..0822702b2 100644 --- a/engine-tests/src/tests/mod.rs +++ b/engine-tests/src/tests/mod.rs @@ -1,9 +1,10 @@ +mod access_keys; mod account_id_precompiles; mod contract_call; mod ecrecover; mod erc20; mod erc20_connector; -mod eth_connector; +mod erc20_mirror; mod ghsa_3p69_m8gg_fwmf; mod hashchain; #[cfg(feature = "meta-call")] @@ -16,11 +17,11 @@ mod pause_contract; mod prepaid_gas_precompile; mod promise_results_precompile; mod random; -mod relayer_keys; mod repro; pub mod sanity; mod self_destruct_state; mod serde; +mod silo; mod standalone; mod standard_precompiles; mod transaction; diff --git a/engine-tests/src/tests/one_inch.rs b/engine-tests/src/tests/one_inch.rs index b5670a965..04dba36a9 100644 --- a/engine-tests/src/tests/one_inch.rs +++ b/engine-tests/src/tests/one_inch.rs @@ -10,8 +10,7 @@ use std::sync::Once; const INITIAL_BALANCE: Wei = Wei::new_u64(1_000_000); const INITIAL_NONCE: u64 = 0; -static DOWNLOAD_ONCE: Once = Once::new(); -static COMPILE_ONCE: Once = Once::new(); +static DOWNLOAD_COMPILE_ONCE: Once = Once::new(); #[test] fn test_1inch_liquidity_protocol() { @@ -100,8 +99,8 @@ fn test_1_inch_limit_order_deploy() { // more than 3.5 million Ethereum gas used assert!(result.gas_used > 3_500_000); - // less than 9 NEAR Tgas used - assert_gas_bound(profile.all_gas(), 9); + // less than 10 NEAR Tgas used + assert_gas_bound(profile.all_gas(), 10); // at least 45% of which is from wasm execution let wasm_fraction = 100 * profile.wasm_gas() / profile.all_gas(); assert!( @@ -116,8 +115,7 @@ fn deploy_1_inch_limit_order_contract( ) -> VMOutcome { let artifacts_path = utils::one_inch::download_and_compile_solidity_sources( "limit-order-protocol", - &DOWNLOAD_ONCE, - &COMPILE_ONCE, + &DOWNLOAD_COMPILE_ONCE, ); let contract_path = artifacts_path.join("LimitOrderProtocol.sol/LimitOrderProtocol.json"); let constructor = diff --git a/engine-tests/src/tests/pausable_precompiles.rs b/engine-tests/src/tests/pausable_precompiles.rs index bd975d9e3..99fdcf326 100644 --- a/engine-tests/src/tests/pausable_precompiles.rs +++ b/engine-tests/src/tests/pausable_precompiles.rs @@ -19,11 +19,9 @@ fn test_paused_precompile_is_shown_when_viewing() { let call_args = PausePrecompilesCallArgs { paused_mask: EXIT_TO_ETHEREUM_FLAG, }; + let input = call_args.try_to_vec().unwrap(); - let mut input: Vec = Vec::new(); - call_args.serialize(&mut input).unwrap(); - - let _res = runner.call(PAUSE_PRECOMPILES, CALLED_ACCOUNT_ID, input.clone()); + let _res = runner.call(PAUSE_PRECOMPILES, CALLED_ACCOUNT_ID, input); let result = runner .one_shot() .call(PAUSED_PRECOMPILES, CALLED_ACCOUNT_ID, Vec::new()) @@ -42,11 +40,9 @@ fn test_executing_paused_precompile_throws_error() { let call_args = PausePrecompilesCallArgs { paused_mask: EXIT_TO_ETHEREUM_FLAG, }; + let input = call_args.try_to_vec().unwrap(); - let mut input: Vec = Vec::new(); - call_args.serialize(&mut input).unwrap(); - - let _res = runner.call(PAUSE_PRECOMPILES, CALLED_ACCOUNT_ID, input.clone()); + let _res = runner.call(PAUSE_PRECOMPILES, CALLED_ACCOUNT_ID, input); let is_to_near = false; let error = tester .withdraw(&mut runner, &mut signer, is_to_near) @@ -65,9 +61,7 @@ fn test_executing_paused_and_then_resumed_precompile_succeeds() { let call_args = PausePrecompilesCallArgs { paused_mask: EXIT_TO_ETHEREUM_FLAG, }; - - let mut input: Vec = Vec::new(); - call_args.serialize(&mut input).unwrap(); + let input = call_args.try_to_vec().unwrap(); let _res = runner.call(PAUSE_PRECOMPILES, CALLED_ACCOUNT_ID, input.clone()); let _res = runner.call(RESUME_PRECOMPILES, CALLED_ACCOUNT_ID, input); @@ -89,10 +83,7 @@ fn test_resuming_precompile_does_not_throw_error() { let mut runner = utils::deploy_runner(); let call_args = PausePrecompilesCallArgs { paused_mask: 0b1 }; - - let mut input: Vec = Vec::new(); - call_args.serialize(&mut input).unwrap(); - + let input = call_args.try_to_vec().unwrap(); let result = runner.call(RESUME_PRECOMPILES, CALLED_ACCOUNT_ID, input); assert!(result.is_ok(), "{result:?}"); diff --git a/engine-tests/src/tests/repro.rs b/engine-tests/src/tests/repro.rs index 5776df0cb..35b7eede4 100644 --- a/engine-tests/src/tests/repro.rs +++ b/engine-tests/src/tests/repro.rs @@ -71,7 +71,7 @@ fn repro_FRcorNv() { block_timestamp: 1_650_960_438_774_745_116, input_path: "src/tests/res/input_FRcorNv.hex", evm_gas_used: 1_239_721, - near_gas_used: 177, + near_gas_used: 178, }); } @@ -88,7 +88,7 @@ fn repro_5bEgfRQ() { block_timestamp: 1_651_073_772_931_594_646, input_path: "src/tests/res/input_5bEgfRQ.hex", evm_gas_used: 6_414_105, - near_gas_used: 648, + near_gas_used: 649, }); } @@ -106,7 +106,7 @@ fn repro_D98vwmi() { block_timestamp: 1_651_753_443_421_003_245, input_path: "src/tests/res/input_D98vwmi.hex", evm_gas_used: 1_035_348, - near_gas_used: 179, + near_gas_used: 180, }); } diff --git a/engine-tests/src/tests/sanity.rs b/engine-tests/src/tests/sanity.rs index 302930550..95bf59e48 100644 --- a/engine-tests/src/tests/sanity.rs +++ b/engine-tests/src/tests/sanity.rs @@ -2,12 +2,12 @@ use crate::prelude::{Address, U256}; use crate::prelude::{Wei, ERC20_MINT_SELECTOR}; use crate::utils::{self, str_to_account_id}; use aurora_engine::engine::{EngineErrorKind, GasPaymentError, ZERO_ADDRESS_FIX_HEIGHT}; -use aurora_engine::fungible_token::FungibleTokenMetadata; use aurora_engine::parameters::{SetOwnerArgs, SetUpgradeDelayBlocksArgs, TransactionStatus}; use aurora_engine_sdk as sdk; use aurora_engine_types::borsh::{BorshDeserialize, BorshSerialize}; +#[cfg(not(feature = "ext-connector"))] +use aurora_engine_types::parameters::connector::FungibleTokenMetadata; use aurora_engine_types::H160; -use evm::ExitFatal; use libsecp256k1::SecretKey; use rand::RngCore; use std::path::{Path, PathBuf}; @@ -139,6 +139,7 @@ fn test_total_supply_accounting() { constructor.deployed_at(contract_address) }; + #[cfg(not(feature = "ext-connector"))] let get_total_supply = |runner: &utils::AuroraRunner| -> Wei { let result = runner .one_shot() @@ -163,6 +164,7 @@ fn test_total_supply_accounting() { }) .unwrap(); assert_eq!(runner.get_balance(benefactor), TRANSFER_AMOUNT); + #[cfg(not(feature = "ext-connector"))] assert_eq!(get_total_supply(&mut runner), INITIAL_BALANCE); // Self-destruct with self benefactor burns any ETH in the destroyed contract @@ -176,6 +178,7 @@ fn test_total_supply_accounting() { ) }) .unwrap(); + #[cfg(not(feature = "ext-connector"))] assert_eq!( get_total_supply(&mut runner), INITIAL_BALANCE - TRANSFER_AMOUNT @@ -681,7 +684,7 @@ fn test_num_wasm_functions() { let module = walrus::ModuleConfig::default() .parse(runner.code.code()) .unwrap(); - let expected_number = 1464; + let expected_number = 1550; let actual_number = module.funcs.iter().count(); assert!( @@ -1039,6 +1042,7 @@ fn test_block_hash_contract() { utils::panic_on_fail(result.status); } +#[cfg(not(feature = "ext-connector"))] #[test] fn test_ft_metadata() { let runner = utils::deploy_runner(); @@ -1142,10 +1146,7 @@ fn test_set_owner_fail_on_same_owner() { .unwrap_err(); // check error equality - assert_eq!( - error.kind, - EngineErrorKind::EvmFatal(ExitFatal::Other("ERR_SAME_OWNER".into())) - ); + assert_eq!(error.kind, EngineErrorKind::SameOwner); } #[test] diff --git a/engine-tests/src/tests/silo.rs b/engine-tests/src/tests/silo.rs new file mode 100644 index 000000000..fa9387cc4 --- /dev/null +++ b/engine-tests/src/tests/silo.rs @@ -0,0 +1,1095 @@ +use aurora_engine::engine::EngineErrorKind; +use aurora_engine_sdk as sdk; +use aurora_engine_types::account_id::AccountId; +use aurora_engine_types::borsh::BorshSerialize; +use aurora_engine_types::parameters::engine::TransactionStatus; +use aurora_engine_types::parameters::silo::{ + FixedGasCostArgs, SiloParamsArgs, WhitelistAccountArgs, WhitelistAddressArgs, WhitelistArgs, + WhitelistKind, WhitelistStatusArgs, +}; +use libsecp256k1::SecretKey; +use rand::{rngs::ThreadRng, Rng, RngCore}; +use std::fmt::Debug; + +use crate::{ + prelude::{Address, Wei}, + utils::{self, validate_address_balance_and_nonce, AuroraRunner}, +}; + +const INITIAL_BALANCE: Wei = Wei::new_u64(10u64.pow(18) * 10); +const ZERO_BALANCE: Wei = Wei::zero(); +const INITIAL_NONCE: u64 = 0; +const TRANSFER_AMOUNT: Wei = Wei::new_u64(10u64.pow(18) * 4); +const FEE: Wei = Wei::new_u64(10u64.pow(18)); +const ERC20_FALLBACK_ADDRESS: Address = Address::zero(); +const SILO_PARAMS_ARGS: SiloParamsArgs = SiloParamsArgs { + fixed_gas_cost: FEE, + erc20_fallback_address: ERC20_FALLBACK_ADDRESS, +}; +// https://github.com/aurora-is-near/aurora-engine/blob/master/engine-tests/src/test_utils/mod.rs#L393 +const CALLER_ACCOUNT_ID: &str = "some-account.near"; + +#[test] +fn test_address_transfer_success() { + // set up Aurora runner and accounts + let (mut runner, mut source_account, receiver) = initialize_transfer(); + let sender = utils::address_from_secret_key(&source_account.secret_key); + let caller: AccountId = CALLER_ACCOUNT_ID.parse().unwrap(); + + set_silo_params(&mut runner, Some(SILO_PARAMS_ARGS)); + + // Allow to submit transactions + add_account_to_whitelist(&mut runner, caller); + add_address_to_whitelist(&mut runner, sender); + + // validate pre-state + validate_address_balance_and_nonce(&runner, sender, INITIAL_BALANCE, INITIAL_NONCE.into()); + validate_address_balance_and_nonce(&runner, receiver, ZERO_BALANCE, INITIAL_NONCE.into()); + + // perform transfer + runner + .submit_with_signer(&mut source_account, |nonce| { + utils::transfer(receiver, TRANSFER_AMOUNT, nonce) + }) + .unwrap(); + + // validate post-state + validate_address_balance_and_nonce( + &runner, + sender, + INITIAL_BALANCE - FEE - TRANSFER_AMOUNT, + (INITIAL_NONCE + 1).into(), + ); + validate_address_balance_and_nonce(&runner, receiver, TRANSFER_AMOUNT, INITIAL_NONCE.into()); +} + +#[test] +fn test_transfer_insufficient_balance() { + let (mut runner, mut source_account, receiver) = initialize_transfer(); + let sender = utils::address_from_secret_key(&source_account.secret_key); + let caller: AccountId = CALLER_ACCOUNT_ID.parse().unwrap(); + + set_silo_params(&mut runner, Some(SILO_PARAMS_ARGS)); + add_account_to_whitelist(&mut runner, caller); + add_address_to_whitelist(&mut runner, sender); + + // validate pre-state + validate_address_balance_and_nonce(&runner, sender, INITIAL_BALANCE, INITIAL_NONCE.into()); + validate_address_balance_and_nonce(&runner, receiver, ZERO_BALANCE, INITIAL_NONCE.into()); + + // attempt transfer + let result = runner + .submit_with_signer(&mut source_account, |nonce| { + // try to transfer more than we have + utils::transfer(receiver, INITIAL_BALANCE + INITIAL_BALANCE, nonce) + }) + .unwrap(); + assert_eq!(result.status, TransactionStatus::OutOfFund); + + // validate post-state + validate_address_balance_and_nonce( + &runner, + sender, + INITIAL_BALANCE - FEE, + // the nonce is still incremented even though the transfer failed + (INITIAL_NONCE + 1).into(), + ); + validate_address_balance_and_nonce(&runner, receiver, ZERO_BALANCE, INITIAL_NONCE.into()); +} + +#[test] +fn test_transfer_insufficient_balance_fee() { + const HALF_FEE: Wei = Wei::new_u64(10u64.pow(18) / 2); + + let (mut runner, mut source_account, receiver) = initialize_transfer(); + let sender = utils::address_from_secret_key(&source_account.secret_key); + let caller: AccountId = CALLER_ACCOUNT_ID.parse().unwrap(); + + set_silo_params(&mut runner, Some(SILO_PARAMS_ARGS)); + add_account_to_whitelist(&mut runner, caller); + add_address_to_whitelist(&mut runner, sender); + + // validate pre-state + validate_address_balance_and_nonce(&runner, sender, INITIAL_BALANCE, INITIAL_NONCE.into()); + validate_address_balance_and_nonce(&runner, receiver, ZERO_BALANCE, INITIAL_NONCE.into()); + + // attempt transfer + let result = runner + .submit_with_signer(&mut source_account, |nonce| { + // try to transfer more than we have + utils::transfer( + receiver, + // We want to leave TRANSFER_AMOUNT + HALF_FEE on the balance. + INITIAL_BALANCE - TRANSFER_AMOUNT - FEE - HALF_FEE, + nonce, + ) + }) + .unwrap(); + assert!(matches!(result.status, TransactionStatus::Succeed(_))); + + // validate post-state + validate_address_balance_and_nonce( + &runner, + sender, + TRANSFER_AMOUNT + HALF_FEE, + // the nonce is still incremented even though the transfer failed + (INITIAL_NONCE + 1).into(), + ); + validate_address_balance_and_nonce( + &runner, + receiver, + INITIAL_BALANCE - TRANSFER_AMOUNT - FEE - HALF_FEE, + INITIAL_NONCE.into(), + ); + + // attempt transfer + let result = runner + .submit_with_signer(&mut source_account, |nonce| { + // try to transfer more than we have + utils::transfer(receiver, TRANSFER_AMOUNT, nonce) + }) + .unwrap(); + assert!(matches!(result.status, TransactionStatus::OutOfFund)); +} + +#[test] +fn test_eth_transfer_incorrect_nonce() { + let (mut runner, mut source_account, receiver) = initialize_transfer(); + let sender = utils::address_from_secret_key(&source_account.secret_key); + let caller: AccountId = CALLER_ACCOUNT_ID.parse().unwrap(); + + set_silo_params(&mut runner, Some(SILO_PARAMS_ARGS)); + add_account_to_whitelist(&mut runner, caller); + add_address_to_whitelist(&mut runner, sender); + + // validate pre-state + validate_address_balance_and_nonce(&runner, sender, INITIAL_BALANCE, INITIAL_NONCE.into()); + validate_address_balance_and_nonce(&runner, receiver, ZERO_BALANCE, INITIAL_NONCE.into()); + + // attempt transfer + let err = runner + .submit_with_signer(&mut source_account, |nonce| { + // creating transaction with incorrect nonce + utils::transfer(receiver, TRANSFER_AMOUNT, nonce + 1) + }) + .unwrap_err(); + assert_eq!(err.kind, EngineErrorKind::IncorrectNonce); + + // validate post-state (which is the same as pre-state in this case) + validate_address_balance_and_nonce(&runner, sender, INITIAL_BALANCE, INITIAL_NONCE.into()); + validate_address_balance_and_nonce(&runner, receiver, ZERO_BALANCE, INITIAL_NONCE.into()); +} + +#[test] +fn test_transfer_with_low_gas_limit() { + let (mut runner, mut signer, receiver) = initialize_transfer(); + let sender = utils::address_from_secret_key(&signer.secret_key); + let caller: AccountId = CALLER_ACCOUNT_ID.parse().unwrap(); + + set_silo_params(&mut runner, Some(SILO_PARAMS_ARGS)); + add_account_to_whitelist(&mut runner, caller); + add_address_to_whitelist(&mut runner, sender); + + let transaction = |nonce| { + let mut tx = utils::transfer(receiver, TRANSFER_AMOUNT, nonce); + // it's not enough gas for common tx, but it doesn't matter if fixed cost is set + tx.gas_limit = 10_000.into(); + tx + }; + + // validate pre-state + validate_address_balance_and_nonce(&runner, sender, INITIAL_BALANCE, INITIAL_NONCE.into()); + validate_address_balance_and_nonce(&runner, receiver, ZERO_BALANCE, INITIAL_NONCE.into()); + + // make transfer + let result = runner.submit_with_signer(&mut signer, transaction).unwrap(); + assert!(matches!(result.status, TransactionStatus::Succeed(_))); + + validate_address_balance_and_nonce( + &runner, + sender, + INITIAL_BALANCE - FEE - TRANSFER_AMOUNT, + (INITIAL_NONCE + 1).into(), + ); + validate_address_balance_and_nonce(&runner, receiver, TRANSFER_AMOUNT, INITIAL_NONCE.into()); +} + +#[test] +fn test_relayer_balance_after_transfer() { + let (mut runner, mut source_account, receiver) = initialize_transfer(); + let sender = utils::address_from_secret_key(&source_account.secret_key); + let caller: AccountId = CALLER_ACCOUNT_ID.parse().unwrap(); + let transaction = |nonce| utils::transfer(receiver, TRANSFER_AMOUNT, nonce); + + set_silo_params(&mut runner, Some(SILO_PARAMS_ARGS)); + add_account_to_whitelist(&mut runner, caller); + add_address_to_whitelist(&mut runner, sender); + + // validate pre-state + validate_address_balance_and_nonce(&runner, sender, INITIAL_BALANCE, INITIAL_NONCE.into()); + validate_address_balance_and_nonce(&runner, receiver, ZERO_BALANCE, INITIAL_NONCE.into()); + + // do transfer + runner + .submit_with_signer(&mut source_account, transaction) + .unwrap(); + + let relayer = sdk::types::near_account_to_evm_address( + runner.context.predecessor_account_id.as_ref().as_bytes(), + ); + + // validate post-state + validate_address_balance_and_nonce( + &runner, + sender, + INITIAL_BALANCE - TRANSFER_AMOUNT - FEE, + (INITIAL_NONCE + 1).into(), + ); + validate_address_balance_and_nonce(&runner, receiver, TRANSFER_AMOUNT, INITIAL_NONCE.into()); + validate_address_balance_and_nonce(&runner, relayer, FEE, INITIAL_NONCE.into()); +} + +#[test] +fn test_admin_access_right() { + let (mut runner, signer, _) = initialize_transfer(); + let sender = utils::address_from_secret_key(&signer.secret_key); + let caller: AccountId = CALLER_ACCOUNT_ID.parse().unwrap(); + + set_silo_params(&mut runner, Some(SILO_PARAMS_ARGS)); + // Allow to submit transactions. + + let account = WhitelistArgs::WhitelistAccountArgs(WhitelistAccountArgs { + account_id: caller.clone(), + kind: WhitelistKind::Account, + }) + .try_to_vec() + .unwrap(); + let address = WhitelistArgs::WhitelistAddressArgs(WhitelistAddressArgs { + address: sender, + kind: WhitelistKind::Address, + }) + .try_to_vec() + .unwrap(); + + let err = runner + .call("add_entry_to_whitelist", caller.as_ref(), account.clone()) + .unwrap_err(); + assert_eq!(err.kind, EngineErrorKind::NotAllowed); + let err = runner + .call("add_entry_to_whitelist", caller.as_ref(), address.clone()) + .unwrap_err(); + assert_eq!(err.kind, EngineErrorKind::NotAllowed); + + add_admin(&mut runner, caller.clone()); + + let result = runner.call("add_entry_to_whitelist", caller.as_ref(), account); + assert!(result.is_ok()); + let result = runner.call("add_entry_to_whitelist", caller.as_ref(), address); + assert!(result.is_ok()); +} + +#[test] +fn test_submit_access_right() { + let (mut runner, signer, receiver) = initialize_transfer(); + let sender = utils::address_from_secret_key(&signer.secret_key); + let caller: AccountId = CALLER_ACCOUNT_ID.parse().unwrap(); + let transaction = utils::transfer(receiver, TRANSFER_AMOUNT, INITIAL_NONCE.into()); + + set_silo_params(&mut runner, Some(SILO_PARAMS_ARGS)); + + validate_address_balance_and_nonce(&runner, sender, INITIAL_BALANCE, INITIAL_NONCE.into()); + validate_address_balance_and_nonce(&runner, receiver, ZERO_BALANCE, INITIAL_NONCE.into()); + + // Allow to submit transactions. + + // perform transfer + let err = runner + .submit_transaction(&signer.secret_key, transaction.clone()) + .unwrap_err(); + assert_eq!(err.kind, EngineErrorKind::NotAllowed); + + // validate post-state + validate_address_balance_and_nonce(&runner, sender, INITIAL_BALANCE, INITIAL_NONCE.into()); + validate_address_balance_and_nonce(&runner, receiver, ZERO_BALANCE, INITIAL_NONCE.into()); + + // Add caller and signer to whitelists. + add_account_to_whitelist(&mut runner, caller); + add_address_to_whitelist(&mut runner, sender); + + // perform transfer + let result = runner + .submit_transaction(&signer.secret_key, transaction) + .unwrap(); + assert!(matches!(result.status, TransactionStatus::Succeed(_))); + + // validate post-state + validate_address_balance_and_nonce( + &runner, + sender, + INITIAL_BALANCE - TRANSFER_AMOUNT - FEE, + (INITIAL_NONCE + 1).into(), + ); + validate_address_balance_and_nonce(&runner, receiver, TRANSFER_AMOUNT, INITIAL_NONCE.into()); +} + +#[test] +fn test_submit_access_right_via_batch() { + let (mut runner, signer, receiver) = initialize_transfer(); + let sender = utils::address_from_secret_key(&signer.secret_key); + let caller: AccountId = CALLER_ACCOUNT_ID.parse().unwrap(); + let transaction = utils::transfer(receiver, TRANSFER_AMOUNT, INITIAL_NONCE.into()); + + set_silo_params(&mut runner, Some(SILO_PARAMS_ARGS)); + + validate_address_balance_and_nonce(&runner, sender, INITIAL_BALANCE, INITIAL_NONCE.into()); + validate_address_balance_and_nonce(&runner, receiver, ZERO_BALANCE, INITIAL_NONCE.into()); + + // Allow to submit transactions. + + // perform transfer + let err = runner + .submit_transaction(&signer.secret_key, transaction.clone()) + .unwrap_err(); + assert_eq!(err.kind, EngineErrorKind::NotAllowed); + + // validate post-state + validate_address_balance_and_nonce(&runner, sender, INITIAL_BALANCE, INITIAL_NONCE.into()); + validate_address_balance_and_nonce(&runner, receiver, ZERO_BALANCE, INITIAL_NONCE.into()); + + // Add caller and signer to whitelists via batch. + let args = vec![ + WhitelistArgs::WhitelistAccountArgs(WhitelistAccountArgs { + kind: WhitelistKind::Account, + account_id: caller, + }), + WhitelistArgs::WhitelistAddressArgs(WhitelistAddressArgs { + kind: WhitelistKind::Address, + address: sender, + }), + ]; + + call_function(&mut runner, "add_entry_to_whitelist_batch", args); + + // perform transfer + let result = runner + .submit_transaction(&signer.secret_key, transaction) + .unwrap(); + assert!(matches!(result.status, TransactionStatus::Succeed(_))); + + // validate post-state + validate_address_balance_and_nonce( + &runner, + sender, + INITIAL_BALANCE - TRANSFER_AMOUNT - FEE, + (INITIAL_NONCE + 1).into(), + ); + validate_address_balance_and_nonce(&runner, receiver, TRANSFER_AMOUNT, INITIAL_NONCE.into()); +} + +#[test] +fn test_submit_with_disabled_whitelist() { + let (mut runner, signer, receiver) = initialize_transfer(); + let sender = utils::address_from_secret_key(&signer.secret_key); + let transaction = utils::transfer(receiver, TRANSFER_AMOUNT, INITIAL_NONCE.into()); + + set_silo_params(&mut runner, Some(SILO_PARAMS_ARGS)); + + validate_address_balance_and_nonce(&runner, sender, INITIAL_BALANCE, INITIAL_NONCE.into()); + validate_address_balance_and_nonce(&runner, receiver, ZERO_BALANCE, INITIAL_NONCE.into()); + + // Allow to submit transactions. + + // perform transfer + let err = runner + .submit_transaction(&signer.secret_key, transaction.clone()) + .unwrap_err(); + assert_eq!(err.kind, EngineErrorKind::NotAllowed); + + // validate post-state + validate_address_balance_and_nonce(&runner, sender, INITIAL_BALANCE, INITIAL_NONCE.into()); + validate_address_balance_and_nonce(&runner, receiver, ZERO_BALANCE, INITIAL_NONCE.into()); + + // Disable whitelists. + disable_whitelist(&mut runner, WhitelistKind::Account); + disable_whitelist(&mut runner, WhitelistKind::Address); + + // perform transfer + let result = runner + .submit_transaction(&signer.secret_key, transaction.clone()) + .unwrap(); + assert!(matches!(result.status, TransactionStatus::Succeed(_))); + + // validate post-state + validate_address_balance_and_nonce( + &runner, + sender, + INITIAL_BALANCE - TRANSFER_AMOUNT - FEE, + (INITIAL_NONCE + 1).into(), + ); + validate_address_balance_and_nonce(&runner, receiver, TRANSFER_AMOUNT, INITIAL_NONCE.into()); + + // Enable whitelist. + enable_whitelist(&mut runner, WhitelistKind::Account); + enable_whitelist(&mut runner, WhitelistKind::Address); + + let err = runner + .submit_transaction(&signer.secret_key, transaction) + .unwrap_err(); + assert_eq!(err.kind, EngineErrorKind::NotAllowed); +} + +#[test] +fn test_submit_with_removing_entries() { + let (mut runner, signer, receiver) = initialize_transfer(); + let sender = utils::address_from_secret_key(&signer.secret_key); + let caller: AccountId = CALLER_ACCOUNT_ID.parse().unwrap(); + let transaction = utils::transfer(receiver, TRANSFER_AMOUNT, INITIAL_NONCE.into()); + + set_silo_params(&mut runner, Some(SILO_PARAMS_ARGS)); + + // Allow to submit transactions. + add_account_to_whitelist(&mut runner, caller.clone()); + add_address_to_whitelist(&mut runner, sender); + + validate_address_balance_and_nonce(&runner, sender, INITIAL_BALANCE, INITIAL_NONCE.into()); + validate_address_balance_and_nonce(&runner, receiver, ZERO_BALANCE, INITIAL_NONCE.into()); + + // perform transfer + let result = runner + .submit_transaction(&signer.secret_key, transaction.clone()) + .unwrap(); + assert!(matches!(result.status, TransactionStatus::Succeed(_))); + + // validate post-state + validate_address_balance_and_nonce( + &runner, + sender, + INITIAL_BALANCE - TRANSFER_AMOUNT - FEE, + (INITIAL_NONCE + 1).into(), + ); + validate_address_balance_and_nonce(&runner, receiver, TRANSFER_AMOUNT, INITIAL_NONCE.into()); + + // Remove account id and address from white lists. + remove_account_from_whitelist(&mut runner, caller); + remove_address_from_whitelist(&mut runner, sender); + + // perform transfer + let err = runner + .submit_transaction(&signer.secret_key, transaction) + .unwrap_err(); + assert_eq!(err.kind, EngineErrorKind::NotAllowed); + + // validate post-state + validate_address_balance_and_nonce( + &runner, + sender, + INITIAL_BALANCE - TRANSFER_AMOUNT - FEE, + (INITIAL_NONCE + 1).into(), + ); + validate_address_balance_and_nonce(&runner, receiver, TRANSFER_AMOUNT, INITIAL_NONCE.into()); +} + +#[test] +fn test_deploy_access_rights() { + let (mut runner, signer, _) = initialize_transfer(); + let sender = utils::address_from_secret_key(&signer.secret_key); + let code: Vec = { + let mut rng = rand::thread_rng(); + let len = rng.gen_range(512..=1024); + let mut buf = vec![0u8; len]; + rng.fill_bytes(&mut buf); + buf + }; + let caller: AccountId = CALLER_ACCOUNT_ID.parse().unwrap(); + let deploy_tx = utils::create_deploy_transaction(code.clone(), INITIAL_NONCE.into()); + // Check that caller's balance is enough. + let balance = runner.get_balance(sender); + assert_eq!(balance, INITIAL_BALANCE); + + set_silo_params(&mut runner, Some(SILO_PARAMS_ARGS)); + + // Try to deploy code without adding to admins white list. + let err = runner + .submit_transaction(&signer.secret_key, deploy_tx.clone()) + .unwrap_err(); + assert_eq!(err.kind, EngineErrorKind::NotAllowed); + + // Check that the balance and the nonce haven't been changed. + validate_address_balance_and_nonce(&runner, sender, INITIAL_BALANCE, INITIAL_NONCE.into()); + + // Add caller's account id and sender address to admins list to allow deploying a code. + add_admin(&mut runner, caller); + add_evm_admin(&mut runner, sender); + + // Deploy that code + let result = runner + .submit_transaction(&signer.secret_key, deploy_tx) + .unwrap(); + let address = Address::try_from_slice(utils::unwrap_success_slice(&result)).unwrap(); + + // Confirm the code stored at that address is equal to the input code. + let stored_code = runner.get_code(address); + assert_eq!(code, stored_code); + + // Check that the balance and the nonce haven't been changed. + validate_address_balance_and_nonce( + &runner, + sender, + INITIAL_BALANCE - FEE, + (INITIAL_NONCE + 1).into(), + ); +} + +#[test] +fn test_deploy_with_disabled_whitelist() { + let (mut runner, signer, _) = initialize_transfer(); + let sender = utils::address_from_secret_key(&signer.secret_key); + let code: Vec = { + let mut rng = rand::thread_rng(); + let len = rng.gen_range(512..=1024); + let mut buf = vec![0u8; len]; + rng.fill_bytes(&mut buf); + buf + }; + let deploy_tx = utils::create_deploy_transaction(code.clone(), INITIAL_NONCE.into()); + // Check that caller's balance is enough. + let balance = runner.get_balance(sender); + assert_eq!(balance, INITIAL_BALANCE); + + set_silo_params(&mut runner, Some(SILO_PARAMS_ARGS)); + + // Try to deploy code without adding to admins white list. + let err = runner + .submit_transaction(&signer.secret_key, deploy_tx.clone()) + .unwrap_err(); + assert_eq!(err.kind, EngineErrorKind::NotAllowed); + + // Check that the balance and the nonce haven't been changed. + validate_address_balance_and_nonce(&runner, sender, INITIAL_BALANCE, INITIAL_NONCE.into()); + + // Disable whitelists. + disable_whitelist(&mut runner, WhitelistKind::Admin); + disable_whitelist(&mut runner, WhitelistKind::EvmAdmin); + + // Deploy that code + let result = runner + .submit_transaction(&signer.secret_key, deploy_tx) + .unwrap(); + let address = Address::try_from_slice(utils::unwrap_success_slice(&result)).unwrap(); + + // Confirm the code stored at that address is equal to the input code. + let stored_code = runner.get_code(address); + assert_eq!(code, stored_code); + + // Check that the balance and the nonce haven't been changed. + validate_address_balance_and_nonce( + &runner, + sender, + INITIAL_BALANCE - FEE, + (INITIAL_NONCE + 1).into(), + ); +} + +#[test] +fn test_switch_between_fix_gas_cost() { + const TRANSFER: Wei = Wei::new_u64(10_000_000); + let (mut runner, mut signer, receiver) = initialize_transfer(); + let sender = utils::address_from_secret_key(&signer.secret_key); + let caller: AccountId = CALLER_ACCOUNT_ID.parse().unwrap(); + + add_account_to_whitelist(&mut runner, caller); + add_address_to_whitelist(&mut runner, sender); + + // validate pre-state + validate_address_balance_and_nonce(&runner, sender, INITIAL_BALANCE, INITIAL_NONCE.into()); + validate_address_balance_and_nonce(&runner, receiver, ZERO_BALANCE, INITIAL_NONCE.into()); + + // Defining gas cost in transaction + // do transfer + let result = runner + .submit_with_signer(&mut signer, |nonce| { + let mut tx = utils::transfer(receiver, TRANSFER, nonce); + tx.gas_limit = 30_0000.into(); + tx.gas_price = 1.into(); + tx + }) + .unwrap(); + + // validate post-state + validate_address_balance_and_nonce( + &runner, + sender, + INITIAL_BALANCE - TRANSFER - Wei::new_u64(result.gas_used), + (INITIAL_NONCE + 1).into(), + ); + validate_address_balance_and_nonce(&runner, receiver, TRANSFER, 0.into()); + + // Set fixed gas cost + let fixed_gas_cost = Wei::new_u64(1_000_000); + set_silo_params( + &mut runner, + Some(SiloParamsArgs { + fixed_gas_cost, + erc20_fallback_address: ERC20_FALLBACK_ADDRESS, + }), + ); + // Check that fixed gas cost has been set successfully. + assert_eq!(runner.get_fixed_gas_cost(), Some(fixed_gas_cost)); + + let balance_before_transfer = runner.get_balance(sender); + let result = runner + .submit_with_signer(&mut signer, |nonce| { + utils::transfer(receiver, TRANSFER, nonce) + }) + .unwrap(); + assert!(matches!(result.status, TransactionStatus::Succeed(_))); + + let sender_balance = balance_before_transfer - TRANSFER - fixed_gas_cost; + let receiver_balance = TRANSFER + TRANSFER; + + // validate post-state + validate_address_balance_and_nonce(&runner, sender, sender_balance, (INITIAL_NONCE + 2).into()); + validate_address_balance_and_nonce(&runner, receiver, receiver_balance, INITIAL_NONCE.into()); + + // Unset fixed gas cost. Should be used usual gas charge mechanism. + set_silo_params(&mut runner, None); + assert_eq!(runner.get_fixed_gas_cost(), None); + let balance_before_transfer = runner.get_balance(sender); + + // do transfer + let result = runner + .submit_with_signer(&mut signer, |nonce| { + let mut tx = utils::transfer(receiver, TRANSFER, nonce); + tx.gas_limit = 30_0000.into(); + tx.gas_price = 1.into(); + tx + }) + .unwrap(); + + let sender_balance = balance_before_transfer - TRANSFER - Wei::new_u64(result.gas_used); + let receiver_balance = TRANSFER + TRANSFER + TRANSFER; + + // validate post-state + validate_address_balance_and_nonce(&runner, sender, sender_balance, (INITIAL_NONCE + 3).into()); + validate_address_balance_and_nonce(&runner, receiver, receiver_balance, INITIAL_NONCE.into()); +} + +#[test] +#[should_panic(expected = "SILO_MODE_IS_OFF")] +fn test_set_fixed_gas_cost_in_disabled_silo_mode() { + let mut runner = utils::deploy_runner(); + set_fixed_gas_cost(&mut runner, Some(FEE)); +} + +#[test] +#[should_panic(expected = "FIXED_GAS_COST_IS_NONE")] +fn test_set_none_fixed_gas_cost() { + let mut runner = utils::deploy_runner(); + set_fixed_gas_cost(&mut runner, None); +} + +#[test] +fn test_set_fixed_gas_cost() { + let mut runner = utils::deploy_runner(); + set_silo_params(&mut runner, Some(SILO_PARAMS_ARGS)); + set_fixed_gas_cost(&mut runner, Some(FEE)); +} + +fn initialize_transfer() -> (AuroraRunner, utils::Signer, Address) { + // set up Aurora runner and accounts + let mut runner = utils::deploy_runner(); + let mut rng = rand::thread_rng(); + let (source_address, source_account) = keys(&mut rng); + runner.create_address(source_address, INITIAL_BALANCE, INITIAL_NONCE.into()); + let (dest_address, _) = keys(&mut rng); + let mut signer = utils::Signer::new(source_account); + signer.nonce = INITIAL_NONCE; + + (runner, signer, dest_address) +} + +fn keys(rng: &mut ThreadRng) -> (Address, SecretKey) { + let sk = SecretKey::random(rng); + let address = utils::address_from_secret_key(&sk); + (address, sk) +} + +fn add_admin(runner: &mut AuroraRunner, account_id: AccountId) { + let args = WhitelistArgs::WhitelistAccountArgs(WhitelistAccountArgs { + kind: WhitelistKind::Admin, + account_id, + }); + call_function(runner, "add_entry_to_whitelist", args); +} + +fn add_evm_admin(runner: &mut AuroraRunner, address: Address) { + let args = WhitelistArgs::WhitelistAddressArgs(WhitelistAddressArgs { + kind: WhitelistKind::EvmAdmin, + address, + }); + call_function(runner, "add_entry_to_whitelist", args); +} + +#[allow(dead_code)] +fn enable_whitelist(runner: &mut AuroraRunner, kind: WhitelistKind) { + let args = WhitelistStatusArgs { kind, active: true }; + call_function(runner, "set_whitelist_status", args); +} + +#[allow(dead_code)] +fn disable_whitelist(runner: &mut AuroraRunner, kind: WhitelistKind) { + let args = WhitelistStatusArgs { + kind, + active: false, + }; + call_function(runner, "set_whitelist_status", args); +} + +fn add_account_to_whitelist(runner: &mut AuroraRunner, account_id: AccountId) { + let args = WhitelistArgs::WhitelistAccountArgs(WhitelistAccountArgs { + kind: WhitelistKind::Account, + account_id, + }); + call_function(runner, "add_entry_to_whitelist", args); +} + +fn add_address_to_whitelist(runner: &mut AuroraRunner, address: Address) { + let args = WhitelistArgs::WhitelistAddressArgs(WhitelistAddressArgs { + kind: WhitelistKind::Address, + address, + }); + call_function(runner, "add_entry_to_whitelist", args); +} + +fn remove_account_from_whitelist(runner: &mut AuroraRunner, account_id: AccountId) { + let args = WhitelistArgs::WhitelistAccountArgs(WhitelistAccountArgs { + kind: WhitelistKind::Account, + account_id, + }); + call_function(runner, "remove_entry_from_whitelist", args); +} + +fn remove_address_from_whitelist(runner: &mut AuroraRunner, address: Address) { + let args = WhitelistArgs::WhitelistAddressArgs(WhitelistAddressArgs { + kind: WhitelistKind::Address, + address, + }); + call_function(runner, "remove_entry_from_whitelist", args); +} + +fn set_fixed_gas_cost(runner: &mut AuroraRunner, cost: Option) { + let args = FixedGasCostArgs { cost }; + call_function(runner, "set_fixed_gas_cost", args); +} + +fn set_silo_params(runner: &mut AuroraRunner, silo_params: Option) { + call_function(runner, "set_silo_params", silo_params); +} + +fn call_function(runner: &mut AuroraRunner, func: &str, args: T) { + let input = args.try_to_vec().unwrap(); + let result = runner.call(func, &runner.aurora_account_id.clone(), input); + assert!( + result.is_ok(), + "{}: {:?}, args: {:?}", + func, + result.unwrap_err(), + args + ); +} + +pub mod workspace { + use super::FEE; + use crate::tests::erc20_connector::workspace::{erc20_balance, exit_to_near}; + use crate::utils::solidity::erc20::ERC20; + use crate::utils::workspace::{ + deploy_engine, deploy_erc20_from_nep_141, deploy_nep_141, nep_141_balance_of, + }; + use aurora_engine_types::parameters::silo::{ + SiloParamsArgs, WhitelistAddressArgs, WhitelistArgs, WhitelistKind, + }; + use aurora_engine_types::types::Address; + use aurora_engine_workspace::{account::Account, parse_near, EngineContract, RawContract}; + + const FT_ACCOUNT: &str = "test_token"; + const FT_TOTAL_SUPPLY: u128 = 1_000_000; + const FT_TRANSFER_AMOUNT: u128 = 300_000; + + #[tokio::test] + async fn test_transfer_nep141_to_non_whitelisted_address() { + let SiloTestContext { + aurora, + fallback_account, + fallback_address, + ft_owner, + ft_owner_address, + nep_141, + erc20, + } = init_silo().await; + + // Transfer tokens from `ft_owner` to non-whitelisted address `ft_owner_address` + transfer_nep_141_to_erc_20( + &nep_141, + &ft_owner, + ft_owner_address, + FT_TRANSFER_AMOUNT, + &aurora, + ) + .await; + + // Verify the nep141 and erc20 tokens balances + assert_eq!( + nep_141_balance_of(&nep_141, &ft_owner.id()).await, + FT_TOTAL_SUPPLY - FT_TRANSFER_AMOUNT + ); + assert_eq!( + nep_141_balance_of(&nep_141, &fallback_account.id()).await, + 0 + ); + assert_eq!( + erc20_balance(&erc20, ft_owner_address, &aurora).await, + 0.into() + ); + assert_eq!( + erc20_balance(&erc20, fallback_address, &aurora).await, + FT_TRANSFER_AMOUNT.into() + ); + + // Transfer tokens from fallback address to fallback near account + let result = exit_to_near( + &fallback_account, + fallback_account.id().as_ref(), + FT_TRANSFER_AMOUNT, + &erc20, + &aurora, + ) + .await; + assert!(result.is_success()); + + // Verify the nep141 and erc20 tokens balances + assert_eq!( + nep_141_balance_of(&nep_141, &ft_owner.id()).await, + FT_TOTAL_SUPPLY - FT_TRANSFER_AMOUNT + ); + assert_eq!( + nep_141_balance_of(&nep_141, &fallback_account.id()).await, + FT_TRANSFER_AMOUNT + ); + assert_eq!( + erc20_balance(&erc20, ft_owner_address, &aurora).await, + 0.into() + ); + assert_eq!( + erc20_balance(&erc20, fallback_address, &aurora).await, + 0.into() + ); + } + + #[tokio::test] + async fn test_transfer_nep141_to_whitelisted_address() { + let SiloTestContext { + aurora, + fallback_account, + fallback_address, + ft_owner, + ft_owner_address, + nep_141, + erc20, + } = init_silo().await; + + add_address_to_whitelist(&aurora, ft_owner_address).await; + + // Transfer tokens from `ft_owner` to whitelisted address `ft_owner_address` + transfer_nep_141_to_erc_20( + &nep_141, + &ft_owner, + ft_owner_address, + FT_TRANSFER_AMOUNT, + &aurora, + ) + .await; + + // Verify the nep141 and erc20 tokens balances + assert_eq!( + nep_141_balance_of(&nep_141, &ft_owner.id()).await, + FT_TOTAL_SUPPLY - FT_TRANSFER_AMOUNT + ); + assert_eq!( + nep_141_balance_of(&nep_141, &fallback_account.id()).await, + 0 + ); + assert_eq!( + erc20_balance(&erc20, ft_owner_address, &aurora).await, + FT_TRANSFER_AMOUNT.into() + ); + assert_eq!( + erc20_balance(&erc20, fallback_address, &aurora).await, + 0.into() + ); + + // Transfer tokens from ft_owner evm address to ft_owner near account + let result = exit_to_near( + &ft_owner, + ft_owner.id().as_ref(), + FT_TRANSFER_AMOUNT, + &erc20, + &aurora, + ) + .await; + assert!(result.is_success()); + + // Verify the nep141 and erc20 tokens balances + assert_eq!( + nep_141_balance_of(&nep_141, &ft_owner.id()).await, + FT_TOTAL_SUPPLY + ); + assert_eq!( + nep_141_balance_of(&nep_141, &fallback_account.id()).await, + 0 + ); + assert_eq!( + erc20_balance(&erc20, ft_owner_address, &aurora).await, + 0.into() + ); + assert_eq!( + erc20_balance(&erc20, fallback_address, &aurora).await, + 0.into() + ); + } + + struct SiloTestContext { + pub aurora: EngineContract, + pub fallback_account: Account, + pub fallback_address: Address, + pub ft_owner: Account, + pub ft_owner_address: Address, + pub nep_141: RawContract, + pub erc20: ERC20, + } + + async fn add_address_to_whitelist(aurora: &EngineContract, address: Address) { + let entry = WhitelistArgs::WhitelistAddressArgs(WhitelistAddressArgs { + kind: WhitelistKind::Address, + address, + }); + let result = aurora + .add_entry_to_whitelist(entry) + .transact() + .await + .unwrap(); + assert!(result.is_success()); + } + + async fn transfer_nep_141_to_erc_20( + nep_141: &RawContract, + source: &Account, + dest: Address, + amount: u128, + aurora: &EngineContract, + ) { + let transfer_args = serde_json::json!({ + "receiver_id": aurora.id(), + "amount": format!("{amount}"), + "msg": dest.encode(), + }); + let result = source + .call(&nep_141.id(), "ft_transfer_call") + .args_json(transfer_args) + .deposit(1) + .max_gas() + .transact() + .await + .unwrap(); + assert!(result.is_success(), "{result:?}"); + } + + /// Deploys the EVM, deploys nep141 contract, and calls `set_silo_params` + async fn init_silo() -> SiloTestContext { + // Deploy Aurora Engine + let aurora = deploy_engine().await; + // Create fallback account and evm address + let fallback_account = aurora + .root() + .create_subaccount("fallback", parse_near!("10 N")) + .await + .unwrap(); + let fallback_address = + aurora_engine_sdk::types::near_account_to_evm_address(fallback_account.id().as_bytes()); + + // Set silo mode + let params = Some(SiloParamsArgs { + fixed_gas_cost: FEE, + erc20_fallback_address: fallback_address, + }); + + let result = aurora.set_silo_params(params).transact().await.unwrap(); + assert!(result.is_success()); + + // Create `ft_owner` account and evm address + let ft_owner = aurora + .root() + .create_subaccount("ft_owner", parse_near!("10 N")) + .await + .unwrap(); + let ft_owner_address = + aurora_engine_sdk::types::near_account_to_evm_address(ft_owner.id().as_bytes()); + + let nep_141_account = aurora + .root() + .create_subaccount(FT_ACCOUNT, parse_near!("10 N")) + .await + .unwrap(); + + // Deploy nep141 token + let nep_141 = deploy_nep_141(&nep_141_account, &ft_owner, FT_TOTAL_SUPPLY, &aurora) + .await + .unwrap(); + + // Call storage deposit for fallback account + let result = aurora + .root() + .call(&nep_141.id(), "storage_deposit") + .args_json(serde_json::json!({ + "account_id": fallback_account.id(), + "registration_only": None:: + })) + .deposit(parse_near!("50 N")) + .transact() + .await + .unwrap(); + assert!(result.is_success()); + + // Deploy erc20 token + let erc20 = deploy_erc20_from_nep_141(nep_141_account.id().as_ref(), &aurora) + .await + .unwrap(); + + // Verify tokens balances + assert_eq!( + nep_141_balance_of(&nep_141, &ft_owner.id()).await, + FT_TOTAL_SUPPLY + ); + assert_eq!( + nep_141_balance_of(&nep_141, &fallback_account.id()).await, + 0 + ); + assert_eq!( + erc20_balance(&erc20, ft_owner_address, &aurora).await, + 0.into() + ); + assert_eq!( + erc20_balance(&erc20, fallback_address, &aurora).await, + 0.into() + ); + + SiloTestContext { + aurora, + fallback_account, + fallback_address, + ft_owner, + ft_owner_address, + nep_141, + erc20, + } + } +} diff --git a/engine-tests/src/tests/standalone/sanity.rs b/engine-tests/src/tests/standalone/sanity.rs index cc303ba9e..3638ac6d9 100644 --- a/engine-tests/src/tests/standalone/sanity.rs +++ b/engine-tests/src/tests/standalone/sanity.rs @@ -43,6 +43,7 @@ fn test_deploy_code() { origin, Wei::zero(), evm_deploy(&code_to_deploy), + None, u64::MAX, Vec::new(), &mut handler, diff --git a/engine-tests/src/tests/standalone/sync.rs b/engine-tests/src/tests/standalone/sync.rs index 3fdbf3337..43f8a7476 100644 --- a/engine-tests/src/tests/standalone/sync.rs +++ b/engine-tests/src/tests/standalone/sync.rs @@ -1,8 +1,12 @@ -use aurora_engine::deposit_event::TokenMessageData; +#[cfg(not(feature = "ext-connector"))] +use aurora_engine::contract_methods::connector::deposit_event::TokenMessageData; use aurora_engine_modexp::AuroraModExp; use aurora_engine_sdk::env::{Env, Timestamp}; +#[cfg(not(feature = "ext-connector"))] use aurora_engine_types::borsh::{BorshDeserialize, BorshSerialize}; -use aurora_engine_types::types::{Address, Balance, Fee, NEP141Wei, Wei}; +use aurora_engine_types::types::{Address, Balance, Wei}; +#[cfg(not(feature = "ext-connector"))] +use aurora_engine_types::types::{Fee, NEP141Wei}; use aurora_engine_types::{account_id::AccountId, H160, H256, U256}; use engine_standalone_storage::sync; @@ -38,6 +42,7 @@ fn test_consume_block_message() { runner.close(); } +#[cfg(not(feature = "ext-connector"))] #[test] fn test_consume_deposit_message() { let (mut runner, block_message) = initialize(); @@ -292,7 +297,6 @@ fn test_consume_deploy_erc20_message() { fn test_consume_ft_on_transfer_message() { // Only need to check the case of aurora calling `ft_on_transfer` on itself, the other case // is handled in the `test_consume_deploy_erc20_message` above. - let (mut runner, block_message) = initialize(); let mint_amount = 8_675_309; @@ -313,13 +317,19 @@ fn test_consume_ft_on_transfer_message() { }; let tx_kind = sync::types::TransactionKind::FtOnTransfer(args); let raw_input = tx_kind.raw_bytes(); + #[cfg(not(feature = "ext-connector"))] + let caller = runner.env.predecessor_account_id(); + #[cfg(feature = "ext-connector")] + let caller = crate::utils::standalone::mocks::EXT_ETH_CONNECTOR + .parse() + .unwrap(); let transaction_message = sync::types::TransactionMessage { block_hash: block_message.hash, near_receipt_id: H256([8u8; 32]), position: 0, succeeded: true, signer: runner.env.signer_account_id(), - caller: runner.env.predecessor_account_id(), + caller, attached_near: 0, transaction: tx_kind, promise_data: Vec::new(), @@ -443,7 +453,11 @@ fn test_consume_submit_message() { assert_eq!(runner.get_nonce(&signer_address), U256::one()); } +#[cfg(not(feature = "ext-connector"))] fn mock_proof(recipient_address: Address, deposit_amount: Wei) -> aurora_engine::proof::Proof { + use aurora_engine::contract_methods::connector::deposit_event::{ + DepositedEvent, DEPOSITED_EVENT, + }; let eth_custodian_address = utils::standalone::mocks::ETH_CUSTODIAN_ADDRESS; let fee = Fee::new(NEP141Wei::new(0)); @@ -452,7 +466,7 @@ fn mock_proof(recipient_address: Address, deposit_amount: Wei) -> aurora_engine: TokenMessageData::parse_event_message_and_prepare_token_message_data(&message, fee) .unwrap(); - let deposit_event = aurora_engine::deposit_event::DepositedEvent { + let deposit_event = DepositedEvent { eth_custodian_address, sender: Address::new(H160([0u8; 20])), token_message_data, @@ -461,8 +475,8 @@ fn mock_proof(recipient_address: Address, deposit_amount: Wei) -> aurora_engine: }; let event_schema = ethabi::Event { - name: aurora_engine::deposit_event::DEPOSITED_EVENT.into(), - inputs: aurora_engine::deposit_event::DepositedEvent::event_params(), + name: DEPOSITED_EVENT.into(), + inputs: DepositedEvent::event_params(), anonymous: false, }; let log_entry = aurora_engine_types::parameters::connector::LogEntry { diff --git a/engine-tests/src/tests/standalone/tracing.rs b/engine-tests/src/tests/standalone/tracing.rs index 153f00ef5..616a574bf 100644 --- a/engine-tests/src/tests/standalone/tracing.rs +++ b/engine-tests/src/tests/standalone/tracing.rs @@ -212,7 +212,7 @@ fn check_transaction_trace>(trace: &TransactionTrace, expected_tr }; assert_eq!(trace.logs().0.len(), expected_trace.len()); - for (log, step) in trace.logs().0.iter().zip(expected_trace.into_iter()) { + for (log, step) in trace.logs().0.iter().zip(expected_trace) { assert_eq!( log.program_counter.0, step.pc, "Program counters should match" diff --git a/engine-tests/src/tests/standard_precompiles.rs b/engine-tests/src/tests/standard_precompiles.rs index d227216d7..0f965e6a3 100644 --- a/engine-tests/src/tests/standard_precompiles.rs +++ b/engine-tests/src/tests/standard_precompiles.rs @@ -60,13 +60,13 @@ fn profile_modexp() { #[test] fn profile_ecadd() { let profile = precompile_execution_profile("test_ecadd"); - utils::assert_gas_bound(profile.all_gas(), 5); + utils::assert_gas_bound(profile.all_gas(), 6); } #[test] fn profile_ecmul() { let profile = precompile_execution_profile("test_ecmul"); - utils::assert_gas_bound(profile.all_gas(), 6); + utils::assert_gas_bound(profile.all_gas(), 7); } #[test] diff --git a/engine-tests/src/tests/uniswap.rs b/engine-tests/src/tests/uniswap.rs index ae4dfe522..ed97ae29f 100644 --- a/engine-tests/src/tests/uniswap.rs +++ b/engine-tests/src/tests/uniswap.rs @@ -38,7 +38,7 @@ fn test_uniswap_input_multihop() { let (_amount_out, _evm_gas, profile) = context.exact_input(&tokens, INPUT_AMOUNT.into()); - assert_eq!(114, profile.all_gas() / 1_000_000_000_000); + assert_eq!(115, profile.all_gas() / 1_000_000_000_000); } #[test] diff --git a/engine-tests/src/tests/xcc.rs b/engine-tests/src/tests/xcc.rs index c70e71060..d4c8ea6ae 100644 --- a/engine-tests/src/tests/xcc.rs +++ b/engine-tests/src/tests/xcc.rs @@ -704,7 +704,11 @@ pub mod workspace { engine_balance_after_xcc.max(engine_balance_before_xcc) - engine_balance_after_xcc.min(engine_balance_before_xcc) < 10_000_000_000_000_000_000_000, - "Engine lost too much NEAR funding xcc: Before={engine_balance_before_xcc} After={engine_balance_after_xcc}", + "Engine lost too much NEAR funding xcc: Before={:?} After={:?} Eq={:?}", + engine_balance_before_xcc, + engine_balance_after_xcc, + engine_balance_after_xcc.max(engine_balance_before_xcc) + - engine_balance_after_xcc.min(engine_balance_before_xcc) ); let router_balance = aurora.node.get_balance(&router_account_id).await.unwrap(); @@ -752,7 +756,6 @@ pub mod workspace { async fn init_xcc() -> anyhow::Result { let aurora = deploy_engine().await; let chain_id = aurora.get_chain_id().await?.result.as_u64(); - let xcc_wasm_bytes = super::contract_bytes(); let result = aurora.factory_update(xcc_wasm_bytes).transact().await?; assert!(result.is_success()); @@ -851,7 +854,7 @@ pub mod workspace { } async fn get_engine_near_balance(aurora: &EngineContract) -> u128 { - aurora.ft_balance_of(&aurora.id()).await.unwrap().result.0 + nep_141_balance_of(aurora.as_raw_contract(), &aurora.id()).await } pub async fn deploy_wnear(aurora: &EngineContract) -> anyhow::Result { diff --git a/engine-tests/src/utils/mocked_external.rs b/engine-tests/src/utils/mocked_external.rs index 13718d4d4..d3fcd3560 100644 --- a/engine-tests/src/utils/mocked_external.rs +++ b/engine-tests/src/utils/mocked_external.rs @@ -56,7 +56,7 @@ impl near_vm_logic::External for MockedExternalWithTrie { fn storage_get<'a>( &'a self, key: &[u8], - mode: near_vm_logic::StorageGetMode, + mode: StorageGetMode, ) -> Result>, VMLogicError> { self.increment_new_trie_node_count(MAINNET_AVERAGE_TOUCHED_TRIE_PER_READ); self.increment_cached_trie_node_count(MAINNET_AVERAGE_READ_CACHED_TRIE_PER_READ); diff --git a/engine-tests/src/utils/mod.rs b/engine-tests/src/utils/mod.rs index 047379ee4..b33613533 100644 --- a/engine-tests/src/utils/mod.rs +++ b/engine-tests/src/utils/mod.rs @@ -2,7 +2,15 @@ use aurora_engine::engine::{EngineError, EngineErrorKind, GasPaymentError}; use aurora_engine::parameters::{SubmitArgs, ViewCallArgs}; use aurora_engine_types::account_id::AccountId; use aurora_engine_types::borsh::{BorshDeserialize, BorshSerialize}; -use aurora_engine_types::types::{NEP141Wei, PromiseResult}; +#[cfg(not(feature = "ext-connector"))] +use aurora_engine_types::parameters::connector::FungibleTokenMetadata; +#[cfg(feature = "ext-connector")] +use aurora_engine_types::parameters::connector::{ + SetEthConnectorContractAccountArgs, WithdrawSerializeType, +}; +use aurora_engine_types::parameters::engine::{NewCallArgs, NewCallArgsV4}; +use aurora_engine_types::parameters::silo::FixedGasCostArgs; +use aurora_engine_types::types::PromiseResult; use evm::ExitFatal; use libsecp256k1::{self, Message, PublicKey, SecretKey}; use near_primitives::runtime::config_store::RuntimeConfigStore; @@ -19,11 +27,9 @@ use near_vm_runner::MockCompiledContractCache; use rlp::RlpStream; use std::borrow::Cow; -use crate::prelude::fungible_token::{FungibleToken, FungibleTokenMetadata}; -use crate::prelude::parameters::{ - InitCallArgs, LegacyNewCallArgs, RelayerKeyManagerArgs, StartHashchainArgs, SubmitResult, - TransactionStatus, -}; +#[cfg(not(feature = "ext-connector"))] +use crate::prelude::parameters::InitCallArgs; +use crate::prelude::parameters::{StartHashchainArgs, SubmitResult, TransactionStatus}; use crate::prelude::transactions::{ eip_1559::{self, SignedTransaction1559, Transaction1559}, eip_2930::{self, SignedTransaction2930, Transaction2930}, @@ -290,51 +296,56 @@ impl AuroraRunner { trie.insert(code_key.to_vec(), code); } - let ft_key = crate::prelude::storage::bytes_to_key( - crate::prelude::storage::KeyPrefix::EthConnector, - &[crate::prelude::storage::EthConnectorStorageId::FungibleToken.into()], - ); - 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_eth_supply_on_near = - current_ft.total_eth_supply_on_near + NEP141Wei::new(init_balance.raw().as_u128()); - current_ft.total_eth_supply_on_aurora = current_ft.total_eth_supply_on_aurora - + NEP141Wei::new(init_balance.raw().as_u128()); - current_ft - }; - - let aurora_balance_key = [ - ft_key.as_slice(), - self.context.current_account_id.as_ref().as_bytes(), - ] - .concat(); - let aurora_balance_value = { - let mut current_balance: u128 = trie - .get(&aurora_balance_key) - .map(|bytes| u128::try_from_slice(bytes).unwrap()) - .unwrap_or_default(); - current_balance += init_balance.raw().as_u128(); - current_balance - }; - - let proof_key = crate::prelude::storage::bytes_to_key( - crate::prelude::storage::KeyPrefix::EthConnector, - &[crate::prelude::storage::EthConnectorStorageId::UsedEvent.into()], - ); - trie.insert(balance_key.to_vec(), balance_value.to_vec()); if !init_nonce.is_zero() { trie.insert(nonce_key.to_vec(), nonce_value.to_vec()); } - trie.insert(ft_key, ft_value.try_to_vec().unwrap()); - trie.insert(proof_key, vec![0]); - trie.insert( - aurora_balance_key, - aurora_balance_value.try_to_vec().unwrap(), - ); + + #[cfg(not(feature = "ext-connector"))] + { + use aurora_engine::contract_methods::connector::fungible_token::FungibleToken; + let ft_key = crate::prelude::storage::bytes_to_key( + crate::prelude::storage::KeyPrefix::EthConnector, + &[crate::prelude::storage::EthConnectorStorageId::FungibleToken.into()], + ); + 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_eth_supply_on_near = current_ft.total_eth_supply_on_near + + aurora_engine_types::types::NEP141Wei::new(init_balance.raw().as_u128()); + current_ft.total_eth_supply_on_aurora = current_ft.total_eth_supply_on_aurora + + aurora_engine_types::types::NEP141Wei::new(init_balance.raw().as_u128()); + current_ft + }; + + let aurora_balance_key = [ + ft_key.as_slice(), + self.context.current_account_id.as_ref().as_bytes(), + ] + .concat(); + let aurora_balance_value = { + let mut current_balance: u128 = trie + .get(&aurora_balance_key) + .map(|bytes| u128::try_from_slice(bytes).unwrap()) + .unwrap_or_default(); + current_balance += init_balance.raw().as_u128(); + current_balance + }; + + let proof_key = crate::prelude::storage::bytes_to_key( + crate::prelude::storage::KeyPrefix::EthConnector, + &[crate::prelude::storage::EthConnectorStorageId::UsedEvent.into()], + ); + + trie.insert(ft_key, ft_value.try_to_vec().unwrap()); + trie.insert(proof_key, vec![0]); + trie.insert( + aurora_balance_key, + aurora_balance_value.try_to_vec().unwrap(), + ); + } if let Some(standalone_runner) = &mut self.standalone_runner { standalone_runner.env.block_height = self.context.block_height; @@ -489,6 +500,15 @@ impl AuroraRunner { self.getter_method_call("get_code", address) } + pub fn get_fixed_gas_cost(&mut self) -> Option { + let outcome = self + .one_shot() + .call("get_fixed_gas_cost", "getter", vec![]) + .unwrap(); + let val = outcome.return_data.as_value()?; + FixedGasCostArgs::try_from_slice(&val).unwrap().cost + } + pub fn get_storage(&self, address: Address, key: H256) -> H256 { let input = aurora_engine::parameters::GetStorageAtArgs { address, @@ -529,29 +549,65 @@ impl AuroraRunner { let standalone_state = standalone_runner.get_current_state(); // The number of keys in standalone_state may be larger because values are never deleted // (they are replaced with a Deleted identifier instead; this is important for replaying transactions). - assert!(self.ext.underlying.fake_trie.len() <= standalone_state.iter().count()); + let fake_trie_len = self.ext.underlying.fake_trie.len(); + let stand_alone_len = standalone_state.iter().count(); + + if fake_trie_len > stand_alone_len { + let fake_keys = self + .ext + .underlying + .fake_trie + .keys() + .map(Clone::clone) + .collect::>(); + let standalone_keys = standalone_state + .iter() + .map(|x| x.0.clone()) + .collect::>(); + let diff = fake_keys.difference(&standalone_keys).collect::>(); + + panic!("The standalone state has fewer amount of keys: {fake_trie_len} vs {stand_alone_len}\nDiff: {diff:?}"); + } + for (key, value) in standalone_state.iter() { let trie_value = self.ext.underlying.fake_trie.get(key).map(Vec::as_slice); let standalone_value = value.value(); assert_eq!( trie_value, standalone_value, - "Standalone mismatch at {key:?}.\nStandlaone: {standalone_value:?}\nWasm: {trie_value:?}", + "Standalone mismatch at {key:?}.\nStandalone: {standalone_value:?}\nWasm: {trie_value:?}", ); } } } -} -impl Default for AuroraRunner { - fn default() -> Self { - let evm_wasm_bytes = if cfg!(feature = "mainnet-test") { - std::fs::read("../bin/aurora-mainnet-test.wasm").unwrap() + pub fn get_engine_code() -> Vec { + let path = if cfg!(feature = "mainnet-test") { + if cfg!(feature = "ext-connector") { + "../bin/aurora-mainnet-silo-test.wasm" + } else { + "../bin/aurora-mainnet-test.wasm" + } } else if cfg!(feature = "testnet-test") { - std::fs::read("../bin/aurora-testnet-test.wasm").unwrap() + if cfg!(feature = "ext-connector") { + "../bin/aurora-testnet-silo-test.wasm" + } else { + "../bin/aurora-testnet-test.wasm" + } } else { panic!("AuroraRunner requires mainnet-test or testnet-test feature enabled.") }; + std::fs::read(path).unwrap() + } + + pub const fn get_default_chain_id() -> u64 { + DEFAULT_CHAIN_ID + } +} + +impl Default for AuroraRunner { + fn default() -> Self { + let evm_wasm_bytes = Self::get_engine_code(); // Fetch config (mainly costs) for the latest protocol version. let runtime_config_store = RuntimeConfigStore::new(None); let runtime_config = runtime_config_store.get_config(PROTOCOL_VERSION); @@ -621,37 +677,44 @@ impl ExecutionProfile { pub fn deploy_runner() -> AuroraRunner { let mut runner = AuroraRunner::default(); let aurora_account_id = str_to_account_id(runner.aurora_account_id.as_str()); - let args = LegacyNewCallArgs { + let args = NewCallArgs::V4(NewCallArgsV4 { chain_id: crate::prelude::u256_to_arr(&U256::from(runner.chain_id)), owner_id: aurora_account_id.clone(), - bridge_prover_id: str_to_account_id("bridge_prover.near"), upgrade_delay_blocks: 1, - }; + key_manager: aurora_account_id, + initial_hashchain: Some([0u8; 32]), + }); let account_id = runner.aurora_account_id.clone(); let result = runner.call("new", &account_id, args.try_to_vec().unwrap()); assert!(result.is_ok()); - let args = InitCallArgs { - prover_account: str_to_account_id("prover.near"), - eth_custodian_address: "d045f7e19B2488924B97F9c145b5E51D0D895A65".to_string(), - metadata: FungibleTokenMetadata::default(), + #[cfg(not(feature = "ext-connector"))] + let result = { + let args = InitCallArgs { + prover_account: str_to_account_id("prover.near"), + eth_custodian_address: "d045f7e19B2488924B97F9c145b5E51D0D895A65".to_string(), + metadata: FungibleTokenMetadata::default(), + }; + runner.call("new_eth_connector", &account_id, args.try_to_vec().unwrap()) }; - let result = runner.call("new_eth_connector", &account_id, args.try_to_vec().unwrap()); - assert!(result.is_ok()); - // Need to set a key manager because that is the only account that can initialize the hashchain - let args = RelayerKeyManagerArgs { - key_manager: Some(aurora_account_id), + #[cfg(feature = "ext-connector")] + let result = { + let args = SetEthConnectorContractAccountArgs { + account: AccountId::new("aurora_eth_connector.root").unwrap(), + withdraw_serialize_type: WithdrawSerializeType::Borsh, + }; + + runner.call( + "set_eth_connector_contract_account", + &account_id, + args.try_to_vec().unwrap(), + ) }; - let result: Result = runner.call( - "set_key_manager", - &account_id, - serde_json::to_vec(&args).unwrap(), - ); + assert!(result.is_ok()); - init_hashchain(&mut runner, &account_id, None); runner } @@ -919,6 +982,8 @@ fn into_engine_error(gas_used: u64, aborted: &FunctionCallError) -> EngineError "ERR_GAS_OVERFLOW" => EngineErrorKind::GasOverflow, "ERR_INTRINSIC_GAS" => EngineErrorKind::IntrinsicGasNotMet, "ERR_INCORRECT_NONCE" => EngineErrorKind::IncorrectNonce, + "ERR_NOT_ALLOWED" => EngineErrorKind::NotAllowed, + "ERR_SAME_OWNER" => EngineErrorKind::SameOwner, "ERR_PAUSED" => EngineErrorKind::EvmFatal(ExitFatal::Other("ERR_PAUSED".into())), msg => EngineErrorKind::EvmFatal(ExitFatal::Other(Cow::Owned(msg.into()))), } diff --git a/engine-tests/src/utils/one_inch/liquidity_protocol.rs b/engine-tests/src/utils/one_inch/liquidity_protocol.rs index 66cb11068..4ac0a7ad2 100644 --- a/engine-tests/src/utils/one_inch/liquidity_protocol.rs +++ b/engine-tests/src/utils/one_inch/liquidity_protocol.rs @@ -6,8 +6,7 @@ use aurora_engine_types::types::Wei; use std::path::PathBuf; use std::sync::Once; -static DOWNLOAD_ONCE: Once = Once::new(); -static COMPILE_ONCE: Once = Once::new(); +static DOWNLOAD_COMPILE_ONCE: Once = Once::new(); pub struct Helper<'a> { pub runner: &'a mut utils::AuroraRunner, @@ -19,7 +18,7 @@ impl<'a> Helper<'a> { &mut self, ) -> (SubmitResult, ExecutionProfile, PoolDeployer) { let artifacts_path = download_and_compile_solidity_sources(); - let deployer_constructor = utils::solidity::ContractConstructor::compile_from_extended_json( + let deployer_constructor = solidity::ContractConstructor::compile_from_extended_json( artifacts_path.join("MooniswapDeployer.sol/MooniswapDeployer.json"), ); let data = deployer_constructor.code; @@ -54,7 +53,7 @@ impl<'a> Helper<'a> { pool_deployer: &PoolDeployer, ) -> (SubmitResult, ExecutionProfile, PoolFactory) { let artifacts_path = download_and_compile_solidity_sources(); - let constructor = utils::solidity::ContractConstructor::compile_from_extended_json( + let constructor = solidity::ContractConstructor::compile_from_extended_json( artifacts_path.join("MooniswapFactory.sol/MooniswapFactory.json"), ); @@ -251,9 +250,5 @@ impl Pool { } fn download_and_compile_solidity_sources() -> PathBuf { - super::download_and_compile_solidity_sources( - "liquidity-protocol", - &DOWNLOAD_ONCE, - &COMPILE_ONCE, - ) + super::download_and_compile_solidity_sources("liquidity-protocol", &DOWNLOAD_COMPILE_ONCE) } diff --git a/engine-tests/src/utils/one_inch/mod.rs b/engine-tests/src/utils/one_inch/mod.rs index 914415bd4..a35947a35 100644 --- a/engine-tests/src/utils/one_inch/mod.rs +++ b/engine-tests/src/utils/one_inch/mod.rs @@ -1,19 +1,22 @@ use std::path::{Path, PathBuf}; use std::process::Command; -use std::sync::Once; +use std::sync::{Condvar, Mutex, Once}; pub mod liquidity_protocol; +static READY: Mutex = Mutex::new(false); +static CV: Condvar = Condvar::new(); + pub fn download_and_compile_solidity_sources( repo_name: &str, - download_once: &'static Once, - compile_once: &'static Once, + download_compile_once: &'static Once, ) -> PathBuf { let sources_dir = Path::new("target").join(repo_name); - if !sources_dir.exists() { - // Contracts not already present, so download them (but only once, even - // if multiple tests running in parallel saw `contracts_dir` does not exist). - download_once.call_once(|| { + + // Contracts not already present, so download and compile them (but only once, even + // if multiple tests running in parallel saw `contracts_dir` does not exist). + download_compile_once.call_once(|| { + if !sources_dir.exists() { let url = format!("https://github.com/1inch/{repo_name}"); let repo = git2::Repository::clone(&url, &sources_dir).unwrap(); if repo_name == "limit-order-protocol" { @@ -26,10 +29,8 @@ pub fn download_and_compile_solidity_sources( let mut opts = git2::build::CheckoutBuilder::new(); repo.checkout_head(Some(opts.force())).unwrap(); } - }); - } + } - compile_once.call_once(|| { // install packages let status = Command::new("/usr/bin/env") .current_dir(&sources_dir) @@ -56,7 +57,17 @@ pub fn download_and_compile_solidity_sources( // clean and compile hardhat("clean"); hardhat("compile"); + + *READY.lock().unwrap() = true; + CV.notify_all(); }); + // Wait for finished compilation. + let mut ready = READY.lock().unwrap(); + + while !*ready { + ready = CV.wait(ready).unwrap(); + } + sources_dir.join("artifacts/contracts") } diff --git a/engine-tests/src/utils/standalone/mocks/mod.rs b/engine-tests/src/utils/standalone/mocks/mod.rs index f43864a57..fd998fb39 100644 --- a/engine-tests/src/utils/standalone/mocks/mod.rs +++ b/engine-tests/src/utils/standalone/mocks/mod.rs @@ -1,20 +1,25 @@ use crate::utils; -use aurora_engine::fungible_token::FungibleTokenMetadata; -use aurora_engine::parameters::{ - FinishDepositCallArgs, InitCallArgs, NEP141FtOnTransferArgs, NewCallArgsV2, -}; -use aurora_engine::{engine, state}; +use aurora_engine::engine; +use aurora_engine::engine::Engine; +#[cfg(not(feature = "ext-connector"))] +use aurora_engine::parameters::InitCallArgs; +#[cfg(not(feature = "ext-connector"))] +use aurora_engine_modexp::ModExpAlgorithm; use aurora_engine_sdk::env::{Env, DEFAULT_PREPAID_GAS}; use aurora_engine_sdk::io::IO; -use aurora_engine_types::types::{make_address, Address, Balance, NEP141Wei, NearGas, Wei}; +#[cfg(not(feature = "ext-connector"))] +use aurora_engine_types::parameters::connector::FungibleTokenMetadata; +use aurora_engine_types::types::{Address, Wei}; use aurora_engine_types::{account_id::AccountId, H256, U256}; use engine_standalone_storage::{BlockMetadata, Storage}; pub mod block; -const DEFAULT_GAS: u64 = 300_000_000_000_000; +#[cfg(not(feature = "ext-connector"))] pub const ETH_CUSTODIAN_ADDRESS: Address = - make_address(0xd045f7e1, 0x9b2488924b97f9c145b5e51d0d895a65); + aurora_engine_types::types::make_address(0xd045f7e1, 0x9b2488924b97f9c145b5e51d0d895a65); +#[cfg(feature = "ext-connector")] +pub const EXT_ETH_CONNECTOR: &str = "aurora_eth_connector.root"; pub fn compute_block_hash(block_height: u64) -> H256 { engine::compute_block_hash([0u8; 32], block_height, b"aurora") @@ -46,21 +51,38 @@ pub fn default_env(block_height: u64) -> aurora_engine_sdk::env::Fixed { } pub fn init_evm(mut io: I, env: &E, chain_id: u64) { + use aurora_engine_types::parameters::engine::NewCallArgsV2; + let new_args = NewCallArgsV2 { chain_id: aurora_engine_types::types::u256_to_arr(&U256::from(chain_id)), owner_id: env.current_account_id(), upgrade_delay_blocks: 1, }; - state::set_state(&mut io, &new_args.into()).unwrap(); + aurora_engine::state::set_state(&mut io, &new_args.into()).unwrap(); +} +#[cfg(feature = "ext-connector")] +pub fn init_connector(io: I) { + use aurora_engine::contract_methods::connector::external::{ + AdminControlled, EthConnectorContract, + }; + use aurora_engine_types::parameters::connector::WithdrawSerializeType; + + let mut connector = EthConnectorContract::init(io).unwrap(); + connector.set_eth_connector_contract_account(&EXT_ETH_CONNECTOR.parse().unwrap()); + connector.set_withdraw_serialize_type(&WithdrawSerializeType::Borsh); +} + +#[cfg(not(feature = "ext-connector"))] +pub fn init_connector(io: I, env: &E) { let connector_args = InitCallArgs { prover_account: utils::str_to_account_id("prover.near"), eth_custodian_address: ETH_CUSTODIAN_ADDRESS.encode(), metadata: FungibleTokenMetadata::default(), }; - aurora_engine::connector::EthConnectorContract::create_contract( + aurora_engine::contract_methods::connector::EthConnectorContract::create_contract( io, &env.current_account_id(), connector_args, @@ -74,14 +96,12 @@ pub fn mint_evm_account( balance: Wei, nonce: U256, code: Option>, - mut io: I, + io: I, env: &E, ) { use evm::backend::ApplyBackend; - let aurora_account_id = env.current_account_id(); - let mut engine: engine::Engine<_, _> = - engine::Engine::new(address, aurora_account_id.clone(), io, env).unwrap(); + let mut engine: Engine<_, _> = Engine::new(address, env.current_account_id(), io, env).unwrap(); let state_change = evm::backend::Apply::Modify { address: address.raw(), basic: evm::backend::Basic { @@ -93,9 +113,24 @@ pub fn mint_evm_account( reset_storage: false, }; - let deposit_args = FinishDepositCallArgs { + #[cfg(not(feature = "ext-connector"))] + deposit(io, &engine, &env.current_account_id(), address, balance); + + engine.apply(std::iter::once(state_change), std::iter::empty(), false); +} + +#[cfg(not(feature = "ext-connector"))] +fn deposit( + mut io: I, + engine: &Engine, + aurora_account_id: &AccountId, + address: Address, + balance: Wei, +) { + const DEFAULT_GAS: u64 = 300_000_000_000_000; + let deposit_args = aurora_engine_types::parameters::connector::FinishDepositCallArgs { new_owner_id: aurora_account_id.clone(), - amount: NEP141Wei::new(balance.raw().as_u128()), + amount: aurora_engine_types::types::NEP141Wei::new(balance.raw().as_u128()), proof_key: String::new(), relayer_id: aurora_account_id.clone(), fee: 0.into(), @@ -109,31 +144,31 @@ pub fn mint_evm_account( ); io.remove_storage(&proof_key); - let mut connector = aurora_engine::connector::EthConnectorContract::init_instance(io).unwrap(); + let mut connector = + aurora_engine::contract_methods::connector::EthConnectorContract::init(io).unwrap(); connector .finish_deposit( aurora_account_id.clone(), aurora_account_id.clone(), deposit_args, - NearGas::new(DEFAULT_GAS), + aurora_engine_types::types::NearGas::new(DEFAULT_GAS), ) .map_err(unsafe_to_string) .unwrap(); - let transfer_args = NEP141FtOnTransferArgs { - sender_id: aurora_account_id, - amount: Balance::new(balance.raw().as_u128()), + let transfer_args = aurora_engine_types::parameters::connector::NEP141FtOnTransferArgs { + sender_id: aurora_account_id.clone(), + amount: aurora_engine_types::types::Balance::new(balance.raw().as_u128()), msg: format!( "aurora:{}{}", hex::encode(Wei::zero().to_bytes()), hex::encode(address.as_bytes()) ), }; - connector.ft_on_transfer(&engine, &transfer_args).unwrap(); - - engine.apply(std::iter::once(state_change), std::iter::empty(), false); + connector.ft_on_transfer(engine, &transfer_args).unwrap(); } -pub fn unsafe_to_string>(e: E) -> String { +#[cfg(not(feature = "ext-connector"))] +fn unsafe_to_string>(e: E) -> String { String::from_utf8(e.as_ref().to_vec()).unwrap() } diff --git a/engine-tests/src/utils/standalone/mod.rs b/engine-tests/src/utils/standalone/mod.rs index cecc4bc2b..b437d9661 100644 --- a/engine-tests/src/utils/standalone/mod.rs +++ b/engine-tests/src/utils/standalone/mod.rs @@ -46,6 +46,10 @@ impl StandaloneRunner { let tx_msg = Self::template_tx_msg(storage, env, 0, transaction_hash, &[], Vec::new()); let result = storage.with_engine_access(env.block_height, 0, &[], |io| { mocks::init_evm(io, env, chain_id); + #[cfg(feature = "ext-connector")] + mocks::init_connector(io); + #[cfg(not(feature = "ext-connector"))] + mocks::init_connector(io, env); }); let outcome = sync::TransactionIncludedOutcome { hash: transaction_hash, @@ -214,12 +218,9 @@ impl StandaloneRunner { PromiseResult::Failed | PromiseResult::NotReady => None, }) .collect(); - let transaction_kind = engine_standalone_storage::sync::parse_transaction_kind( - method_name, - ctx.input.clone(), - &promise_data, - ) - .expect("All method names must be known by standalone"); + let transaction_kind = + sync::parse_transaction_kind(method_name, ctx.input.clone(), &promise_data) + .expect("All method names must be known by standalone"); let transaction_hash = if let TransactionKind::SubmitWithArgs(args) = &transaction_kind { aurora_engine_sdk::keccak(&args.tx_data) diff --git a/engine-tests/src/utils/workspace.rs b/engine-tests/src/utils/workspace.rs index bfdcc3072..c6a1146a8 100644 --- a/engine-tests/src/utils/workspace.rs +++ b/engine-tests/src/utils/workspace.rs @@ -5,6 +5,8 @@ use crate::utils::solidity::erc20::{ERC20Constructor, ERC20}; /// it does not execute promises; but `aurora-workspaces` does. use crate::utils::AuroraRunner; use aurora_engine_types::account_id::AccountId; +#[cfg(feature = "ext-connector")] +use aurora_engine_types::parameters::connector::{FungibleTokenMetadata, WithdrawSerializeType}; use aurora_engine_types::types::Address; use aurora_engine_types::U256; use aurora_engine_workspace::account::Account; @@ -13,13 +15,16 @@ use serde_json::json; const FT_PATH: &str = "src/tests/res/fungible_token.wasm"; const STORAGE_AMOUNT: u128 = 50_000_000_000_000_000_000_000_000; +#[cfg(feature = "ext-connector")] +const AURORA_ETH_CONNECTOR: &str = "aurora_eth_connector"; -pub async fn deploy_engine() -> EngineContract { - let aurora_runner = AuroraRunner::default(); +/// Deploy Aurora smart contract WITHOUT init external eth-connector. +pub async fn deploy_engine_with_code(code: Vec) -> EngineContract { + let chain_id = AuroraRunner::get_default_chain_id(); aurora_engine_workspace::EngineContractBuilder::new() .unwrap() - .with_chain_id(aurora_runner.chain_id) - .with_code(aurora_runner.code.code().to_vec()) + .with_chain_id(chain_id) + .with_code(code) .with_custodian_address("d045f7e19B2488924B97F9c145b5E51D0D895A65") .unwrap() .with_root_balance(parse_near!("10000 N")) @@ -29,6 +34,53 @@ pub async fn deploy_engine() -> EngineContract { .unwrap() } +#[allow(clippy::let_and_return)] +pub async fn deploy_engine() -> EngineContract { + let code = AuroraRunner::get_engine_code(); + let contract = deploy_engine_with_code(code).await; + + #[cfg(feature = "ext-connector")] + init_eth_connector(&contract).await.unwrap(); + + contract +} + +/// Deploy and init external eth connector +#[cfg(feature = "ext-connector")] +async fn init_eth_connector(aurora: &EngineContract) -> anyhow::Result<()> { + let contract_bytes = get_aurora_eth_connector_contract(); + let contract_account = aurora + .root() + .create_subaccount(AURORA_ETH_CONNECTOR, 15 * STORAGE_AMOUNT) + .await + .unwrap(); + let contract = contract_account.deploy(&contract_bytes).await.unwrap(); + let metadata = FungibleTokenMetadata::default(); + let init_args = json!({ + "prover_account": contract_account.id(), + "eth_custodian_address": "096DE9C2B8A5B8c22cEe3289B101f6960d68E51E", + "metadata": metadata, + "account_with_access_right": aurora.id(), + "owner_id": aurora.id() + }); + + let result = contract + .call("new") + .args_json(init_args) + .max_gas() + .transact() + .await?; + assert!(result.is_success()); + + let result = aurora + .set_eth_connector_contract_account(contract_account.id(), WithdrawSerializeType::Borsh) + .transact() + .await?; + assert!(result.is_success()); + + Ok(()) +} + pub async fn create_sub_account( master_account: &Account, account: &str, @@ -85,12 +137,14 @@ pub async fn transfer_nep_141_to_erc_20( pub async fn nep_141_balance_of(nep_141: &RawContract, account_id: &AccountId) -> u128 { nep_141 - .view("ft_balance_of") + .call("ft_balance_of") // XCC requires gas .args_json(json!({ "account_id": account_id })) + .max_gas() + .transact() .await .unwrap() - .json::() - .map(|s| s.parse().unwrap()) + .json::() + .map(|s| s.0) .unwrap() } @@ -160,3 +214,10 @@ pub async fn transfer_nep_141( Ok(()) } + +#[cfg(feature = "ext-connector")] +fn get_aurora_eth_connector_contract() -> Vec { + use std::path::Path; + let contract_path = Path::new("../engine-tests-connector/etc/aurora-eth-connector"); + std::fs::read(contract_path.join("bin/aurora-eth-connector-test.wasm")).unwrap() +} diff --git a/engine-types/src/account_id.rs b/engine-types/src/account_id.rs index af0ea93ff..680f0da8d 100644 --- a/engine-types/src/account_id.rs +++ b/engine-types/src/account_id.rs @@ -2,7 +2,7 @@ //! //! Inspired by: `https://github.com/near/nearcore/tree/master/core/account-id` -use crate::{fmt, str, str::FromStr, Box, String, ToString, Vec}; +use crate::{fmt, str, str::FromStr, AsBytes, Box, String, ToString, Vec}; #[cfg(not(feature = "borsh-compat"))] use borsh::{maybestd::io, BorshDeserialize, BorshSerialize}; #[cfg(feature = "borsh-compat")] @@ -185,6 +185,12 @@ impl From for Vec { } } +impl AsBytes for AccountId { + fn as_bytes(&self) -> &[u8] { + self.as_bytes() + } +} + impl AsRef for AccountId where Box: AsRef, diff --git a/engine-types/src/lib.rs b/engine-types/src/lib.rs index bd9ab7406..ed77baaaf 100644 --- a/engine-types/src/lib.rs +++ b/engine-types/src/lib.rs @@ -42,3 +42,7 @@ mod v0 { } pub use v0::*; + +pub trait AsBytes { + fn as_bytes(&self) -> &[u8]; +} diff --git a/engine-types/src/parameters/connector.rs b/engine-types/src/parameters/connector.rs index 5b3449ea3..5cfe22d74 100644 --- a/engine-types/src/parameters/connector.rs +++ b/engine-types/src/parameters/connector.rs @@ -149,6 +149,38 @@ pub struct NEP141FtOnTransferArgs { pub msg: String, } +#[derive(BorshSerialize)] +pub struct EngineWithdrawCallArgs { + pub sender_id: AccountId, + pub recipient_address: Address, + pub amount: NEP141Wei, +} + +/// `storage_unregister` eth-connector call args +#[derive(Debug, Clone, BorshSerialize, BorshDeserialize, Serialize, Deserialize, PartialEq, Eq)] +pub struct StorageUnregisterCallArgs { + pub force: Option, +} + +#[derive(Debug, Clone, BorshSerialize, BorshDeserialize, PartialEq, Eq)] +pub struct SetEthConnectorContractAccountArgs { + pub account: AccountId, + pub withdraw_serialize_type: WithdrawSerializeType, +} + +#[derive(Debug, Clone, BorshSerialize, BorshDeserialize, PartialEq, Eq)] +pub enum WithdrawSerializeType { + Json, + Borsh, +} + +pub type PausedMask = u8; + +#[derive(Debug, Clone, BorshSerialize, BorshDeserialize, PartialEq, Eq)] +pub struct PauseEthConnectorCallArgs { + pub paused_mask: PausedMask, +} + /// Fungible token Reference hash type. /// Used for `FungibleTokenMetadata` #[derive(Debug, BorshDeserialize, BorshSerialize, Deserialize, Serialize, Clone, PartialEq, Eq)] @@ -220,13 +252,41 @@ impl rlp::Encodable for LogEntry { } } +/// Borsh-encoded parameters for `mirror_erc20_token` function. +#[derive(BorshSerialize, BorshDeserialize, Debug, Eq, PartialEq, Clone)] +pub struct MirrorErc20TokenArgs { + /// AccountId of the main Aurora contract which has previously deployed ERC-20. + pub contract_id: AccountId, + /// AccountId of the bridged NEP-141 token. + pub nep141: AccountId, +} + /// Parameters for `set_erc20_metadata` function. #[derive(BorshDeserialize, BorshSerialize, Debug, Clone, Eq, PartialEq, Serialize, Deserialize)] pub struct SetErc20MetadataArgs { - /// Address of the ERC-20 contract. - pub erc20_address: Address, + /// Address or corresponding NEP-141 account id of the ERC-20 contract. + pub erc20_identifier: Erc20Identifier, /// Metadata of the ERC-20 contract. - pub erc20_metadata: Erc20Metadata, + pub metadata: Erc20Metadata, +} + +#[derive(BorshDeserialize, BorshSerialize, Debug, Clone, Eq, PartialEq, Serialize, Deserialize)] +#[serde(untagged)] +pub enum Erc20Identifier { + Erc20 { address: Address }, + Nep141 { account_id: AccountId }, +} + +impl From
for Erc20Identifier { + fn from(address: Address) -> Self { + Self::Erc20 { address } + } +} + +impl From for Erc20Identifier { + fn from(account_id: AccountId) -> Self { + Self::Nep141 { account_id } + } } /// Metadata of ERC-20 contract. diff --git a/engine-types/src/parameters/engine.rs b/engine-types/src/parameters/engine.rs index 76d641842..06d9f5f09 100644 --- a/engine-types/src/parameters/engine.rs +++ b/engine-types/src/parameters/engine.rs @@ -16,6 +16,7 @@ pub enum NewCallArgs { V1(LegacyNewCallArgs), V2(NewCallArgsV2), V3(NewCallArgsV3), + V4(NewCallArgsV4), } impl NewCallArgs { @@ -25,6 +26,14 @@ impl NewCallArgs { Ok, ) } + + #[must_use] + pub const fn initial_hashchain(&self) -> Option { + match self { + Self::V4(args) => args.initial_hashchain, + Self::V1(_) | Self::V2(_) | Self::V3(_) => None, + } + } } /// Old Borsh-encoded parameters for the `new` function. @@ -66,6 +75,22 @@ pub struct NewCallArgsV3 { pub key_manager: AccountId, } +#[derive(Debug, Clone, PartialEq, Eq, BorshSerialize, BorshDeserialize)] +pub struct NewCallArgsV4 { + /// Chain id, according to the EIP-115 / ethereum-lists spec. + pub chain_id: RawU256, + /// Account which can upgrade this contract. + /// Use empty to disable updatability. + pub owner_id: AccountId, + /// How many blocks after staging upgrade can deploy it. + pub upgrade_delay_blocks: u64, + /// Relayer keys manager. + pub key_manager: AccountId, + /// Initial value of the hashchain. + /// If none is provided then the hashchain will start disabled. + pub initial_hashchain: Option, +} + /// Borsh-encoded parameters for the `set_owner` function. #[derive(Debug, Clone, PartialEq, Eq, BorshSerialize, BorshDeserialize)] #[cfg_attr(feature = "impl-serde", derive(serde::Serialize, serde::Deserialize))] @@ -151,14 +176,6 @@ pub struct RegisterRelayerCallArgs { pub address: Address, } -pub type PausedMask = u8; - -#[derive(Debug, Clone, PartialEq, Eq, BorshSerialize, BorshDeserialize)] -#[cfg_attr(feature = "impl-serde", derive(serde::Serialize, serde::Deserialize))] -pub struct PauseEthConnectorCallArgs { - pub paused_mask: PausedMask, -} - #[derive(Debug, Clone, BorshSerialize, BorshDeserialize, PartialEq, Eq)] pub struct PausePrecompilesCallArgs { pub paused_mask: u32, @@ -309,6 +326,17 @@ pub struct GetStorageAtArgs { pub key: RawH256, } +#[derive(Debug, Clone, PartialEq, Eq, BorshSerialize, BorshDeserialize, Serialize, Deserialize)] +pub struct StorageUnregisterArgs { + pub force: bool, +} + +pub fn parse_json_args<'de, T: Deserialize<'de>>( + bytes: &'de [u8], +) -> Result { + serde_json::from_slice(bytes).map_err(Into::into) +} + /// Parameters for setting relayer keys manager. #[derive(Debug, Clone, Eq, PartialEq, BorshSerialize, BorshDeserialize, Serialize, Deserialize)] pub struct RelayerKeyManagerArgs { @@ -321,34 +349,37 @@ pub struct RelayerKeyArgs { pub public_key: PublicKey, } +pub type FullAccessKeyArgs = RelayerKeyArgs; + pub mod errors { use crate::{account_id::ParseAccountError, String, ToString}; pub const ERR_REVERT: &[u8; 10] = b"ERR_REVERT"; + pub const ERR_NOT_ALLOWED: &[u8; 15] = b"ERR_NOT_ALLOWED"; pub const ERR_OUT_OF_FUNDS: &[u8; 16] = b"ERR_OUT_OF_FUNDS"; pub const ERR_CALL_TOO_DEEP: &[u8; 17] = b"ERR_CALL_TOO_DEEP"; pub const ERR_OUT_OF_OFFSET: &[u8; 17] = b"ERR_OUT_OF_OFFSET"; pub const ERR_OUT_OF_GAS: &[u8; 14] = b"ERR_OUT_OF_GAS"; #[derive(Debug)] - pub enum ParseTypeFromJsonError { + pub enum ParseArgsError { Json(String), InvalidAccount(ParseAccountError), } - impl From for ParseTypeFromJsonError { + impl From for ParseArgsError { fn from(e: serde_json::Error) -> Self { Self::Json(e.to_string()) } } - impl From for ParseTypeFromJsonError { + impl From for ParseArgsError { fn from(e: ParseAccountError) -> Self { Self::InvalidAccount(e) } } - impl AsRef<[u8]> for ParseTypeFromJsonError { + impl AsRef<[u8]> for ParseArgsError { fn as_ref(&self) -> &[u8] { match self { Self::Json(e) => e.as_bytes(), diff --git a/engine-types/src/parameters/mod.rs b/engine-types/src/parameters/mod.rs index 02d74b39e..cc339ad84 100644 --- a/engine-types/src/parameters/mod.rs +++ b/engine-types/src/parameters/mod.rs @@ -3,4 +3,5 @@ pub use promise::*; pub mod connector; pub mod engine; pub mod promise; +pub mod silo; pub mod xcc; diff --git a/engine-types/src/parameters/silo.rs b/engine-types/src/parameters/silo.rs new file mode 100644 index 000000000..31a3c7e59 --- /dev/null +++ b/engine-types/src/parameters/silo.rs @@ -0,0 +1,117 @@ +use crate::account_id::AccountId; +use crate::borsh::{self, BorshDeserialize, BorshSerialize}; +use crate::types::{Address, Wei}; + +#[derive(Debug, Clone, PartialEq, Eq, BorshSerialize, BorshDeserialize)] +pub struct FixedGasCostArgs { + pub cost: Option, +} + +#[derive(Debug, Default, Clone, PartialEq, Eq, BorshSerialize, BorshDeserialize)] +pub struct SiloParamsArgs { + pub fixed_gas_cost: Wei, + pub erc20_fallback_address: Address, +} + +#[derive(Debug, Clone, PartialEq, Eq, BorshSerialize, BorshDeserialize)] +#[cfg_attr( + feature = "impl-serde", + derive(serde::Serialize, serde::Deserialize), + serde(untagged) +)] +pub enum WhitelistArgs { + WhitelistAddressArgs(WhitelistAddressArgs), + WhitelistAccountArgs(WhitelistAccountArgs), +} + +#[derive(Debug, Clone, PartialEq, Eq, BorshSerialize, BorshDeserialize)] +#[cfg_attr(feature = "impl-serde", derive(serde::Serialize, serde::Deserialize))] +pub struct WhitelistAddressArgs { + pub kind: WhitelistKind, + pub address: Address, +} + +#[derive(Debug, Clone, PartialEq, Eq, BorshSerialize, BorshDeserialize)] +#[cfg_attr(feature = "impl-serde", derive(serde::Serialize, serde::Deserialize))] +pub struct WhitelistAccountArgs { + pub kind: WhitelistKind, + pub account_id: AccountId, +} + +#[derive(Debug, Clone, PartialEq, Eq, BorshSerialize, BorshDeserialize)] +pub struct WhitelistStatusArgs { + pub kind: WhitelistKind, + pub active: bool, +} + +#[derive(Debug, Clone, PartialEq, Eq, BorshSerialize, BorshDeserialize)] +pub struct WhitelistKindArgs { + pub kind: WhitelistKind, +} + +#[derive(Debug, Copy, Clone, PartialEq, Eq, BorshSerialize, BorshDeserialize)] +#[cfg_attr(feature = "impl-serde", derive(serde::Serialize, serde::Deserialize))] +pub enum WhitelistKind { + /// The whitelist of this type is for storing NEAR accounts. Accounts stored in this whitelist + /// have an admin role. The admin role allows to add new admins and add new entities + /// (`AccountId` and `Address`) to whitelists. Also, this role allows to deploy of EVM code + /// and submit transactions. + Admin = 0x0, + /// The whitelist of this type is for storing EVM addresses. Addresses included in this + /// whitelist can deploy EVM code. + EvmAdmin = 0x1, + /// The whitelist of this type is for storing NEAR accounts. Accounts included in this + /// whitelist can submit transactions. + Account = 0x2, + /// The whitelist of this type is for storing EVM addresses. Addresses included in this + /// whitelist can submit transactions. + Address = 0x3, +} + +impl From for u8 { + fn from(list: WhitelistKind) -> Self { + match list { + WhitelistKind::Admin => 0x0, + WhitelistKind::EvmAdmin => 0x1, + WhitelistKind::Account => 0x2, + WhitelistKind::Address => 0x3, + } + } +} + +#[test] +fn test_account_whitelist_serialize() { + let args = WhitelistArgs::WhitelistAccountArgs(WhitelistAccountArgs { + account_id: "aurora".parse().unwrap(), + kind: WhitelistKind::Admin, + }); + let bytes = args.try_to_vec().unwrap(); + let args = WhitelistArgs::try_from_slice(&bytes).unwrap(); + + assert_eq!( + args, + WhitelistArgs::WhitelistAccountArgs(WhitelistAccountArgs { + account_id: "aurora".parse().unwrap(), + kind: WhitelistKind::Admin, + }) + ); +} + +#[test] +fn test_address_whitelist_serialize() { + let address = Address::decode("096DE9C2B8A5B8c22cEe3289B101f6960d68E51E").unwrap(); + let args = WhitelistArgs::WhitelistAddressArgs(WhitelistAddressArgs { + address, + kind: WhitelistKind::EvmAdmin, + }); + let bytes = args.try_to_vec().unwrap(); + let args = WhitelistArgs::try_from_slice(&bytes).unwrap(); + + assert_eq!( + args, + WhitelistArgs::WhitelistAddressArgs(WhitelistAddressArgs { + address, + kind: WhitelistKind::EvmAdmin, + }) + ); +} diff --git a/engine-types/src/public_key.rs b/engine-types/src/public_key.rs index cf75a6002..e578bbbbb 100644 --- a/engine-types/src/public_key.rs +++ b/engine-types/src/public_key.rs @@ -107,9 +107,10 @@ impl fmt::Display for PublicKey { } } +#[derive(Debug, Copy, Clone, serde::Deserialize, serde::Serialize)] pub enum KeyType { - Ed25519, - Secp256k1, + Ed25519 = 0, + Secp256k1 = 1, } impl TryFrom for KeyType { @@ -149,6 +150,15 @@ impl FromStr for KeyType { } } +impl From for u8 { + fn from(key_type: KeyType) -> Self { + match key_type { + KeyType::Ed25519 => 0, + KeyType::Secp256k1 => 1, + } + } +} + fn split_key_type_data(value: &str) -> Result<(KeyType, &str), DecodeBs58Error> { if let Some(idx) = value.find(':') { let (prefix, key_data) = value.split_at(idx); @@ -208,3 +218,21 @@ pub enum DecodeBs58Error { BadLength { expected: usize, received: usize }, BadData(String), } + +#[cfg(feature = "std")] +impl fmt::Display for DecodeBs58Error { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + Self::BadLength { expected, received } => { + write!( + f, + "Bad length of date: expected: {expected}, received: {received}" + ) + } + Self::BadData(data) => write!(f, "Bad data: {data}"), + } + } +} + +#[cfg(feature = "std")] +impl std::error::Error for DecodeBs58Error {} diff --git a/engine-types/src/storage.rs b/engine-types/src/storage.rs index ae36d1046..f474d0298 100644 --- a/engine-types/src/storage.rs +++ b/engine-types/src/storage.rs @@ -34,6 +34,8 @@ pub enum KeyPrefix { CrossContractCall = 0xa, RelayerFunctionCallKey = 0xb, Hashchain = 0xc, + Silo = 0xd, + Whitelist = 0xe, } impl From for u8 { @@ -52,6 +54,8 @@ impl From for u8 { KeyPrefix::CrossContractCall => 0xa, KeyPrefix::RelayerFunctionCallKey => 0xb, KeyPrefix::Hashchain => 0xc, + KeyPrefix::Silo => 0xd, + KeyPrefix::Whitelist => 0xe, } } } @@ -65,6 +69,8 @@ pub enum EthConnectorStorageId { PausedMask = 0x3, StatisticsAuroraAccountsCounter = 0x4, FungibleTokenMetadata = 0x5, + EthConnectorAccount = 0x6, + WithdrawSerializationType = 0x7, } impl From for u8 { @@ -76,6 +82,8 @@ impl From for u8 { EthConnectorStorageId::PausedMask => 0x3, EthConnectorStorageId::StatisticsAuroraAccountsCounter => 0x4, EthConnectorStorageId::FungibleTokenMetadata => 0x5, + EthConnectorStorageId::EthConnectorAccount => 0x6, + EthConnectorStorageId::WithdrawSerializationType => 0x7, } } } @@ -99,6 +107,9 @@ impl From for KeyPrefix { 0x9 => Self::Erc20Nep141Map, 0xa => Self::CrossContractCall, 0xb => Self::RelayerFunctionCallKey, + 0xc => Self::Hashchain, + 0xd => Self::Silo, + 0xe => Self::Whitelist, _ => unreachable!("Unknown key prefix"), } } diff --git a/engine-types/src/types/address.rs b/engine-types/src/types/address.rs index da6c8a28a..755d900a1 100755 --- a/engine-types/src/types/address.rs +++ b/engine-types/src/types/address.rs @@ -1,4 +1,4 @@ -use crate::{format, String, H160}; +use crate::{format, AsBytes, String, H160}; #[cfg(not(feature = "borsh-compat"))] use borsh::{maybestd::io, BorshDeserialize, BorshSerialize}; #[cfg(feature = "borsh-compat")] @@ -69,6 +69,12 @@ impl TryFrom<&[u8]> for Address { } } +impl AsBytes for Address { + fn as_bytes(&self) -> &[u8] { + self.as_bytes() + } +} + impl BorshSerialize for Address { fn serialize(&self, writer: &mut W) -> io::Result<()> { writer.write_all(self.0.as_bytes()) diff --git a/engine-types/src/types/balance.rs b/engine-types/src/types/balance.rs index 4a37b10c0..d5b217f63 100644 --- a/engine-types/src/types/balance.rs +++ b/engine-types/src/types/balance.rs @@ -67,18 +67,7 @@ impl<'de> Deserialize<'de> for Balance { } #[derive( - Default, - BorshSerialize, - BorshDeserialize, - Serialize, - Deserialize, - Debug, - Clone, - Copy, - Eq, - PartialEq, - Ord, - PartialOrd, + Default, BorshSerialize, BorshDeserialize, Debug, Clone, Copy, Eq, PartialEq, Ord, PartialOrd, )] /// Near Yocto type which wraps an underlying u128. /// 1 NEAR = 10^24 `yoctoNEAR` @@ -104,6 +93,34 @@ impl Yocto { } } +impl Serialize for Yocto { + fn serialize(&self, serializer: S) -> Result + where + S: Serializer, + { + let value = self.0.to_string(); + serializer.serialize_str(&value) + } +} + +impl<'de> Deserialize<'de> for Yocto { + fn deserialize(deserializer: D) -> Result + where + D: Deserializer<'de>, + D::Error: serde::de::Error, + { + use serde::de::Error; + + let value = serde_json::Value::deserialize(deserializer)?; + Ok(Self( + value + .as_str() + .ok_or_else(|| Error::custom(format!("Wait for a string but got: {value}"))) + .and_then(|value| value.parse().map_err(Error::custom))?, + )) + } +} + impl Add for Yocto { type Output = Self; diff --git a/engine-types/src/types/wei.rs b/engine-types/src/types/wei.rs index 4baefa5d4..b8d2f7402 100644 --- a/engine-types/src/types/wei.rs +++ b/engine-types/src/types/wei.rs @@ -3,13 +3,13 @@ use crate::types::balance::error; use crate::types::Fee; use crate::{format, Add, Display, Sub, SubAssign, ToString, U256}; #[cfg(not(feature = "borsh-compat"))] -use borsh::{BorshDeserialize, BorshSerialize}; +use borsh::{maybestd::io, BorshDeserialize, BorshSerialize}; #[cfg(feature = "borsh-compat")] -use borsh_compat::{self as borsh, BorshDeserialize, BorshSerialize}; +use borsh_compat::{self as borsh, maybestd::io, BorshDeserialize, BorshSerialize}; use serde::{Deserialize, Deserializer, Serialize, Serializer}; pub const ZERO_NEP141_WEI: NEP141Wei = NEP141Wei::new(0); -pub const ZERO_WEI: Wei = Wei::new_u64(0); +pub const ZERO_WEI: Wei = Wei::zero(); /// Wei compatible Borsh-encoded raw value to attach an ETH balance to the transaction pub type WeiU256 = [u8; 32]; @@ -107,7 +107,7 @@ impl Wei { #[must_use] pub const fn zero() -> Self { - Self(U256([0, 0, 0, 0])) + Self(U256::zero()) } #[must_use] @@ -215,6 +215,47 @@ pub fn u256_to_arr(value: &U256) -> [u8; 32] { result } +impl BorshSerialize for Wei { + fn serialize(&self, writer: &mut W) -> io::Result<()> { + writer.write_all(&self.to_bytes()) + } +} + +#[cfg(feature = "borsh-compat")] +impl BorshDeserialize for Wei { + fn deserialize(buf: &mut &[u8]) -> io::Result { + if buf.len() < 32 { + return Err(io::Error::new( + io::ErrorKind::InvalidInput, + "Invalid length of the input", + )); + } + + let mut buffer = [0; 32]; + buffer.copy_from_slice(&buf[..32]); + *buf = &buf[32..]; + + Ok(Self::from(buffer)) + } +} + +#[cfg(not(feature = "borsh-compat"))] +impl BorshDeserialize for Wei { + fn deserialize_reader(reader: &mut R) -> io::Result { + let mut buf = [0u8; 32]; + let maybe_read = reader.read_exact(&mut buf); + + if maybe_read.as_ref().err().map(io::Error::kind) == Some(io::ErrorKind::UnexpectedEof) { + return Err(io::Error::new( + io::ErrorKind::InvalidInput, + "Invalid length of the input", + )); + } + maybe_read?; + Ok(Self::from(buf)) + } +} + #[cfg(test)] mod tests { use super::*; diff --git a/engine-workspace/Cargo.toml b/engine-workspace/Cargo.toml index d002d4def..532db07d6 100644 --- a/engine-workspace/Cargo.toml +++ b/engine-workspace/Cargo.toml @@ -24,3 +24,8 @@ tokio = { workspace = true, features = [ "time" ] } [dev-dependencies] hex.workspace = true lazy_static.workspace = true + +[features] +mainnet-test = [] +testnet-test = [] +ext-connector = [] diff --git a/engine-workspace/src/account.rs b/engine-workspace/src/account.rs index c8b4112a5..c86c85d3c 100644 --- a/engine-workspace/src/account.rs +++ b/engine-workspace/src/account.rs @@ -52,6 +52,7 @@ impl Account { pub fn public_key(&self) -> anyhow::Result { let pk = self.inner.secret_key().public_key(); - PublicKey::from_str(&serde_json::to_string(&pk)?).map_err(|e| anyhow::anyhow!("{e:?}")) + PublicKey::from_str(serde_json::to_string(&pk)?.trim_matches('"')) + .map_err(|e| anyhow::anyhow!("{e:?}")) } } diff --git a/engine-workspace/src/contract.rs b/engine-workspace/src/contract.rs index f7fe70551..a51563be7 100644 --- a/engine-workspace/src/contract.rs +++ b/engine-workspace/src/contract.rs @@ -1,27 +1,38 @@ use crate::account::Account; use crate::node::Node; use crate::operation::{ - CallAddRelayerKey, CallCall, CallDeployCode, CallDeployErc20Token, CallDeployUpgrade, + CallAddEntryToWhitelist, CallAddEntryToWhitelistBatch, CallAddRelayerKey, + CallAttachFullAccessKey, CallCall, CallDeployCode, CallDeployErc20Token, CallDeployUpgrade, CallDeposit, CallFactorySetWNearAddress, CallFactoryUpdate, CallFactoryUpdateAddressVersion, CallFtOnTransfer, CallFtTransfer, CallFtTransferCall, CallFundXccSubAccount, CallMintAccount, - CallNew, CallNewEthConnector, CallPausePrecompiles, CallRefundOnError, CallRegisterRelayer, - CallRemoveRelayerKey, CallResumePrecompiles, CallSetEthConnectorContractData, - CallSetKeyManager, CallSetPausedFlags, CallStageUpgrade, CallStateMigration, - CallStorageDeposit, CallStorageUnregister, CallStorageWithdraw, CallSubmit, CallWithdraw, - ViewAccountsCounter, ViewBalance, ViewBlockHash, ViewBridgeProver, ViewChainId, ViewCode, - ViewErc20FromNep141, ViewFactoryWnearAddress, ViewFtBalanceOf, ViewFtBalanceOfEth, - ViewFtMetadata, ViewFtTotalEthSupplyOnAurora, ViewFtTotalEthSupplyOnNear, ViewFtTotalSupply, - ViewIsUsedProof, ViewNep141FromErc20, ViewNonce, ViewOwner, ViewPausedFlags, - ViewPausedPrecompiles, ViewStorageAt, ViewStorageBalanceOf, ViewUpgradeIndex, ViewVersion, - ViewView, + CallMirrorErc20Token, CallNew, CallNewEthConnector, CallPauseContract, CallPausePrecompiles, + CallRefundOnError, CallRegisterRelayer, CallRemoveEntryFromWhitelist, CallRemoveRelayerKey, + CallResumeContract, CallResumePrecompiles, CallSetErc20Metadata, + CallSetEthConnectorContractAccount, CallSetEthConnectorContractData, CallSetFixedGasCost, + CallSetKeyManager, CallSetOwner, CallSetPausedFlags, CallSetSiloParams, CallSetWhitelistStatus, + CallStageUpgrade, CallStateMigration, CallStorageDeposit, CallStorageUnregister, + CallStorageWithdraw, CallSubmit, CallWithdraw, ViewAccountsCounter, ViewBalance, ViewBlockHash, + ViewBridgeProver, ViewChainId, ViewCode, ViewErc20FromNep141, ViewFactoryWnearAddress, + ViewFtBalanceOf, ViewFtBalanceOfEth, ViewFtMetadata, ViewFtTotalEthSupplyOnAurora, + ViewFtTotalEthSupplyOnNear, ViewFtTotalSupply, ViewGetErc20Metadata, + ViewGetEthConnectorContractAccount, ViewGetFixedGasCost, ViewGetSiloParams, + ViewGetWhitelistStatus, ViewIsUsedProof, ViewNep141FromErc20, ViewNonce, ViewOwner, + ViewPausedFlags, ViewPausedPrecompiles, ViewStorageAt, ViewStorageBalanceOf, ViewUpgradeIndex, + ViewVersion, ViewView, }; use crate::transaction::{CallTransaction, ViewTransaction}; use aurora_engine_types::account_id::AccountId; -use aurora_engine_types::parameters::connector::{FungibleTokenMetadata, Proof}; +use aurora_engine_types::parameters::connector::{ + Erc20Identifier, FungibleTokenMetadata, MirrorErc20TokenArgs, PausedMask, Proof, + SetErc20MetadataArgs, SetEthConnectorContractAccountArgs, WithdrawSerializeType, +}; use aurora_engine_types::parameters::engine::{ - CallArgs, FunctionCallArgsV2, NewCallArgs, NewCallArgsV2, PausedMask, RelayerKeyArgs, + CallArgs, FullAccessKeyArgs, FunctionCallArgsV2, NewCallArgs, NewCallArgsV2, RelayerKeyArgs, RelayerKeyManagerArgs, }; +use aurora_engine_types::parameters::silo::{ + FixedGasCostArgs, SiloParamsArgs, WhitelistArgs, WhitelistKindArgs, WhitelistStatusArgs, +}; use aurora_engine_types::parameters::xcc::FundXccArgs; use aurora_engine_types::types::{Address, RawU256, WeiU256}; use aurora_engine_types::{H256, U256}; @@ -159,6 +170,19 @@ impl EngineContract { )) } + pub fn set_eth_connector_contract_account( + &self, + account_id: AccountId, + withdraw_serialize_type: WithdrawSerializeType, + ) -> CallSetEthConnectorContractAccount { + CallSetEthConnectorContractAccount::call(&self.contract).args_borsh( + SetEthConnectorContractAccountArgs { + account: account_id, + withdraw_serialize_type, + }, + ) + } + pub fn factory_update_address_version( &self, address: Address, @@ -188,6 +212,10 @@ impl EngineContract { CallDeployErc20Token::call(&self.contract).args_borsh(account_id) } + pub fn mirror_erc20_token(&self, args: MirrorErc20TokenArgs) -> CallMirrorErc20Token { + CallMirrorErc20Token::call(&self.contract).args_borsh(args) + } + pub fn call(&self, contract: Address, amount: U256, input: Vec) -> CallCall { let value = WeiU256::from(amount); let args = CallArgs::V2(FunctionCallArgsV2 { @@ -283,6 +311,56 @@ impl EngineContract { pub fn remove_relayer_key(&self, key: RelayerKeyArgs) -> CallRemoveRelayerKey { CallRemoveRelayerKey::call(&self.contract).args_json(key) } + + pub fn pause_contract(&self) -> CallPauseContract { + CallPauseContract::call(&self.contract) + } + + pub fn resume_contract(&self) -> CallResumeContract { + CallResumeContract::call(&self.contract) + } + + pub fn set_fixed_gas_cost(&self, cost: FixedGasCostArgs) -> CallSetFixedGasCost { + CallSetFixedGasCost::call(&self.contract).args_borsh(cost) + } + + pub fn set_silo_params(&self, params: Option) -> CallSetSiloParams { + CallSetSiloParams::call(&self.contract).args_borsh(params) + } + + pub fn set_whitelist_status(&self, status: WhitelistStatusArgs) -> CallSetWhitelistStatus { + CallSetWhitelistStatus::call(&self.contract).args_borsh(status) + } + + pub fn add_entry_to_whitelist(&self, entry: WhitelistArgs) -> CallAddEntryToWhitelist { + CallAddEntryToWhitelist::call(&self.contract).args_borsh(entry) + } + + pub fn add_entry_to_whitelist_batch( + &self, + batch: Vec, + ) -> CallAddEntryToWhitelistBatch { + CallAddEntryToWhitelistBatch::call(&self.contract).args_borsh(batch) + } + + pub fn remove_entry_from_whitelist( + &self, + entry: WhitelistArgs, + ) -> CallRemoveEntryFromWhitelist { + CallRemoveEntryFromWhitelist::call(&self.contract).args_borsh(entry) + } + + pub fn set_erc20_metadata(&self, metadata: SetErc20MetadataArgs) -> CallSetErc20Metadata { + CallSetErc20Metadata::call(&self.contract).args_json(metadata) + } + + pub fn attach_full_access_key(&self, args: FullAccessKeyArgs) -> CallAttachFullAccessKey { + CallAttachFullAccessKey::call(&self.contract).args_json(args) + } + + pub fn set_owner(&self, account: &AccountId) -> CallSetOwner { + CallSetOwner::call(&self.contract).args_borsh(account) + } } /// View functions @@ -392,9 +470,29 @@ impl EngineContract { ViewAccountsCounter::view(&self.contract) } + pub fn get_eth_connector_contract_account(&self) -> ViewGetEthConnectorContractAccount { + ViewGetEthConnectorContractAccount::view(&self.contract) + } + + pub fn get_fixed_gas_cost(&self) -> ViewGetFixedGasCost { + ViewGetFixedGasCost::view(&self.contract) + } + + pub fn get_silo_params(&self) -> ViewGetSiloParams { + ViewGetSiloParams::view(&self.contract) + } + + pub fn get_whitelist_status(&self, args: WhitelistKindArgs) -> ViewGetWhitelistStatus { + ViewGetWhitelistStatus::view(&self.contract).args_borsh(args) + } + pub fn factory_get_wnear_address(&self) -> ViewFactoryWnearAddress { ViewFactoryWnearAddress::view(&self.contract) } + + pub fn get_erc20_metadata(&self, identifier: Erc20Identifier) -> ViewGetErc20Metadata { + ViewGetErc20Metadata::view(&self.contract).args_json(identifier) + } } #[derive(Debug, Clone)] diff --git a/engine-workspace/src/lib.rs b/engine-workspace/src/lib.rs index cc0b7ab22..c47083bbb 100644 --- a/engine-workspace/src/lib.rs +++ b/engine-workspace/src/lib.rs @@ -150,7 +150,7 @@ fn into_chain_id(value: u64) -> [u8; 32] { #[tokio::test] async fn test_creating_aurora_contract() { - let code = std::fs::read("../bin/aurora-mainnet-test.wasm").unwrap(); + let code = get_engine_code().unwrap(); let contract = EngineContractBuilder::new() .unwrap() .with_owner_id("aurora.test.near") @@ -163,3 +163,24 @@ async fn test_creating_aurora_contract() { let chain_id = contract.get_chain_id().await.unwrap().result; assert_eq!(chain_id, U256::from(into_chain_id(AURORA_LOCAL_CHAIN_ID))); } + +#[cfg(test)] +fn get_engine_code() -> anyhow::Result> { + let path = if cfg!(feature = "mainnet-test") { + if cfg!(feature = "ext-connector") { + "../bin/aurora-mainnet-silo-test.wasm" + } else { + "../bin/aurora-mainnet-test.wasm" + } + } else if cfg!(feature = "testnet-test") { + if cfg!(feature = "ext-connector") { + "../bin/aurora-testnet-silo-test.wasm" + } else { + "../bin/aurora-testnet-test.wasm" + } + } else { + anyhow::bail!("Requires mainnet-test or testnet-test feature provided.") + }; + + std::fs::read(path).map_err(Into::into) +} diff --git a/engine-workspace/src/operation.rs b/engine-workspace/src/operation.rs index 02507935a..30b369f72 100644 --- a/engine-workspace/src/operation.rs +++ b/engine-workspace/src/operation.rs @@ -1,6 +1,11 @@ use aurora_engine_types::account_id::AccountId; -use aurora_engine_types::parameters::connector::{FungibleTokenMetadata, WithdrawResult}; +use aurora_engine_types::parameters::connector::{ + Erc20Metadata, FungibleTokenMetadata, WithdrawResult, +}; use aurora_engine_types::parameters::engine::{StorageBalance, SubmitResult, TransactionStatus}; +use aurora_engine_types::parameters::silo::{ + FixedGasCostArgs, SiloParamsArgs, WhitelistStatusArgs, +}; use aurora_engine_types::types::Address; use aurora_engine_types::{H256, U256}; use near_sdk::json_types::U128; @@ -24,6 +29,7 @@ impl_call_return![ CallFactoryUpdateAddressVersion, Call::FactoryUpdateAddressVersion ), + (CallSetOwner, Call::SetOwner), (CallRegisterRelayer, Call::RegisterRelayer), (CallRefundOnError, Call::RefundOnError), (CallFactoryUpdate, Call::FactoryUpdate), @@ -39,6 +45,20 @@ impl_call_return![ (CallSetKeyManager, Call::SetKeyManager), (CallAddRelayerKey, Call::AddRelayerKey), (CallRemoveRelayerKey, Call::RemoveRelayerKey), + ( + CallSetEthConnectorContractAccount, + Call::SetEthConnectorContractAccount + ), + (CallPauseContract, Call::PauseContract), + (CallResumeContract, Call::ResumeContract), + (CallSetFixedGasCost, Call::SetFixedGasCost), + (CallSetSiloParams, Call::SetSiloParams), + (CallSetWhitelistStatus, Call::SetWhitelistStatus), + (CallAddEntryToWhitelist, Call::AddEntryToWhitelist), + (CallAddEntryToWhitelistBatch, Call::AddEntryToWhitelistBatch), + (CallRemoveEntryFromWhitelist, Call::RemoveEntryFromWhitelist), + (CallSetErc20Metadata, Call::SetErc20Metadata), + (CallAttachFullAccessKey, Call::AttachFullAccessKey) ]; impl_call_return![ @@ -49,6 +69,7 @@ impl_call_return![ (CallWithdraw => WithdrawResult, Call::Withdraw, borsh), (CallDeployCode => SubmitResult, Call::DeployCode, borsh), (CallDeployErc20Token => Address, Call::DeployErc20Token, borsh_address), + (CallMirrorErc20Token => Address, Call::MirrorErc20Token, borsh_address), (CallCall => SubmitResult, Call::Call, borsh), (CallSubmit => SubmitResult, Call::Submit, borsh), (CallFtOnTransfer => U128, Call::FtOnTransfer, json), @@ -60,7 +81,7 @@ impl_view_return![ (ViewStorageBalanceOf => StorageBalance, View::StorageBalanceOf, json), (ViewFtMetadata => FungibleTokenMetadata, View::FtMetadata, json), (ViewVersion => String, View::Version, borsh), - (ViewOwner => AccountId, View::Owner, borsh), + (ViewOwner => AccountId, View::Owner, from_bytes), (ViewBridgeProver => AccountId, View::BridgeProver, borsh), (ViewChainId => U256, View::ChainId, borsh_U256), (ViewUpgradeIndex => u64, View::UpgradeIndex, borsh), @@ -79,7 +100,12 @@ impl_view_return![ (ViewNep141FromErc20 => AccountId, View::Nep141FromErc20, borsh), (ViewPausedFlags => u8, View::PausedFlags, borsh), (ViewAccountsCounter => u64, View::AccountsCounter, borsh), - (ViewFactoryWnearAddress => Address, View::FactoryWnearAddress, borsh) + (ViewGetEthConnectorContractAccount => AccountId, View::GetEthConnectorContractAccount, borsh), + (ViewGetFixedGasCost => FixedGasCostArgs, View::GetFixedGasCost, borsh), + (ViewGetSiloParams => SiloParamsArgs, View::GetSiloParams, borsh), + (ViewGetWhitelistStatus => WhitelistStatusArgs, View::GetWhitelistStatus, borsh), + (ViewFactoryWnearAddress => Address, View::FactoryWnearAddress, borsh), + (ViewGetErc20Metadata => Erc20Metadata, View::GetErc20Metadata, json) ]; #[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] @@ -89,8 +115,10 @@ pub(crate) enum Call { NewEthConnector, DeployCode, DeployErc20Token, + MirrorErc20Token, Call, Submit, + SetOwner, RegisterRelayer, FtOnTransfer, Withdraw, @@ -109,6 +137,7 @@ pub(crate) enum Call { FundXccSubAccount, FactorySetWNearAddress, SetEthConnectorContractData, + SetEthConnectorContractAccount, FactoryUpdateAddressVersion, RefundOnError, MintAccount, @@ -116,6 +145,16 @@ pub(crate) enum Call { SetKeyManager, AddRelayerKey, RemoveRelayerKey, + PauseContract, + ResumeContract, + SetFixedGasCost, + SetSiloParams, + SetWhitelistStatus, + AddEntryToWhitelist, + AddEntryToWhitelistBatch, + RemoveEntryFromWhitelist, + SetErc20Metadata, + AttachFullAccessKey, } impl AsRef for Call { @@ -125,8 +164,10 @@ impl AsRef for Call { Call::NewEthConnector => "new_eth_connector", Call::DeployCode => "deploy_code", Call::DeployErc20Token => "deploy_erc20_token", + Call::MirrorErc20Token => "mirror_erc20_token", Call::Call => "call", Call::Submit => "submit", + Call::SetOwner => "set_owner", Call::RegisterRelayer => "register_relayer", Call::FtOnTransfer => "ft_on_transfer", Call::Withdraw => "withdraw", @@ -145,6 +186,7 @@ impl AsRef for Call { Call::FundXccSubAccount => "fund_xcc_sub_account", Call::FactorySetWNearAddress => "factory_set_wnear_address", Call::SetEthConnectorContractData => "set_eth_connector_contract_data", + Call::SetEthConnectorContractAccount => "set_eth_connector_contract_account", Call::FactoryUpdateAddressVersion => "factory_update_address_version", Call::RefundOnError => "refund_on_error", Call::MintAccount => "mint_account", @@ -152,6 +194,16 @@ impl AsRef for Call { Call::SetKeyManager => "set_key_manager", Call::AddRelayerKey => "add_relayer_key", Call::RemoveRelayerKey => "remove_relayer_key", + Call::PauseContract => "pause_contract", + Call::ResumeContract => "resume_contract", + Call::SetFixedGasCost => "set_fixed_gas_cost", + Call::SetSiloParams => "set_silo_params", + Call::SetWhitelistStatus => "set_whitelist_status", + Call::AddEntryToWhitelist => "add_entry_to_whitelist", + Call::AddEntryToWhitelistBatch => "add_entry_to_whitelist_batch", + Call::RemoveEntryFromWhitelist => "remove_entry_from_whitelist", + Call::SetErc20Metadata => "set_erc20_metadata", + Call::AttachFullAccessKey => "attach_full_access_key", } } } @@ -182,7 +234,12 @@ pub enum View { Erc20FromNep141, Nep141FromErc20, AccountsCounter, + GetEthConnectorContractAccount, + GetFixedGasCost, + GetSiloParams, + GetWhitelistStatus, FactoryWnearAddress, + GetErc20Metadata, } impl AsRef for View { @@ -212,7 +269,12 @@ impl AsRef for View { View::Erc20FromNep141 => "get_erc20_from_nep141", View::Nep141FromErc20 => "get_nep141_from_erc20", View::AccountsCounter => "get_accounts_counter", + View::GetEthConnectorContractAccount => "get_eth_connector_contract_account", + View::GetFixedGasCost => "get_fixed_gas_cost", + View::GetSiloParams => "get_silo_params", + View::GetWhitelistStatus => "get_whitelist_status", View::FactoryWnearAddress => "factory_get_wnear_address", + View::GetErc20Metadata => "get_erc20_metadata", } } } diff --git a/engine-workspace/src/result.rs b/engine-workspace/src/result.rs index c341a5b0a..fd8725197 100644 --- a/engine-workspace/src/result.rs +++ b/engine-workspace/src/result.rs @@ -3,6 +3,7 @@ use aurora_engine_types::types::Address; use aurora_engine_types::{H256, U256}; use near_sdk::{json_types::U128, PromiseOrValue}; use serde::de::DeserializeOwned; +use std::fmt::Debug; use workspaces::result::{ExecutionFinalResult, ExecutionOutcome, ViewResultDetails}; use workspaces::types::Gas; @@ -63,6 +64,19 @@ impl ViewResult { } } +impl ViewResult +where + T: for<'a> TryFrom<&'a [u8]>, +{ + pub fn from_bytes(view: ViewResultDetails) -> anyhow::Result { + Ok(Self { + result: T::try_from(view.result.as_slice()) + .map_err(|_| anyhow::anyhow!("couldn't create T from bytes"))?, + logs: view.logs, + }) + } +} + #[derive(Debug)] pub struct ExecutionResult { inner: workspaces::result::ExecutionSuccess, diff --git a/engine/Cargo.toml b/engine/Cargo.toml index 8db44dc1f..b65bef5ee 100644 --- a/engine/Cargo.toml +++ b/engine/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "aurora-engine" -version = "3.1.0" +version = "3.2.0" authors.workspace = true edition.workspace = true homepage.workspace = true @@ -22,7 +22,7 @@ aurora-engine-types.workspace = true aurora-engine-sdk.workspace = true bitflags.workspace = true ethabi.workspace = true -evm.workspace = true +evm = { workspace = true, features = [ "create-fixed" ] } function_name.workspace = true hex.workspace = true rlp.workspace = true @@ -51,3 +51,4 @@ all-promise-actions = ["aurora-engine-sdk/all-promise-actions"] mainnet = ["contract", "log", "aurora-engine-sdk/mainnet"] testnet = ["contract", "log", "aurora-engine-sdk/testnet"] impl-serde = ["aurora-engine-types/impl-serde", "aurora-engine-transactions/impl-serde", "evm/with-serde"] +ext-connector = ["aurora-engine-precompiles/ext-connector"] diff --git a/engine/src/contract_methods/admin.rs b/engine/src/contract_methods/admin.rs index 05c184ba8..a0db2abe1 100644 --- a/engine/src/contract_methods/admin.rs +++ b/engine/src/contract_methods/admin.rs @@ -7,7 +7,7 @@ //! the smart contract and the standalone. use crate::{ - connector::EthConnectorContract, + contract_methods::connector::EthConnectorContract, contract_methods::{ predecessor_address, require_key_manager_only, require_owner_only, require_paused, require_running, ContractError, @@ -19,7 +19,7 @@ use crate::{ Authorizer, EngineAuthorizer, EnginePrecompilesPauser, PausedPrecompilesChecker, PausedPrecompilesManager, PrecompileFlags, }, - state, + state::{self, EngineState}, }; use aurora_engine_hashchain::{bloom::Bloom, hashchain::Hashchain}; use aurora_engine_modexp::AuroraModExp; @@ -29,6 +29,7 @@ use aurora_engine_sdk::{ io::{StorageIntermediate, IO}, promise::PromiseHandler, }; +use aurora_engine_types::parameters::engine::FullAccessKeyArgs; use aurora_engine_types::{ borsh::BorshDeserialize, parameters::{ @@ -48,17 +49,38 @@ const CODE_KEY: &[u8; 4] = b"CODE"; const CODE_STAGE_KEY: &[u8; 10] = b"CODE_STAGE"; #[named] -pub fn new(io: I, env: &E) -> Result<(), ContractError> { - with_hashchain(io, env, function_name!(), |mut io| { - if state::get_state(&io).is_ok() { - return Err(b"ERR_ALREADY_INITIALIZED".into()); - } +pub fn new(mut io: I, env: &E) -> Result<(), ContractError> { + if state::get_state(&io).is_ok() { + return Err(b"ERR_ALREADY_INITIALIZED".into()); + } - let bytes = io.read_input().to_vec(); - let args = NewCallArgs::deserialize(&bytes).map_err(|_| errors::ERR_BORSH_DESERIALIZE)?; - state::set_state(&mut io, &args.into())?; - Ok(()) - }) + let input = io.read_input().to_vec(); + let args = NewCallArgs::deserialize(&input).map_err(|_| errors::ERR_BORSH_DESERIALIZE)?; + + let initial_hashchain = args.initial_hashchain(); + let state: EngineState = args.into(); + + if let Some(block_hashchain) = initial_hashchain { + let block_height = env.block_height(); + let mut hashchain = Hashchain::new( + state.chain_id, + env.current_account_id(), + block_height, + block_hashchain, + ); + + hashchain.add_block_tx( + block_height, + function_name!(), + &input, + &[], + &Bloom::default(), + )?; + crate::hashchain::save_hashchain(&mut io, &hashchain)?; + } + + state::set_state(&mut io, &state)?; + Ok(()) } pub fn get_version(mut io: I) -> Result<(), ContractError> { @@ -94,9 +116,19 @@ pub fn set_owner(io: I, env: &E) -> Result<(), ContractErr }) } -pub fn get_bridge_prover(mut io: I) -> Result<(), ContractError> { - let connector = EthConnectorContract::init_instance(io)?; +pub fn get_bridge_prover(mut io: I) -> Result<(), ContractError> { + let connector = EthConnectorContract::init(io)?; + + #[cfg(not(feature = "ext-connector"))] io.return_output(connector.get_bridge_prover().as_bytes()); + + #[cfg(feature = "ext-connector")] + { + let promise_args = connector.get_bridge_prover(); + let promise_id = unsafe { io.promise_create_call(&promise_args) }; + io.promise_return(promise_id); + } + Ok(()) } @@ -395,6 +427,38 @@ pub fn get_latest_hashchain(io: &mut I) -> Result<(), ContractError> { Ok(()) } +pub fn attach_full_access_key( + io: I, + env: &E, + handler: &mut H, +) -> Result<(), ContractError> { + let state = state::get_state(&io)?; + + require_running(&state)?; + require_owner_only(&state, &env.predecessor_account_id())?; + + let public_key = serde_json::from_slice::(&io.read_input().to_vec()) + .map(|args| args.public_key) + .map_err(|_| errors::ERR_JSON_DESERIALIZE)?; + let current_account_id = env.current_account_id(); + let action = PromiseAction::AddFullAccessKey { + public_key, + nonce: 0, // not actually used - depends on block height + }; + let promise = PromiseBatchAction { + target_account_id: current_account_id, + actions: vec![action], + }; + // SAFETY: This action is dangerous because it adds a new full access key (FAK) to the Engine account. + // However, it is safe to do so here because of the `require_owner_only` check above; only the + // (trusted) owner account can add a new FAK. + let promise_id = unsafe { handler.promise_create_batch(&promise) }; + + handler.promise_return(promise_id); + + Ok(()) +} + fn internal_get_upgrade_index(io: &I) -> Result { match io.read_u64(&storage::bytes_to_key(KeyPrefix::Config, CODE_STAGE_KEY)) { Ok(index) => Ok(index), diff --git a/engine/src/contract_methods/connector.rs b/engine/src/contract_methods/connector.rs deleted file mode 100644 index 999e17116..000000000 --- a/engine/src/contract_methods/connector.rs +++ /dev/null @@ -1,476 +0,0 @@ -use crate::{ - connector::{self, EthConnectorContract}, - contract_methods::{predecessor_address, require_owner_only, require_running, ContractError}, - engine::{self, Engine}, - errors, - hashchain::with_hashchain, - state, -}; -use aurora_engine_modexp::AuroraModExp; -use aurora_engine_sdk::{ - env::Env, - io::{StorageIntermediate, IO}, - promise::PromiseHandler, -}; -use aurora_engine_types::parameters::{ - ExitToNearPrecompileCallbackCallArgs, PromiseAction, PromiseBatchAction, -}; -use aurora_engine_types::{ - borsh::{BorshDeserialize, BorshSerialize}, - parameters::{ - connector::{ - InitCallArgs, NEP141FtOnTransferArgs, ResolveTransferCallArgs, SetContractDataCallArgs, - SetErc20MetadataArgs, StorageDepositCallArgs, StorageWithdrawCallArgs, - TransferCallArgs, TransferCallCallArgs, - }, - engine::{ - errors::ParseTypeFromJsonError, DeployErc20TokenArgs, PauseEthConnectorCallArgs, - SubmitResult, - }, - PromiseWithCallbackArgs, - }, - types::{Address, PromiseResult, Yocto}, - vec, Vec, -}; -use function_name::named; - -#[named] -pub fn ft_on_transfer( - io: I, - env: &E, - handler: &mut H, -) -> Result<(), ContractError> { - with_hashchain(io, env, function_name!(), |io| { - let state = state::get_state(&io)?; - require_running(&state)?; - let current_account_id = env.current_account_id(); - let predecessor_account_id = env.predecessor_account_id(); - let mut engine: Engine<_, E, AuroraModExp> = Engine::new_with_state( - state, - predecessor_address(&predecessor_account_id), - current_account_id.clone(), - io, - env, - ); - - let args: NEP141FtOnTransferArgs = serde_json::from_slice(&io.read_input().to_vec()) - .map_err(Into::::into)?; - - if predecessor_account_id == current_account_id { - EthConnectorContract::init_instance(io)?.ft_on_transfer(&engine, &args)?; - } else { - engine.receive_erc20_tokens( - &predecessor_account_id, - &args, - ¤t_account_id, - handler, - ); - } - Ok(()) - }) -} - -#[named] -pub fn deploy_erc20_token( - io: I, - env: &E, - handler: &mut H, -) -> Result { - with_hashchain(io, env, function_name!(), |mut io| { - require_running(&state::get_state(&io)?)?; - // Id of the NEP141 token in Near - let args: DeployErc20TokenArgs = io.read_input_borsh()?; - - let address = engine::deploy_erc20_token(args, io, env, handler)?; - - io.return_output( - &address - .as_bytes() - .try_to_vec() - .map_err(|_| errors::ERR_SERIALIZE)?, - ); - Ok(address) - }) -} - -#[named] -pub fn exit_to_near_precompile_callback( - io: I, - env: &E, - handler: &mut H, -) -> Result, ContractError> { - with_hashchain(io, env, function_name!(), |io| { - let state = state::get_state(&io)?; - require_running(&state)?; - env.assert_private_call()?; - - // This function should only be called as the callback of - // exactly one promise. - if handler.promise_results_count() != 1 { - return Err(errors::ERR_PROMISE_COUNT.into()); - } - - let args: ExitToNearPrecompileCallbackCallArgs = io.read_input_borsh()?; - - let maybe_result = if let Some(PromiseResult::Successful(_)) = handler.promise_result(0) { - if let Some(args) = args.transfer_near { - let action = PromiseAction::Transfer { - amount: Yocto::new(args.amount), - }; - let promise = PromiseBatchAction { - target_account_id: args.target_account_id, - actions: vec![action], - }; - - // Safety: this call is safe because it comes from the exit to near precompile, not users. - // The call is to transfer the unwrapped wNEAR tokens. - let promise_id = unsafe { handler.promise_create_batch(&promise) }; - handler.promise_return(promise_id); - } - - None - } else if let Some(args) = args.refund { - // Exit call failed; need to refund tokens - let refund_result = engine::refund_on_error(io, env, state, &args, handler)?; - - if !refund_result.status.is_ok() { - return Err(errors::ERR_REFUND_FAILURE.into()); - } - - Some(refund_result) - } else { - None - }; - - Ok(maybe_result) - }) -} - -#[named] -pub fn new_eth_connector(io: I, env: &E) -> Result<(), ContractError> { - with_hashchain(io, env, function_name!(), |io| { - let state = state::get_state(&io)?; - require_running(&state)?; - // Only the owner can initialize the EthConnector - let is_private = env.assert_private_call(); - if is_private.is_err() { - require_owner_only(&state, &env.predecessor_account_id())?; - } - - let args: InitCallArgs = io.read_input_borsh()?; - let owner_id = env.current_account_id(); - - EthConnectorContract::create_contract(io, &owner_id, args)?; - Ok(()) - }) -} - -#[named] -pub fn set_eth_connector_contract_data( - io: I, - env: &E, -) -> Result<(), ContractError> { - with_hashchain(io, env, function_name!(), |mut io| { - let state = state::get_state(&io)?; - require_running(&state)?; - // Only the owner can set the EthConnector contract data - let is_private = env.assert_private_call(); - if is_private.is_err() { - require_owner_only(&state, &env.predecessor_account_id())?; - } - - let args: SetContractDataCallArgs = io.read_input_borsh()?; - connector::set_contract_data(&mut io, args)?; - Ok(()) - }) -} - -#[named] -pub fn withdraw(io: I, env: &E) -> Result, ContractError> { - with_hashchain(io, env, function_name!(), |io| { - require_running(&state::get_state(&io)?)?; - env.assert_one_yocto()?; - let args = io.read_input_borsh()?; - let current_account_id = env.current_account_id(); - let predecessor_account_id = env.predecessor_account_id(); - let result = EthConnectorContract::init_instance(io)?.withdraw_eth_from_near( - ¤t_account_id, - &predecessor_account_id, - &args, - )?; - let result_bytes = result.try_to_vec().map_err(|_| errors::ERR_SERIALIZE)?; - - // We only return the output via IO in the case of standalone. - // In the case of contract we intentionally avoid IO to call Wasm directly. - #[cfg(not(feature = "contract"))] - { - let mut io = io; - io.return_output(&result_bytes); - } - - Ok(result_bytes) - }) -} - -#[named] -pub fn deposit( - io: I, - env: &E, - handler: &mut H, -) -> Result { - with_hashchain(io, env, function_name!(), |io| { - require_running(&state::get_state(&io)?)?; - let raw_proof = io.read_input().to_vec(); - let current_account_id = env.current_account_id(); - let predecessor_account_id = env.predecessor_account_id(); - let promise_args = EthConnectorContract::init_instance(io)?.deposit( - raw_proof, - current_account_id, - predecessor_account_id, - )?; - // Safety: this call is safe because it comes from the eth-connector, not users. - // The call is to verify the user-supplied proof for the deposit, with `finish_deposit` - // as a callback. - let promise_id = unsafe { handler.promise_create_with_callback(&promise_args) }; - handler.promise_return(promise_id); - Ok(promise_args) - }) -} - -#[named] -pub fn finish_deposit( - io: I, - env: &E, - handler: &mut H, -) -> Result, ContractError> { - with_hashchain(io, env, function_name!(), |io| { - require_running(&state::get_state(&io)?)?; - env.assert_private_call()?; - - // Check result from proof verification call - if handler.promise_results_count() != 1 { - return Err(errors::ERR_PROMISE_COUNT.into()); - } - let promise_result = match handler.promise_result(0) { - Some(PromiseResult::Successful(bytes)) => { - bool::try_from_slice(&bytes).map_err(|_| errors::ERR_PROMISE_ENCODING)? - } - _ => return Err(errors::ERR_PROMISE_FAILED.into()), - }; - if !promise_result { - return Err(errors::ERR_VERIFY_PROOF.into()); - } - - let data = io.read_input_borsh()?; - let current_account_id = env.current_account_id(); - let predecessor_account_id = env.predecessor_account_id(); - let maybe_promise_args = EthConnectorContract::init_instance(io)?.finish_deposit( - predecessor_account_id, - current_account_id, - data, - env.prepaid_gas(), - )?; - - if let Some(promise_args) = maybe_promise_args.as_ref() { - // Safety: this call is safe because it comes from the eth-connector, not users. - // The call will be to the Engine's ft_transfer_call`, which is needed as part - // of the bridge flow (if depositing ETH to an Aurora address). - let promise_id = unsafe { handler.promise_create_with_callback(promise_args) }; - handler.promise_return(promise_id); - } - - Ok(maybe_promise_args) - }) -} - -#[named] -pub fn ft_transfer(io: I, env: &E) -> Result<(), ContractError> { - with_hashchain(io, env, function_name!(), |io| { - require_running(&state::get_state(&io)?)?; - env.assert_one_yocto()?; - let predecessor_account_id = env.predecessor_account_id(); - let args: TransferCallArgs = serde_json::from_slice(&io.read_input().to_vec()) - .map_err(Into::::into)?; - EthConnectorContract::init_instance(io)?.ft_transfer(&predecessor_account_id, &args)?; - Ok(()) - }) -} - -#[named] -pub fn ft_resolve_transfer( - io: I, - env: &E, - handler: &H, -) -> Result<(), ContractError> { - with_hashchain(io, env, function_name!(), |io| { - require_running(&state::get_state(&io)?)?; - - env.assert_private_call()?; - if handler.promise_results_count() != 1 { - return Err(errors::ERR_PROMISE_COUNT.into()); - } - - let args: ResolveTransferCallArgs = io.read_input().to_value()?; - let promise_result = handler - .promise_result(0) - .ok_or(errors::ERR_PROMISE_ENCODING)?; - - EthConnectorContract::init_instance(io)?.ft_resolve_transfer(&args, promise_result); - Ok(()) - }) -} - -#[named] -pub fn ft_transfer_call( - io: I, - env: &E, - handler: &mut H, -) -> Result { - with_hashchain(io, env, function_name!(), |io| { - require_running(&state::get_state(&io)?)?; - // Check is payable - env.assert_one_yocto()?; - - let args: TransferCallCallArgs = serde_json::from_slice(&io.read_input().to_vec()) - .map_err(Into::::into)?; - let current_account_id = env.current_account_id(); - let predecessor_account_id = env.predecessor_account_id(); - let promise_args = EthConnectorContract::init_instance(io)?.ft_transfer_call( - predecessor_account_id, - current_account_id, - args, - env.prepaid_gas(), - )?; - // Safety: this call is safe. It is required by the NEP-141 spec that `ft_transfer_call` - // creates a call to another contract's `ft_on_transfer` method. - let promise_id = unsafe { handler.promise_create_with_callback(&promise_args) }; - handler.promise_return(promise_id); - Ok(promise_args) - }) -} - -#[named] -pub fn storage_deposit( - io: I, - env: &E, - handler: &mut H, -) -> Result<(), ContractError> { - with_hashchain(io, env, function_name!(), |io| { - require_running(&state::get_state(&io)?)?; - let args: StorageDepositCallArgs = serde_json::from_slice(&io.read_input().to_vec()) - .map_err(Into::::into)?; - let predecessor_account_id = env.predecessor_account_id(); - let amount = Yocto::new(env.attached_deposit()); - let maybe_promise = EthConnectorContract::init_instance(io)?.storage_deposit( - predecessor_account_id, - amount, - args, - )?; - if let Some(promise) = maybe_promise { - // Safety: This call is safe. It is only a transfer back to the user in the case - // that they over paid for their deposit. - unsafe { handler.promise_create_batch(&promise) }; - } - Ok(()) - }) -} - -#[named] -pub fn storage_unregister( - io: I, - env: &E, - handler: &mut H, -) -> Result<(), ContractError> { - with_hashchain(io, env, function_name!(), |io| { - require_running(&state::get_state(&io)?)?; - env.assert_one_yocto()?; - let predecessor_account_id = env.predecessor_account_id(); - let force = serde_json::from_slice::(&io.read_input().to_vec()) - .ok() - .and_then(|args| args["force"].as_bool()); - let maybe_promise = EthConnectorContract::init_instance(io)? - .storage_unregister(predecessor_account_id, force)?; - if let Some(promise) = maybe_promise { - // Safety: This call is safe. It is only a transfer back to the user for their deposit. - unsafe { handler.promise_create_batch(&promise) }; - } - Ok(()) - }) -} - -#[named] -pub fn storage_withdraw(io: I, env: &E) -> Result<(), ContractError> { - with_hashchain(io, env, function_name!(), |io| { - require_running(&state::get_state(&io)?)?; - env.assert_one_yocto()?; - let args: StorageWithdrawCallArgs = serde_json::from_slice(&io.read_input().to_vec()) - .map_err(Into::::into)?; - let predecessor_account_id = env.predecessor_account_id(); - EthConnectorContract::init_instance(io)? - .storage_withdraw(&predecessor_account_id, &args)?; - Ok(()) - }) -} - -#[named] -pub fn set_paused_flags(io: I, env: &E) -> Result<(), ContractError> { - with_hashchain(io, env, function_name!(), |io| { - let state = state::get_state(&io)?; - require_running(&state)?; - let is_private = env.assert_private_call(); - if is_private.is_err() { - require_owner_only(&state, &env.predecessor_account_id())?; - } - let args: PauseEthConnectorCallArgs = io.read_input_borsh()?; - EthConnectorContract::init_instance(io)?.set_paused_flags(&args); - Ok(()) - }) -} - -#[named] -pub fn set_erc20_metadata( - io: I, - env: &E, - handler: &mut H, -) -> Result { - with_hashchain(io, env, function_name!(), |io| { - let state = state::get_state(&io)?; - require_running(&state)?; - // TODO: Define special role for this transaction. Potentially via multisig? - let is_private = env.assert_private_call(); - if is_private.is_err() { - require_owner_only(&state, &env.predecessor_account_id())?; - } - - let args: SetErc20MetadataArgs = serde_json::from_slice(&io.read_input().to_vec()) - .map_err(Into::::into)?; - let current_account_id = env.current_account_id(); - let mut engine: Engine<_, E, AuroraModExp> = Engine::new_with_state( - state, - predecessor_address(&env.predecessor_account_id()), - current_account_id, - io, - env, - ); - let result = engine.set_erc20_metadata(args.erc20_address, args.erc20_metadata, handler)?; - - Ok(result) - }) -} - -pub fn get_erc20_metadata(mut io: I, env: &E) -> Result<(), ContractError> { - let erc20_address = io.read_input_arr20().map(Address::from_array)?; - let state = state::get_state(&io)?; - let current_account_id = env.current_account_id(); - let engine: Engine<_, E, AuroraModExp> = Engine::new_with_state( - state, - predecessor_address(&env.predecessor_account_id()), - current_account_id, - io, - env, - ); - let metadata = engine.get_erc20_metadata(erc20_address)?; - - io.return_output(&serde_json::to_vec(&metadata).map_err(|_| errors::ERR_SERIALIZE)?); - Ok(()) -} diff --git a/engine/src/admin_controlled.rs b/engine/src/contract_methods/connector/admin_controlled.rs similarity index 98% rename from engine/src/admin_controlled.rs rename to engine/src/contract_methods/connector/admin_controlled.rs index 99f4aa214..63a867b01 100644 --- a/engine/src/admin_controlled.rs +++ b/engine/src/contract_methods/connector/admin_controlled.rs @@ -1,7 +1,5 @@ pub type PausedMask = u8; -pub const ERR_PAUSED: &str = "ERR_PAUSED"; - pub trait AdminControlled { /// Return the current mask representing all paused events. fn get_paused(&self) -> PausedMask; @@ -30,7 +28,7 @@ pub struct PausedError; impl AsRef<[u8]> for PausedError { fn as_ref(&self) -> &[u8] { - ERR_PAUSED.as_bytes() + crate::errors::ERR_PAUSED } } diff --git a/engine/src/deposit_event.rs b/engine/src/contract_methods/connector/deposit_event.rs similarity index 80% rename from engine/src/deposit_event.rs rename to engine/src/contract_methods/connector/deposit_event.rs index d58403d3c..324504657 100644 --- a/engine/src/deposit_event.rs +++ b/engine/src/contract_methods/connector/deposit_event.rs @@ -1,4 +1,4 @@ -use crate::deposit_event::error::ParseEventMessageError; +use crate::contract_methods::connector::errors; use crate::prelude::account_id::AccountId; use crate::prelude::{ format, vec, Address, BorshDeserialize, BorshSerialize, Fee, NEP141Wei, String, ToString, Vec, @@ -28,26 +28,26 @@ impl FtTransferMessageData { /// Used for `ft_transfer_call` and `ft_on_transfer` pub fn parse_on_transfer_message( message: &str, - ) -> Result { + ) -> Result { // Split message by separator let (account, msg) = message .split_once(':') - .ok_or(error::ParseOnTransferMessageError::TooManyParts)?; + .ok_or(errors::ParseOnTransferMessageError::TooManyParts)?; // Check relayer account id from 1-th data element let account_id = account .parse() - .map_err(|_| error::ParseOnTransferMessageError::InvalidAccount)?; + .map_err(|_| errors::ParseOnTransferMessageError::InvalidAccount)?; // Decode message array from 2-th element of data array // Length = fee[32] + eth_address[20] bytes let mut data = [0; 52]; hex::decode_to_slice(msg, &mut data).map_err(|e| match e { hex::FromHexError::InvalidHexCharacter { .. } | hex::FromHexError::OddLength => { - error::ParseOnTransferMessageError::InvalidHexData + errors::ParseOnTransferMessageError::InvalidHexData } hex::FromHexError::InvalidStringLength => { - error::ParseOnTransferMessageError::WrongMessageFormat + errors::ParseOnTransferMessageError::WrongMessageFormat } })?; @@ -56,7 +56,7 @@ impl FtTransferMessageData { // This logic is for compatibility. let fee_u128: u128 = U256::from_little_endian(&data[..32]) .try_into() - .map_err(|_| error::ParseOnTransferMessageError::OverflowNumber)?; + .map_err(|_| errors::ParseOnTransferMessageError::OverflowNumber)?; let fee: Fee = fee_u128.into(); // Get recipient Eth address from message slice @@ -88,11 +88,11 @@ impl FtTransferMessageData { relayer_account_id: &AccountId, fee: Fee, recipient: String, - ) -> Result { + ) -> Result { let address = if recipient.len() == 42 { recipient .strip_prefix("0x") - .ok_or(ParseEventMessageError::EthAddressValidationError( + .ok_or(errors::ParseEventMessageError::EthAddressValidationError( AddressError::FailedDecodeHex, ))? .to_string() @@ -100,8 +100,8 @@ impl FtTransferMessageData { recipient }; - let recipient_address = - Address::decode(&address).map_err(ParseEventMessageError::EthAddressValidationError)?; + let recipient_address = Address::decode(&address) + .map_err(errors::ParseEventMessageError::EthAddressValidationError)?; Ok(Self { relayer: relayer_account_id.clone(), @@ -131,19 +131,19 @@ impl TokenMessageData { /// Parse event message data for tokens. Data parsed form event `recipient` field. /// Used for Deposit flow. /// For Eth logic flow message validated and prepared for `ft_on_transfer` logic. - /// It mean validating Eth address correctness and preparing message for + /// It means validating Eth address correctness and preparing message for /// parsing for `ft_on_transfer` message parsing with correct and validated data. pub fn parse_event_message_and_prepare_token_message_data( message: &str, fee: Fee, - ) -> Result { + ) -> Result { let data: Vec<_> = message.split(':').collect(); // Data array can contain 1 or 2 elements if data.len() >= 3 { - return Err(ParseEventMessageError::TooManyParts); + return Err(errors::ParseEventMessageError::TooManyParts); } let account_id = AccountId::try_from(data[0].as_bytes()) - .map_err(|_| ParseEventMessageError::InvalidAccount)?; + .map_err(|_| errors::ParseEventMessageError::InvalidAccount)?; // If data array contain only one element it should return NEAR account id if data.len() == 1 { @@ -189,13 +189,13 @@ impl EthEvent { name: &str, params: EventParams, data: &[u8], - ) -> Result { + ) -> Result { let event = Event { name: name.to_string(), inputs: params, anonymous: false, }; - let log_entry: LogEntry = rlp::decode(data).map_err(|_| error::DecodeError::RlpFailed)?; + let log_entry: LogEntry = rlp::decode(data).map_err(|_| errors::DecodeError::RlpFailed)?; let eth_custodian_address = Address::new(log_entry.address); let topics = log_entry.topics.iter().map(|h| Hash::from(h.0)).collect(); @@ -205,7 +205,7 @@ impl EthEvent { }; let log = event .parse_log(raw_log) - .map_err(|_| error::DecodeError::SchemaMismatch)?; + .map_err(|_| errors::DecodeError::SchemaMismatch)?; Ok(Self { eth_custodian_address, @@ -253,14 +253,14 @@ impl DepositedEvent { } /// Parses raw Ethereum logs proof's entry data - pub fn from_log_entry_data(data: &[u8]) -> Result { + pub fn from_log_entry_data(data: &[u8]) -> Result { let event = EthEvent::fetch_log_entry_data(DEPOSITED_EVENT, Self::event_params(), data) - .map_err(error::ParseError::LogParseFailed)?; + .map_err(errors::ParseError::LogParseFailed)?; let raw_sender = event.log.params[0] .value .clone() .into_address() - .ok_or(error::ParseError::InvalidSender)? + .ok_or(errors::ParseError::InvalidSender)? .0; let sender = Address::from_array(raw_sender); @@ -271,18 +271,18 @@ impl DepositedEvent { .value .clone() .into_uint() - .ok_or(error::ParseError::InvalidAmount)? + .ok_or(errors::ParseError::InvalidAmount)? .try_into() .map(NEP141Wei::new) - .map_err(|_| error::ParseError::OverflowNumber)?; + .map_err(|_| errors::ParseError::OverflowNumber)?; let fee = event.log.params[3] .value .clone() .into_uint() - .ok_or(error::ParseError::InvalidFee)? + .ok_or(errors::ParseError::InvalidFee)? .try_into() .map(|v| Fee::new(NEP141Wei::new(v))) - .map_err(|_| error::ParseError::OverflowNumber)?; + .map_err(|_| errors::ParseError::OverflowNumber)?; let token_message_data = TokenMessageData::parse_event_message_and_prepare_token_message_data( @@ -300,92 +300,6 @@ impl DepositedEvent { } } -pub mod error { - use super::AddressError; - use crate::errors; - - #[derive(Debug)] - pub enum DecodeError { - RlpFailed, - SchemaMismatch, - } - impl AsRef<[u8]> for DecodeError { - fn as_ref(&self) -> &[u8] { - match self { - Self::RlpFailed => errors::ERR_RLP_FAILED, - Self::SchemaMismatch => errors::ERR_PARSE_DEPOSIT_EVENT, - } - } - } - - #[cfg_attr(not(target_arch = "wasm32"), derive(Debug))] - pub enum ParseEventMessageError { - TooManyParts, - InvalidAccount, - EthAddressValidationError(AddressError), - } - - impl AsRef<[u8]> for ParseEventMessageError { - fn as_ref(&self) -> &[u8] { - match self { - Self::TooManyParts => errors::ERR_INVALID_EVENT_MESSAGE_FORMAT, - Self::InvalidAccount => errors::ERR_INVALID_ACCOUNT_ID, - Self::EthAddressValidationError(e) => e.as_ref(), - } - } - } - - impl From for ParseError { - fn from(e: ParseEventMessageError) -> Self { - Self::MessageParseFailed(e) - } - } - - #[cfg_attr(not(target_arch = "wasm32"), derive(Debug))] - pub enum ParseError { - LogParseFailed(DecodeError), - InvalidSender, - InvalidAmount, - InvalidFee, - MessageParseFailed(ParseEventMessageError), - OverflowNumber, - } - - impl AsRef<[u8]> for ParseError { - fn as_ref(&self) -> &[u8] { - match self { - Self::LogParseFailed(e) => e.as_ref(), - Self::InvalidSender => errors::ERR_INVALID_SENDER, - Self::InvalidAmount => errors::ERR_INVALID_AMOUNT, - Self::InvalidFee => errors::ERR_INVALID_FEE, - Self::MessageParseFailed(e) => e.as_ref(), - Self::OverflowNumber => errors::ERR_OVERFLOW_NUMBER, - } - } - } - - #[cfg_attr(not(target_arch = "wasm32"), derive(Debug))] - pub enum ParseOnTransferMessageError { - TooManyParts, - InvalidHexData, - WrongMessageFormat, - InvalidAccount, - OverflowNumber, - } - - impl AsRef<[u8]> for ParseOnTransferMessageError { - fn as_ref(&self) -> &[u8] { - match self { - Self::TooManyParts => errors::ERR_INVALID_ON_TRANSFER_MESSAGE_FORMAT, - Self::InvalidHexData => errors::ERR_INVALID_ON_TRANSFER_MESSAGE_HEX, - Self::WrongMessageFormat => errors::ERR_INVALID_ON_TRANSFER_MESSAGE_DATA, - Self::InvalidAccount => errors::ERR_INVALID_ACCOUNT_ID, - Self::OverflowNumber => errors::ERR_OVERFLOW_NUMBER, - } - } - } -} - #[cfg(test)] mod tests { use super::*; diff --git a/engine/src/contract_methods/connector/errors.rs b/engine/src/contract_methods/connector/errors.rs new file mode 100644 index 000000000..6a397502a --- /dev/null +++ b/engine/src/contract_methods/connector/errors.rs @@ -0,0 +1,336 @@ +use aurora_engine_types::{ + types::address::error::AddressError, types::balance::error::BalanceOverflowError, +}; + +use crate::errors; + +#[derive(Debug)] +pub enum StorageReadError { + KeyNotFound, + BorshDeserialize, +} + +impl AsRef<[u8]> for StorageReadError { + fn as_ref(&self) -> &[u8] { + match self { + Self::KeyNotFound => errors::ERR_CONNECTOR_STORAGE_KEY_NOT_FOUND, + Self::BorshDeserialize => errors::ERR_FAILED_DESERIALIZE_CONNECTOR_DATA, + } + } +} + +#[cfg_attr(not(target_arch = "wasm32"), derive(Debug))] +pub enum FinishDepositError { + TransferCall(FtTransferCallError), + ProofUsed, +} + +impl From for FinishDepositError { + fn from(_: ProofUsed) -> Self { + Self::ProofUsed + } +} + +impl From for FinishDepositError { + fn from(e: FtTransferCallError) -> Self { + Self::TransferCall(e) + } +} + +impl From for FinishDepositError { + fn from(e: DepositError) -> Self { + Self::TransferCall(FtTransferCallError::Transfer(e.into())) + } +} + +impl AsRef<[u8]> for FinishDepositError { + fn as_ref(&self) -> &[u8] { + match self { + Self::ProofUsed => errors::ERR_PROOF_EXIST, + Self::TransferCall(e) => e.as_ref(), + } + } +} + +#[derive(Debug)] +pub enum WithdrawError { + FT(WithdrawFtError), + Paused, + ParseArgs, + TotalSupplyUnderflow, + InsufficientFunds, +} + +impl From for WithdrawError { + fn from(e: WithdrawFtError) -> Self { + Self::FT(e) + } +} + +impl AsRef<[u8]> for WithdrawError { + fn as_ref(&self) -> &[u8] { + match self { + Self::FT(e) => e.as_ref(), + Self::ParseArgs => errors::ERR_PARSE_WITHDRAW_EVENT, + Self::Paused => errors::ERR_PAUSED, + Self::TotalSupplyUnderflow => errors::ERR_TOTAL_SUPPLY_UNDERFLOW, + Self::InsufficientFunds => errors::ERR_NOT_ENOUGH_BALANCE, + } + } +} + +impl From for TransferError { + fn from(err: WithdrawError) -> Self { + Self::Withdraw(err) + } +} + +#[cfg_attr(not(target_arch = "wasm32"), derive(Debug))] +pub enum FtTransferCallError { + BalanceOverflow(BalanceOverflowError), + MessageParseFailed(ParseOnTransferMessageError), + InsufficientAmountForFee, + Transfer(TransferError), +} + +impl From for FtTransferCallError { + fn from(e: TransferError) -> Self { + Self::Transfer(e) + } +} + +impl From for FtTransferCallError { + fn from(e: DepositError) -> Self { + Self::Transfer(e.into()) + } +} + +impl From for FtTransferCallError { + fn from(e: ParseOnTransferMessageError) -> Self { + Self::MessageParseFailed(e) + } +} + +impl AsRef<[u8]> for FtTransferCallError { + fn as_ref(&self) -> &[u8] { + match self { + Self::MessageParseFailed(e) => e.as_ref(), + Self::InsufficientAmountForFee => errors::ERR_NOT_ENOUGH_BALANCE_FOR_FEE, + Self::Transfer(e) => e.as_ref(), + Self::BalanceOverflow(e) => e.as_ref(), + } + } +} + +#[cfg_attr(not(target_arch = "wasm32"), derive(Debug))] +pub enum InitContractError { + AlreadyInitialized, + InvalidCustodianAddress(AddressError), +} + +impl AsRef<[u8]> for InitContractError { + fn as_ref(&self) -> &[u8] { + match self { + Self::AlreadyInitialized => errors::ERR_CONTRACT_INITIALIZED, + Self::InvalidCustodianAddress(e) => e.as_ref(), + } + } +} + +#[cfg_attr(not(target_arch = "wasm32"), derive(Debug))] +pub struct ProofUsed; + +impl AsRef<[u8]> for ProofUsed { + fn as_ref(&self) -> &[u8] { + errors::ERR_PROOF_EXIST + } +} + +#[cfg_attr(not(target_arch = "wasm32"), derive(Debug))] +pub enum DepositError { + TotalSupplyOverflow, + BalanceOverflow, + Paused, + ProofParseFailed, + EventParseFailed(ParseError), + CustodianAddressMismatch, + InsufficientAmountForFee, + InvalidAddress(AddressError), +} + +impl AsRef<[u8]> for DepositError { + fn as_ref(&self) -> &[u8] { + match self { + Self::TotalSupplyOverflow => errors::ERR_TOTAL_SUPPLY_OVERFLOW, + Self::BalanceOverflow => errors::ERR_BALANCE_OVERFLOW, + Self::Paused => errors::ERR_PAUSED, + Self::ProofParseFailed => errors::ERR_BORSH_DESERIALIZE.as_bytes(), + Self::EventParseFailed(e) => e.as_ref(), + Self::CustodianAddressMismatch => errors::ERR_WRONG_EVENT_ADDRESS, + Self::InsufficientAmountForFee => errors::ERR_NOT_ENOUGH_BALANCE_FOR_FEE, + Self::InvalidAddress(e) => e.as_ref(), + } + } +} + +#[derive(Debug)] +pub enum WithdrawFtError { + TotalSupplyUnderflow, + InsufficientFunds, + BalanceOverflow(BalanceOverflowError), +} + +impl AsRef<[u8]> for WithdrawFtError { + fn as_ref(&self) -> &[u8] { + match self { + Self::TotalSupplyUnderflow => errors::ERR_TOTAL_SUPPLY_UNDERFLOW, + Self::InsufficientFunds => errors::ERR_NOT_ENOUGH_BALANCE, + Self::BalanceOverflow(e) => e.as_ref(), + } + } +} + +#[cfg_attr(not(target_arch = "wasm32"), derive(Debug))] +pub enum TransferError { + TotalSupplyUnderflow, + TotalSupplyOverflow, + InsufficientFunds, + BalanceOverflow, + ZeroAmount, + SelfTransfer, + Deposit(DepositError), + Withdraw(WithdrawError), +} + +impl AsRef<[u8]> for TransferError { + fn as_ref(&self) -> &[u8] { + match self { + Self::TotalSupplyUnderflow => errors::ERR_TOTAL_SUPPLY_UNDERFLOW, + Self::TotalSupplyOverflow => errors::ERR_TOTAL_SUPPLY_OVERFLOW, + Self::InsufficientFunds => errors::ERR_NOT_ENOUGH_BALANCE, + Self::BalanceOverflow => errors::ERR_BALANCE_OVERFLOW, + Self::ZeroAmount => errors::ERR_ZERO_AMOUNT, + Self::SelfTransfer => errors::ERR_SENDER_EQUALS_RECEIVER, + Self::Deposit(e) => e.as_ref(), + Self::Withdraw(e) => e.as_ref(), + } + } +} + +impl From for TransferError { + fn from(err: WithdrawFtError) -> Self { + match err { + WithdrawFtError::InsufficientFunds => Self::InsufficientFunds, + WithdrawFtError::TotalSupplyUnderflow => Self::TotalSupplyUnderflow, + WithdrawFtError::BalanceOverflow(_) => Self::BalanceOverflow, + } + } +} + +impl From for TransferError { + fn from(err: DepositError) -> Self { + Self::Deposit(err) + } +} + +#[cfg_attr(not(target_arch = "wasm32"), derive(Debug))] +pub enum StorageFundingError { + NotRegistered, + NoAvailableBalance, + InsufficientDeposit, + UnRegisterPositiveBalance, +} + +impl AsRef<[u8]> for StorageFundingError { + fn as_ref(&self) -> &[u8] { + match self { + Self::NotRegistered => errors::ERR_ACCOUNT_NOT_REGISTERED, + Self::NoAvailableBalance => errors::ERR_NO_AVAILABLE_BALANCE, + Self::InsufficientDeposit => errors::ERR_ATTACHED_DEPOSIT_NOT_ENOUGH, + Self::UnRegisterPositiveBalance => { + errors::ERR_FAILED_UNREGISTER_ACCOUNT_POSITIVE_BALANCE + } + } + } +} + +#[cfg_attr(not(target_arch = "wasm32"), derive(Debug))] +pub enum DecodeError { + RlpFailed, + SchemaMismatch, +} +impl AsRef<[u8]> for DecodeError { + fn as_ref(&self) -> &[u8] { + match self { + Self::RlpFailed => errors::ERR_RLP_FAILED, + Self::SchemaMismatch => errors::ERR_PARSE_DEPOSIT_EVENT, + } + } +} + +#[cfg_attr(not(target_arch = "wasm32"), derive(Debug))] +pub enum ParseEventMessageError { + TooManyParts, + InvalidAccount, + EthAddressValidationError(AddressError), +} + +impl AsRef<[u8]> for ParseEventMessageError { + fn as_ref(&self) -> &[u8] { + match self { + Self::TooManyParts => errors::ERR_INVALID_EVENT_MESSAGE_FORMAT, + Self::InvalidAccount => errors::ERR_INVALID_ACCOUNT_ID, + Self::EthAddressValidationError(e) => e.as_ref(), + } + } +} + +impl From for ParseError { + fn from(e: ParseEventMessageError) -> Self { + Self::MessageParseFailed(e) + } +} + +#[cfg_attr(not(target_arch = "wasm32"), derive(Debug))] +pub enum ParseError { + LogParseFailed(DecodeError), + InvalidSender, + InvalidAmount, + InvalidFee, + MessageParseFailed(ParseEventMessageError), + OverflowNumber, +} + +impl AsRef<[u8]> for ParseError { + fn as_ref(&self) -> &[u8] { + match self { + Self::LogParseFailed(e) => e.as_ref(), + Self::InvalidSender => errors::ERR_INVALID_SENDER, + Self::InvalidAmount => errors::ERR_INVALID_AMOUNT, + Self::InvalidFee => errors::ERR_INVALID_FEE, + Self::MessageParseFailed(e) => e.as_ref(), + Self::OverflowNumber => errors::ERR_OVERFLOW_NUMBER, + } + } +} + +#[cfg_attr(not(target_arch = "wasm32"), derive(Debug))] +pub enum ParseOnTransferMessageError { + TooManyParts, + InvalidHexData, + WrongMessageFormat, + InvalidAccount, + OverflowNumber, +} + +impl AsRef<[u8]> for ParseOnTransferMessageError { + fn as_ref(&self) -> &[u8] { + match self { + Self::TooManyParts => errors::ERR_INVALID_ON_TRANSFER_MESSAGE_FORMAT, + Self::InvalidHexData => errors::ERR_INVALID_ON_TRANSFER_MESSAGE_HEX, + Self::WrongMessageFormat => errors::ERR_INVALID_ON_TRANSFER_MESSAGE_DATA, + Self::InvalidAccount => errors::ERR_INVALID_ACCOUNT_ID, + Self::OverflowNumber => errors::ERR_OVERFLOW_NUMBER, + } + } +} diff --git a/engine/src/contract_methods/connector/external.rs b/engine/src/contract_methods/connector/external.rs new file mode 100644 index 000000000..c679722f8 --- /dev/null +++ b/engine/src/contract_methods/connector/external.rs @@ -0,0 +1,614 @@ +use crate::contract_methods::connector::deposit_event::FtTransferMessageData; +use crate::contract_methods::connector::{construct_contract_key, errors, ZERO_ATTACHED_BALANCE}; +use crate::contract_methods::{ + predecessor_address, require_owner_only, require_running, ContractError, +}; +use crate::engine::Engine; +use crate::hashchain::with_hashchain; +use crate::parameters::{BalanceOfEthCallArgs, NEP141FtOnTransferArgs}; +use crate::prelude::PromiseCreateArgs; +use crate::prelude::Wei; +use crate::prelude::{ + sdk, AccountId, Address, EthConnectorStorageId, NearGas, ToString, Vec, Yocto, +}; +use crate::state; +use aurora_engine_modexp::ModExpAlgorithm; +use aurora_engine_sdk::env::{Env, DEFAULT_PREPAID_GAS}; +use aurora_engine_sdk::io::{StorageIntermediate, IO}; +use aurora_engine_sdk::promise::PromiseHandler; +use aurora_engine_types::borsh::{self, BorshDeserialize, BorshSerialize}; +use aurora_engine_types::parameters::connector::{ + EngineWithdrawCallArgs, InitCallArgs, SetEthConnectorContractAccountArgs, + StorageDepositCallArgs, StorageUnregisterCallArgs, StorageWithdrawCallArgs, TransferCallArgs, + TransferCallCallArgs, WithdrawSerializeType, +}; +use aurora_engine_types::parameters::engine::errors::ParseArgsError; +use aurora_engine_types::parameters::{PromiseWithCallbackArgs, WithdrawCallArgs}; +use aurora_engine_types::types::ZERO_WEI; +use function_name::named; + +/// NEAR Gas for calling `finish_deposit` promise. Used in the `deposit` logic. +pub const GAS_FOR_FINISH_DEPOSIT: NearGas = NearGas::new(50_000_000_000_000); +pub const GAS_FOR_DEPOSIT: NearGas = NearGas::new(120_000_000_000_000); +pub const GAS_FOR_WITHDRAW: NearGas = NearGas::new(20_000_000_000_000); +pub const GAS_FOR_FT_TRANSFER: NearGas = NearGas::new(50_000_000_000_000); +pub const GAS_FOR_FT_TRANSFER_CALL: NearGas = NearGas::new(100_000_000_000_000); +pub const VIEW_CALL_GAS: NearGas = NearGas::new(15_000_000_000_000); + +pub fn withdraw( + mut io: I, + env: &E, +) -> Result<(), ContractError> { + require_running(&state::get_state(&io)?)?; + env.assert_one_yocto()?; + let args: WithdrawCallArgs = io.read_input_borsh()?; + let input = EngineWithdrawCallArgs { + sender_id: env.predecessor_account_id(), + recipient_address: args.recipient_address, + amount: args.amount, + } + .try_to_vec() + .unwrap(); + + let promise_args = EthConnectorContract::init(io)?.withdraw_eth_from_near(input); + let promise_id = unsafe { io.promise_create_call(&promise_args) }; + io.promise_return(promise_id); + + Ok(()) +} + +pub fn deposit( + io: I, + _env: &E, + handler: &mut H, +) -> Result, ContractError> { + require_running(&state::get_state(&io)?)?; + let input = io.read_input().to_vec(); + let promise_args = EthConnectorContract::init(io)?.deposit(input); + let promise_id = unsafe { handler.promise_create_call(&promise_args) }; + handler.promise_return(promise_id); + + Ok(None) +} + +pub fn is_used_proof(mut io: I) -> Result<(), ContractError> { + let input = io.read_input().to_vec(); + let promise_args = EthConnectorContract::init(io)?.is_used_proof(input); + let promise_id = unsafe { io.promise_create_call(&promise_args) }; + io.promise_return(promise_id); + + Ok(()) +} + +pub fn ft_total_eth_supply_on_near( + mut io: I, +) -> Result<(), ContractError> { + let promise_args = EthConnectorContract::init(io)?.ft_total_eth_supply_on_near(); + let promise_id = unsafe { io.promise_create_call(&promise_args) }; + io.promise_return(promise_id); + + Ok(()) +} + +pub fn ft_balance_of(mut io: I) -> Result<(), ContractError> { + let input = io.read_input().to_vec(); + let promise_args = EthConnectorContract::init(io)?.ft_balance_of(input); + let promise_id = unsafe { io.promise_create_call(&promise_args) }; + io.promise_return(promise_id); + Ok(()) +} + +pub fn ft_transfer( + mut io: I, + env: &E, +) -> Result<(), ContractError> { + require_running(&state::get_state(&io)?)?; + env.assert_one_yocto()?; + let input = read_json_args(&io).and_then(|args: TransferCallArgs| { + serde_json::to_vec(&( + env.predecessor_account_id(), + args.receiver_id, + args.amount, + args.memo, + )) + .map_err(Into::::into) + })?; + + let promise_arg = EthConnectorContract::init(io)?.ft_transfer(input); + let promise_id = unsafe { io.promise_create_call(&promise_arg) }; + io.promise_return(promise_id); + + Ok(()) +} + +pub fn ft_transfer_call( + io: I, + env: &E, + handler: &mut H, +) -> Result, ContractError> { + require_running(&state::get_state(&io)?)?; + // Check is payable + env.assert_one_yocto()?; + let input = read_json_args(&io).and_then(|args: TransferCallCallArgs| { + serde_json::to_vec(&( + env.predecessor_account_id(), + args.receiver_id, + args.amount, + args.memo, + args.msg, + )) + .map_err(Into::::into) + })?; + + let promise_args = EthConnectorContract::init(io)?.ft_transfer_call(input); + let promise_id = unsafe { handler.promise_create_call(&promise_args) }; + handler.promise_return(promise_id); + + Ok(None) +} + +pub fn ft_on_transfer( + io: I, + env: &E, + handler: &mut H, +) -> Result<(), ContractError> { + let current_account_id = env.current_account_id(); + let predecessor_account_id = env.predecessor_account_id(); + let mut engine: Engine<_, _> = Engine::new( + predecessor_address(&predecessor_account_id), + current_account_id.clone(), + io, + env, + )?; + + let args: NEP141FtOnTransferArgs = read_json_args(&io).map_err(Into::::into)?; + let mut eth_connector = EthConnectorContract::init(io)?; + + if predecessor_account_id == eth_connector.get_eth_connector_contract_account() { + eth_connector.ft_on_transfer(&engine, &args)?; + } else { + engine.receive_erc20_tokens(&predecessor_account_id, &args, ¤t_account_id, handler); + } + + Ok(()) +} + +#[allow(clippy::missing_const_for_fn)] +pub fn finish_deposit( + _: I, + _: E, + _: H, +) -> Result, ContractError> { + Ok(None) +} + +pub fn storage_deposit( + io: I, + env: &E, + handler: &mut H, +) -> Result<(), ContractError> { + require_running(&state::get_state(&io)?)?; + let input = read_json_args(&io).and_then(|args: StorageDepositCallArgs| { + serde_json::to_vec(&( + env.predecessor_account_id(), + args.account_id, + args.registration_only, + )) + .map_err(Into::::into) + })?; + + let promise_args = + EthConnectorContract::init(io)?.storage_deposit(input, env.attached_deposit()); + let promise_id = unsafe { handler.promise_create_call(&promise_args) }; + handler.promise_return(promise_id); + + Ok(()) +} + +pub fn storage_unregister( + io: I, + env: &E, + handler: &mut H, +) -> Result<(), ContractError> { + require_running(&state::get_state(&io)?)?; + env.assert_one_yocto()?; + + let input = read_json_args(&io).and_then(|args: StorageUnregisterCallArgs| { + serde_json::to_vec(&(env.predecessor_account_id(), args.force)) + .map_err(Into::::into) + })?; + + let promise_args = EthConnectorContract::init(io)?.storage_unregister(input); + let promise_id = unsafe { handler.promise_create_call(&promise_args) }; + handler.promise_return(promise_id); + + Ok(()) +} + +pub fn storage_withdraw( + mut io: I, + env: &E, +) -> Result<(), ContractError> { + require_running(&state::get_state(&io)?)?; + env.assert_one_yocto()?; + + let input = read_json_args(&io).and_then(|args: StorageWithdrawCallArgs| { + serde_json::to_vec(&(env.predecessor_account_id(), args.amount)) + .map_err(Into::::into) + })?; + + let promise_args = EthConnectorContract::init(io)?.storage_withdraw(input); + let promise_id = unsafe { io.promise_create_call(&promise_args) }; + + io.promise_return(promise_id); + + Ok(()) +} + +pub fn storage_balance_of(mut io: I) -> Result<(), ContractError> { + let input = io.read_input().to_vec(); + let promise_args = EthConnectorContract::init(io)?.storage_balance_of(input); + let promise_id = unsafe { io.promise_create_call(&promise_args) }; + io.promise_return(promise_id); + + Ok(()) +} + +#[named] +pub fn set_eth_connector_account_id( + io: I, + env: &E, +) -> Result<(), ContractError> { + with_hashchain(io, env, function_name!(), |io| { + let state = state::get_state(&io)?; + require_running(&state)?; + let is_private = env.assert_private_call(); + + if is_private.is_err() { + require_owner_only(&state, &env.predecessor_account_id())?; + } + + let args: SetEthConnectorContractAccountArgs = io.read_input_borsh()?; + let mut connector = EthConnectorContract::init(io)?; + + connector.set_eth_connector_contract_account(&args.account); + connector.set_withdraw_serialize_type(&args.withdraw_serialize_type); + + Ok(()) + }) +} + +pub fn get_eth_connector_account_id(mut io: I) -> Result<(), ContractError> { + let account = EthConnectorContract::init(io)?.get_eth_connector_contract_account(); + let data = account.try_to_vec().unwrap(); // expect(errors::ERR_FAILED_PARSE); + io.return_output(&data); + + Ok(()) +} + +pub fn get_paused_flags(mut io: I) -> Result<(), ContractError> { + let promise_args = EthConnectorContract::init(io)?.get_paused_flags(); + let promise_id = unsafe { io.promise_create_call(&promise_args) }; + io.promise_return(promise_id); + + Ok(()) +} + +pub fn ft_metadata(mut io: I) -> Result<(), ContractError> { + let promise_args = EthConnectorContract::init(io)?.get_metadata(); + let promise_id = unsafe { io.promise_create_call(&promise_args) }; + io.promise_return(promise_id); + + Ok(()) +} + +/// Eth-connector contract. It's stored in the storage. +/// Contains: +/// * connector specific data +/// * Fungible token data +/// * `paused_mask` - admin control flow data +/// * io - I/O trait handler +pub struct EthConnectorContract { + io: I, +} + +/// Eth connector specific data. It always must contain `prover_account` - account id of the smart +/// contract which is used for verifying a proof used in the deposit flow. +#[derive(BorshSerialize, BorshDeserialize)] +pub struct EthConnector { + /// The account id of the Prover NEAR smart contract. It used in the Deposit flow for verifying + /// a log entry from incoming proof. + pub prover_account: AccountId, + /// It is Ethereum address used in the Deposit and Withdraw logic. + pub eth_custodian_address: Address, +} + +impl EthConnectorContract { + /// Init Eth-connector contract instance. + /// Load contract data from storage and init I/O handler. + /// Used as single point of contract access for various contract actions + pub const fn init(io: I) -> Result { + Ok(Self { io }) + } + + /// Create contract data - init eth-connector contract specific data. + /// Used only once for first time initialization. + /// Initialized contract data stored in the storage. + #[allow(clippy::missing_const_for_fn, clippy::needless_pass_by_value)] + pub fn create_contract( + _: I, + _: &AccountId, + _args: InitCallArgs, + ) -> Result<(), errors::InitContractError> { + // NOTE: do nothing + Ok(()) + } + + /// Deposit all types of tokens + pub fn deposit(&self, data: Vec) -> PromiseCreateArgs { + sdk::log!("Call Deposit"); + PromiseCreateArgs { + target_account_id: self.get_eth_connector_contract_account(), + method: "deposit".to_string(), + args: data, + attached_balance: ZERO_ATTACHED_BALANCE, + attached_gas: GAS_FOR_DEPOSIT, + } + } + + /// Withdraw `nETH` from NEAR accounts + /// NOTE: it should be without any log data + pub fn withdraw_eth_from_near(&self, data: Vec) -> PromiseCreateArgs { + PromiseCreateArgs { + target_account_id: self.get_eth_connector_contract_account(), + method: "engine_withdraw".to_string(), + args: data, + attached_balance: Yocto::new(1), + attached_gas: GAS_FOR_WITHDRAW, + } + } + + /// Returns total ETH supply on NEAR (`nETH` as NEP-141 token) + pub fn ft_total_eth_supply_on_near(&mut self) -> PromiseCreateArgs { + PromiseCreateArgs { + target_account_id: self.get_eth_connector_contract_account(), + method: "ft_total_supply".to_string(), + args: Vec::new(), + attached_balance: ZERO_ATTACHED_BALANCE, + attached_gas: VIEW_CALL_GAS, + } + } + + /// Return `nETH` balance (ETH on NEAR). + pub fn ft_balance_of(&self, input: Vec) -> PromiseCreateArgs { + PromiseCreateArgs { + target_account_id: self.get_eth_connector_contract_account(), + method: "ft_balance_of".to_string(), + args: input, + attached_balance: ZERO_ATTACHED_BALANCE, + attached_gas: VIEW_CALL_GAS, + } + } + + /// Return `ETH` balance (ETH on Aurora). + pub fn ft_balance_of_eth_on_aurora( + &mut self, + args: &BalanceOfEthCallArgs, + ) -> Result<(), ParseArgsError> { + let balance = self.internal_unwrap_balance_of_eth_on_aurora(&args.address); + sdk::log!("Balance of ETH [{}]: {}", args.address.encode(), balance); + self.io.return_output(&serde_json::to_vec(&balance)?); + Ok(()) + } + + /// Balance of ETH (ETH on Aurora) + pub fn internal_unwrap_balance_of_eth_on_aurora(&self, address: &Address) -> Wei { + crate::engine::get_balance(&self.io, address) + } + + /// `ft_on_transfer` callback function. + pub fn ft_on_transfer( + &mut self, + engine: &Engine, + args: &NEP141FtOnTransferArgs, + ) -> Result<(), errors::FtTransferCallError> { + sdk::log!("Call ft_on_transfer"); + // Parse message with specific rules + let message_data = FtTransferMessageData::parse_on_transfer_message(&args.msg) + .map_err(errors::FtTransferCallError::MessageParseFailed)?; + let amount = Wei::new_u128(args.amount.as_u128()); + // Special case when predecessor_account_id is current_account_id + let fee = Wei::from(message_data.fee); + // Mint fee to relayer + let relayer = engine.get_relayer(message_data.relayer.as_bytes()); + + let mint_amount = if relayer.is_some() && fee > ZERO_WEI { + self.mint_eth_on_aurora(relayer.unwrap(), fee)?; + amount - fee + } else { + amount + }; + + self.mint_eth_on_aurora(message_data.recipient, mint_amount)?; + self.io.return_output(b"\"0\""); + + Ok(()) + } + + /// Mint ETH tokens + fn mint_eth_on_aurora( + &mut self, + owner_id: Address, + amount: Wei, + ) -> Result<(), errors::DepositError> { + sdk::log!("Mint {} ETH tokens for: {}", amount, owner_id.encode()); + self.internal_deposit_eth_to_aurora(owner_id, amount) + } + + /// Internal ETH deposit to Aurora + pub fn internal_deposit_eth_to_aurora( + &mut self, + address: Address, + amount: Wei, + ) -> Result<(), errors::DepositError> { + let balance = self.internal_unwrap_balance_of_eth_on_aurora(&address); + let new_balance = balance + .checked_add(amount) + .ok_or(errors::DepositError::BalanceOverflow)?; + crate::engine::set_balance(&mut self.io, &address, &new_balance); + Ok(()) + } + + /// Transfer between NEAR accounts + pub fn ft_transfer(&self, data: Vec) -> PromiseCreateArgs { + PromiseCreateArgs { + target_account_id: self.get_eth_connector_contract_account(), + method: "engine_ft_transfer".to_string(), + args: data, + attached_balance: Yocto::new(1), + attached_gas: GAS_FOR_FT_TRANSFER, + } + } + + /// FT transfer call from sender account (invoker account) to receiver + /// We start 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 + /// We allow empty messages for cases when `receiver_id =! current_account_id` + pub fn ft_transfer_call(&self, data: Vec) -> PromiseCreateArgs { + PromiseCreateArgs { + target_account_id: self.get_eth_connector_contract_account(), + method: "engine_ft_transfer_call".to_string(), + args: data, + attached_balance: Yocto::new(1), + attached_gas: GAS_FOR_FT_TRANSFER_CALL, + } + } + + /// FT storage deposit logic + pub fn storage_deposit(&self, data: Vec, attached_deposit: u128) -> PromiseCreateArgs { + PromiseCreateArgs { + target_account_id: self.get_eth_connector_contract_account(), + method: "engine_storage_deposit".to_string(), + args: data, + attached_balance: Yocto::new(attached_deposit), + attached_gas: DEFAULT_PREPAID_GAS, + } + } + + /// FT storage unregister + pub fn storage_unregister(&self, data: Vec) -> PromiseCreateArgs { + PromiseCreateArgs { + target_account_id: self.get_eth_connector_contract_account(), + method: "engine_storage_unregister".to_string(), + args: data, + attached_balance: ZERO_ATTACHED_BALANCE, + attached_gas: DEFAULT_PREPAID_GAS, + } + } + + /// FT storage withdraw + pub fn storage_withdraw(&self, data: Vec) -> PromiseCreateArgs { + PromiseCreateArgs { + target_account_id: self.get_eth_connector_contract_account(), + method: "engine_storage_withdraw".to_string(), + args: data, + attached_balance: ZERO_ATTACHED_BALANCE, + attached_gas: DEFAULT_PREPAID_GAS, + } + } + + /// Get balance of storage + pub fn storage_balance_of(&self, data: Vec) -> PromiseCreateArgs { + PromiseCreateArgs { + target_account_id: self.get_eth_connector_contract_account(), + method: "storage_balance_of".to_string(), + args: data, + attached_balance: ZERO_ATTACHED_BALANCE, + attached_gas: DEFAULT_PREPAID_GAS, + } + } + + pub fn get_bridge_prover(&self) -> PromiseCreateArgs { + PromiseCreateArgs { + target_account_id: self.get_eth_connector_contract_account(), + method: "get_bridge_prover".to_string(), + args: Vec::new(), + attached_balance: ZERO_ATTACHED_BALANCE, + attached_gas: VIEW_CALL_GAS, + } + } + + /// Checks whether the provided proof was already used + pub fn is_used_proof(&self, data: Vec) -> PromiseCreateArgs { + PromiseCreateArgs { + target_account_id: self.get_eth_connector_contract_account(), + method: "is_used_proof".to_string(), + args: data, + attached_balance: ZERO_ATTACHED_BALANCE, + attached_gas: VIEW_CALL_GAS, + } + } + + /// Get Eth connector paused flags + pub fn get_paused_flags(&self) -> PromiseCreateArgs { + PromiseCreateArgs { + target_account_id: self.get_eth_connector_contract_account(), + method: "get_paused_flags".to_string(), + args: Vec::new(), + attached_balance: ZERO_ATTACHED_BALANCE, + attached_gas: DEFAULT_PREPAID_GAS, + } + } + + /// Return FT metadata + pub fn get_metadata(&self) -> PromiseCreateArgs { + PromiseCreateArgs { + target_account_id: self.get_eth_connector_contract_account(), + method: "ft_metadata".to_string(), + args: Vec::new(), + attached_balance: ZERO_ATTACHED_BALANCE, + attached_gas: VIEW_CALL_GAS, + } + } +} + +pub trait AdminControlled { + fn get_eth_connector_contract_account(&self) -> AccountId; + fn set_eth_connector_contract_account(&mut self, account: &AccountId); + fn get_withdraw_serialize_type(&self) -> WithdrawSerializeType; + fn set_withdraw_serialize_type(&mut self, serialize_type: &WithdrawSerializeType); +} + +impl AdminControlled for EthConnectorContract { + fn get_eth_connector_contract_account(&self) -> AccountId { + super::get_contract_data(&self.io, EthConnectorStorageId::EthConnectorAccount) + .expect("ERROR GETTING ETH CONNECTOR ACCOUNT ID") + } + + fn set_eth_connector_contract_account(&mut self, account: &AccountId) { + self.io.write_borsh( + &construct_contract_key(EthConnectorStorageId::EthConnectorAccount), + account, + ); + } + + fn get_withdraw_serialize_type(&self) -> WithdrawSerializeType { + super::get_contract_data(&self.io, EthConnectorStorageId::WithdrawSerializationType) + .expect("ERROR GETTING WITHDRAW SERIALIZE TYPE") + } + + fn set_withdraw_serialize_type(&mut self, serialize_type: &WithdrawSerializeType) { + self.io.write_borsh( + &construct_contract_key(EthConnectorStorageId::WithdrawSerializationType), + serialize_type, + ); + } +} + +fn read_json_args(io: &I) -> Result +where + T: serde::de::DeserializeOwned, +{ + let bytes = io.read_input().to_vec(); + aurora_engine_types::parameters::engine::parse_json_args(&bytes) +} diff --git a/engine/src/fungible_token.rs b/engine/src/contract_methods/connector/fungible_token.rs similarity index 77% rename from engine/src/fungible_token.rs rename to engine/src/contract_methods/connector/fungible_token.rs index 7570e1318..a31229195 100644 --- a/engine/src/fungible_token.rs +++ b/engine/src/contract_methods/connector/fungible_token.rs @@ -1,4 +1,5 @@ -use crate::connector::ZERO_ATTACHED_BALANCE; +use super::errors; +use crate::contract_methods::connector::ZERO_ATTACHED_BALANCE; use crate::engine; use crate::parameters::{NEP141FtOnTransferArgs, ResolveTransferCallArgs, StorageBalance}; use crate::prelude::account_id::AccountId; @@ -83,18 +84,18 @@ impl FungibleTokenOps { &mut self, account_id: &AccountId, amount: NEP141Wei, - ) -> Result<(), error::DepositError> { + ) -> Result<(), errors::DepositError> { let balance = self .get_account_eth_balance(account_id) .unwrap_or(ZERO_NEP141_WEI); let new_balance = balance .checked_add(amount) - .ok_or(error::DepositError::BalanceOverflow)?; + .ok_or(errors::DepositError::BalanceOverflow)?; self.accounts_insert(account_id, new_balance); self.total_eth_supply_on_near = self .total_eth_supply_on_near .checked_add(amount) - .ok_or(error::DepositError::TotalSupplyOverflow)?; + .ok_or(errors::DepositError::TotalSupplyOverflow)?; Ok(()) } @@ -103,16 +104,16 @@ impl FungibleTokenOps { &mut self, address: Address, amount: Wei, - ) -> Result<(), error::DepositError> { + ) -> Result<(), errors::DepositError> { let balance = self.internal_unwrap_balance_of_eth_on_aurora(&address); let new_balance = balance .checked_add(amount) - .ok_or(error::DepositError::BalanceOverflow)?; + .ok_or(errors::DepositError::BalanceOverflow)?; engine::set_balance(&mut self.io, &address, &new_balance); self.total_eth_supply_on_aurora = self .total_eth_supply_on_aurora .checked_add(amount) - .ok_or(error::DepositError::TotalSupplyOverflow)?; + .ok_or(errors::DepositError::TotalSupplyOverflow)?; Ok(()) } @@ -121,18 +122,19 @@ impl FungibleTokenOps { &mut self, account_id: &AccountId, amount: NEP141Wei, - ) -> Result<(), error::WithdrawError> { + ) -> Result<(), errors::WithdrawError> { let balance = self .get_account_eth_balance(account_id) .unwrap_or(ZERO_NEP141_WEI); let new_balance = balance .checked_sub(amount) - .ok_or(error::WithdrawError::InsufficientFunds)?; + .ok_or(errors::WithdrawError::InsufficientFunds)?; self.accounts_insert(account_id, new_balance); self.total_eth_supply_on_near = self .total_eth_supply_on_near .checked_sub(amount) - .ok_or(error::WithdrawError::TotalSupplyUnderflow)?; + .ok_or(errors::WithdrawError::TotalSupplyUnderflow)?; + Ok(()) } @@ -140,11 +142,12 @@ impl FungibleTokenOps { pub fn internal_withdraw_eth_from_aurora( &mut self, amount: Wei, - ) -> Result<(), error::WithdrawError> { + ) -> Result<(), errors::WithdrawError> { self.total_eth_supply_on_aurora = self .total_eth_supply_on_aurora .checked_sub(amount) - .ok_or(error::WithdrawError::TotalSupplyUnderflow)?; + .ok_or(errors::WithdrawError::TotalSupplyUnderflow)?; + Ok(()) } @@ -155,12 +158,12 @@ impl FungibleTokenOps { receiver_id: &AccountId, amount: NEP141Wei, #[allow(unused_variables)] memo: &Option, - ) -> Result<(), error::TransferError> { + ) -> Result<(), errors::TransferError> { if sender_id == receiver_id { - return Err(error::TransferError::SelfTransfer); + return Err(errors::TransferError::SelfTransfer); } if amount == ZERO_NEP141_WEI { - return Err(error::TransferError::ZeroAmount); + return Err(errors::TransferError::ZeroAmount); } // Check is account receiver_id exist @@ -211,13 +214,13 @@ impl FungibleTokenOps { msg: String, current_account_id: AccountId, prepaid_gas: NearGas, - ) -> Result { + ) -> Result { // check balance to prevent setting an arbitrary value for `amount` for (receiver_id == receiver_id). let balance = self .get_account_eth_balance(&sender_id) .unwrap_or(ZERO_NEP141_WEI); if amount > balance { - return Err(error::TransferError::InsufficientFunds); + return Err(errors::TransferError::InsufficientFunds); } // Special case for Aurora transfer itself - we shouldn't transfer if sender_id != receiver_id { @@ -336,7 +339,7 @@ impl FungibleTokenOps { &mut self, account_id: AccountId, force: Option, - ) -> Result<(NEP141Wei, PromiseBatchAction), error::StorageFundingError> { + ) -> Result<(NEP141Wei, PromiseBatchAction), errors::StorageFundingError> { let force = force.unwrap_or(false); if let Some(balance) = self.get_account_eth_balance(&account_id) { if balance == ZERO_NEP141_WEI || force { @@ -353,11 +356,11 @@ impl FungibleTokenOps { }; Ok((balance, promise)) } else { - Err(error::StorageFundingError::UnRegisterPositiveBalance) + Err(errors::StorageFundingError::UnRegisterPositiveBalance) } } else { sdk::log!("The account {} is not registered", account_id); - Err(error::StorageFundingError::NotRegistered) + Err(errors::StorageFundingError::NotRegistered) } } @@ -394,7 +397,7 @@ impl FungibleTokenOps { account_id: &AccountId, amount: Yocto, registration_only: Option, - ) -> Result<(StorageBalance, Option), error::StorageFundingError> { + ) -> Result<(StorageBalance, Option), errors::StorageFundingError> { let promise = if self.accounts_contains_key(account_id) { sdk::log!("The account is already registered, refunding the deposit"); amount @@ -402,7 +405,7 @@ impl FungibleTokenOps { let min_balance = self.storage_balance_bounds().min; if amount < min_balance { - return Err(error::StorageFundingError::InsufficientDeposit); + return Err(errors::StorageFundingError::InsufficientDeposit); } self.internal_register_account(account_id); @@ -428,15 +431,15 @@ impl FungibleTokenOps { &mut self, account_id: &AccountId, amount: Option, - ) -> Result { + ) -> Result { self.internal_storage_balance_of(account_id).map_or( - Err(error::StorageFundingError::NotRegistered), + Err(errors::StorageFundingError::NotRegistered), |storage_balance| match amount { Some(amount) if amount > ZERO_YOCTO => { // The available balance is always zero because `StorageBalanceBounds::max` is // equal to `StorageBalanceBounds::min`. Therefore, it is impossible to withdraw // a positive amount. - Err(error::StorageFundingError::NoAvailableBalance) + Err(errors::StorageFundingError::NoAvailableBalance) } _ => Ok(storage_balance), }, @@ -504,110 +507,3 @@ impl FungibleTokenOps { self.io.write_storage(&key, &accounts_counter.to_le_bytes()); } } - -pub mod error { - use crate::errors; - use crate::prelude::types::balance::error::BalanceOverflowError; - - const TOTAL_SUPPLY_OVERFLOW: &[u8; 25] = errors::ERR_TOTAL_SUPPLY_OVERFLOW; - const BALANCE_OVERFLOW: &[u8; 20] = errors::ERR_BALANCE_OVERFLOW; - const NOT_ENOUGH_BALANCE: &[u8; 22] = errors::ERR_NOT_ENOUGH_BALANCE; - const TOTAL_SUPPLY_UNDERFLOW: &[u8; 26] = errors::ERR_TOTAL_SUPPLY_UNDERFLOW; - const ZERO_AMOUNT: &[u8; 15] = errors::ERR_ZERO_AMOUNT; - const SELF_TRANSFER: &[u8; 26] = errors::ERR_SENDER_EQUALS_RECEIVER; - - #[derive(Debug)] - pub enum DepositError { - TotalSupplyOverflow, - BalanceOverflow, - } - - impl AsRef<[u8]> for DepositError { - fn as_ref(&self) -> &[u8] { - match self { - Self::TotalSupplyOverflow => TOTAL_SUPPLY_OVERFLOW, - Self::BalanceOverflow => BALANCE_OVERFLOW, - } - } - } - - #[derive(Debug)] - pub enum WithdrawError { - TotalSupplyUnderflow, - InsufficientFunds, - BalanceOverflow(BalanceOverflowError), - } - - impl AsRef<[u8]> for WithdrawError { - fn as_ref(&self) -> &[u8] { - match self { - Self::TotalSupplyUnderflow => TOTAL_SUPPLY_UNDERFLOW, - Self::InsufficientFunds => NOT_ENOUGH_BALANCE, - Self::BalanceOverflow(e) => e.as_ref(), - } - } - } - - #[derive(Debug)] - pub enum TransferError { - TotalSupplyUnderflow, - TotalSupplyOverflow, - InsufficientFunds, - BalanceOverflow, - ZeroAmount, - SelfTransfer, - } - - impl AsRef<[u8]> for TransferError { - fn as_ref(&self) -> &[u8] { - match self { - Self::TotalSupplyUnderflow => TOTAL_SUPPLY_UNDERFLOW, - Self::TotalSupplyOverflow => TOTAL_SUPPLY_OVERFLOW, - Self::InsufficientFunds => NOT_ENOUGH_BALANCE, - Self::BalanceOverflow => BALANCE_OVERFLOW, - Self::ZeroAmount => ZERO_AMOUNT, - Self::SelfTransfer => SELF_TRANSFER, - } - } - } - - impl From for TransferError { - fn from(err: WithdrawError) -> Self { - match err { - WithdrawError::InsufficientFunds => Self::InsufficientFunds, - WithdrawError::TotalSupplyUnderflow => Self::TotalSupplyUnderflow, - WithdrawError::BalanceOverflow(_) => Self::BalanceOverflow, - } - } - } - - impl From for TransferError { - fn from(err: DepositError) -> Self { - match err { - DepositError::BalanceOverflow => Self::BalanceOverflow, - DepositError::TotalSupplyOverflow => Self::TotalSupplyOverflow, - } - } - } - - #[derive(Debug)] - pub enum StorageFundingError { - NotRegistered, - NoAvailableBalance, - InsufficientDeposit, - UnRegisterPositiveBalance, - } - - impl AsRef<[u8]> for StorageFundingError { - fn as_ref(&self) -> &[u8] { - match self { - Self::NotRegistered => errors::ERR_ACCOUNT_NOT_REGISTERED, - Self::NoAvailableBalance => errors::ERR_NO_AVAILABLE_BALANCE, - Self::InsufficientDeposit => errors::ERR_ATTACHED_DEPOSIT_NOT_ENOUGH, - Self::UnRegisterPositiveBalance => { - errors::ERR_FAILED_UNREGISTER_ACCOUNT_POSITIVE_BALANCE - } - } - } - } -} diff --git a/engine/src/connector.rs b/engine/src/contract_methods/connector/internal.rs similarity index 54% rename from engine/src/connector.rs rename to engine/src/contract_methods/connector/internal.rs index 5925f311d..e183e6a7c 100644 --- a/engine/src/connector.rs +++ b/engine/src/contract_methods/connector/internal.rs @@ -1,34 +1,53 @@ -use crate::admin_controlled::{AdminControlled, PausedMask}; -use crate::deposit_event::{DepositedEvent, FtTransferMessageData, TokenMessageData}; -use crate::engine::Engine; -use crate::fungible_token::{self, FungibleToken, FungibleTokenMetadata, FungibleTokenOps}; -use crate::parameters::{ - BalanceOfCallArgs, BalanceOfEthCallArgs, FinishDepositCallArgs, InitCallArgs, - NEP141FtOnTransferArgs, PauseEthConnectorCallArgs, ResolveTransferCallArgs, - SetContractDataCallArgs, StorageBalanceOfCallArgs, StorageDepositCallArgs, - StorageWithdrawCallArgs, TransferCallArgs, TransferCallCallArgs, WithdrawResult, +use crate::contract_methods::connector::admin_controlled::AdminControlled; +use crate::contract_methods::connector::deposit_event::{ + DepositedEvent, FtTransferMessageData, TokenMessageData, +}; +use crate::contract_methods::connector::errors; +use crate::contract_methods::connector::fungible_token::{FungibleToken, FungibleTokenOps}; +use crate::contract_methods::connector::{ + construct_contract_key, proof_key, ZERO_ATTACHED_BALANCE, }; -use crate::prelude::{ - address::error::AddressError, NEP141Wei, String, Wei, ZERO_NEP141_WEI, ZERO_WEI, +use crate::contract_methods::{ + predecessor_address, require_owner_only, require_running, ContractError, }; -use crate::prelude::{ - format, sdk, str, AccountId, Address, BorshDeserialize, BorshSerialize, EthConnectorStorageId, - KeyPrefix, NearGas, PromiseResult, ToString, Vec, WithdrawCallArgs, Yocto, ERR_FAILED_PARSE, +use crate::engine::Engine; +use crate::hashchain::with_hashchain; +use crate::prelude::{format, sdk, ToString, Vec}; +use crate::state; +use aurora_engine_modexp::{AuroraModExp, ModExpAlgorithm}; +use aurora_engine_sdk::io::StorageIntermediate; +use aurora_engine_sdk::promise::PromiseHandler; +use aurora_engine_sdk::{env::Env, io::IO}; +use aurora_engine_types::account_id::AccountId; +use aurora_engine_types::parameters::connector::{ + BalanceOfCallArgs, BalanceOfEthCallArgs, FinishDepositCallArgs, FungibleTokenMetadata, + IsUsedProofCallArgs, PauseEthConnectorCallArgs, PausedMask, Proof, StorageBalanceOfCallArgs, + WithdrawResult, }; -use crate::prelude::{PromiseBatchAction, PromiseCreateArgs, PromiseWithCallbackArgs}; -use crate::proof::Proof; -use aurora_engine_modexp::ModExpAlgorithm; -use aurora_engine_sdk::env::Env; -use aurora_engine_sdk::io::{StorageIntermediate, IO}; -use aurora_engine_types::borsh; - -pub const ERR_NOT_ENOUGH_BALANCE_FOR_FEE: &str = "ERR_NOT_ENOUGH_BALANCE_FOR_FEE"; -/// Indicate zero attached balance for promise call -pub const ZERO_ATTACHED_BALANCE: Yocto = Yocto::new(0); +use aurora_engine_types::parameters::engine::errors::ParseArgsError; +use aurora_engine_types::parameters::{PromiseBatchAction, PromiseCreateArgs, WithdrawCallArgs}; +use aurora_engine_types::storage::EthConnectorStorageId; +use aurora_engine_types::types::address::error::AddressError; +use aurora_engine_types::types::error::BalanceOverflowError; +use aurora_engine_types::types::{NEP141Wei, NearGas, Wei, ZERO_NEP141_WEI}; +use aurora_engine_types::{ + borsh::{self, BorshDeserialize, BorshSerialize}, + parameters::{ + connector::{ + InitCallArgs, NEP141FtOnTransferArgs, ResolveTransferCallArgs, SetContractDataCallArgs, + StorageDepositCallArgs, StorageWithdrawCallArgs, TransferCallArgs, + TransferCallCallArgs, + }, + PromiseWithCallbackArgs, + }, + types::{Address, PromiseResult, Yocto}, +}; +use function_name::named; + /// NEAR Gas for calling `finish_deposit` promise. Used in the `deposit` logic. pub const GAS_FOR_FINISH_DEPOSIT: NearGas = NearGas::new(50_000_000_000_000); /// NEAR Gas for calling `verify_log_entry` promise. Used in the `deposit` logic. -// Note: Is 40Tgas always enough? +// Note: Is 40 TGas always enough? const GAS_FOR_VERIFY_LOG_ENTRY: NearGas = NearGas::new(40_000_000_000_000); /// Admin control flow flag indicates that all control flow unpause (unblocked). @@ -38,6 +57,417 @@ pub const PAUSE_DEPOSIT: PausedMask = 1 << 0; /// Admin control flow flag indicates that withdrawal is paused. pub const PAUSE_WITHDRAW: PausedMask = 1 << 1; +#[named] +pub fn new_eth_connector(io: I, env: &E) -> Result<(), ContractError> { + with_hashchain(io, env, function_name!(), |io| { + let state = state::get_state(&io)?; + require_running(&state)?; + // Only the owner can initialize the EthConnector + let is_private = env.assert_private_call(); + if is_private.is_err() { + require_owner_only(&state, &env.predecessor_account_id())?; + } + + let args: InitCallArgs = io.read_input_borsh()?; + let owner_id = env.current_account_id(); + + EthConnectorContract::create_contract(io, &owner_id, args)?; + Ok(()) + }) +} + +#[named] +pub fn set_eth_connector_contract_data( + io: I, + env: &E, +) -> Result<(), ContractError> { + with_hashchain(io, env, function_name!(), |mut io| { + let state = state::get_state(&io)?; + require_running(&state)?; + // Only the owner can set the EthConnector contract data + let is_private = env.assert_private_call(); + if is_private.is_err() { + require_owner_only(&state, &env.predecessor_account_id())?; + } + + let args: SetContractDataCallArgs = io.read_input_borsh()?; + set_contract_data(&mut io, args)?; + Ok(()) + }) +} + +#[named] +pub fn withdraw(io: I, env: &E) -> Result<(), ContractError> { + with_hashchain(io, env, function_name!(), |io| { + require_running(&state::get_state(&io)?)?; + env.assert_one_yocto()?; + let args = io.read_input_borsh()?; + let current_account_id = env.current_account_id(); + let predecessor_account_id = env.predecessor_account_id(); + let result = EthConnectorContract::init(io)?.withdraw_eth_from_near( + ¤t_account_id, + &predecessor_account_id, + &args, + )?; + let result_bytes = result + .try_to_vec() + .map_err(|_| crate::errors::ERR_SERIALIZE)?; + + // We only return the output via IO in the case of standalone. + // In the case of contract we intentionally avoid IO to call Wasm directly. + #[cfg(not(feature = "contract"))] + { + let mut io = io; + io.return_output(&result_bytes); + } + + #[allow(clippy::as_conversions)] + #[cfg(feature = "contract")] + unsafe { + crate::contract::exports::value_return( + u64::try_from(result_bytes.len()).unwrap(), // sdk_unwrap(), + result_bytes.as_ptr() as u64, + ); + } + + Ok(()) + }) +} + +#[named] +pub fn deposit( + io: I, + env: &E, + handler: &mut H, +) -> Result, ContractError> { + with_hashchain(io, env, function_name!(), |io| { + require_running(&state::get_state(&io)?)?; + let raw_proof = io.read_input().to_vec(); + let current_account_id = env.current_account_id(); + let predecessor_account_id = env.predecessor_account_id(); + let promise_args = EthConnectorContract::init(io)?.deposit( + raw_proof, + current_account_id, + predecessor_account_id, + )?; + // Safety: this call is safe because it comes from the eth-connector, not users. + // The call is to verify the user-supplied proof for the deposit, with `finish_deposit` + // as a callback. + let promise_id = unsafe { handler.promise_create_with_callback(&promise_args) }; + handler.promise_return(promise_id); + + Ok(Some(promise_args)) + }) +} + +#[named] +pub fn ft_transfer(io: I, env: &E) -> Result<(), ContractError> { + with_hashchain(io, env, function_name!(), |io| { + require_running(&state::get_state(&io)?)?; + env.assert_one_yocto()?; + let predecessor_account_id = env.predecessor_account_id(); + let args: TransferCallArgs = serde_json::from_slice(&io.read_input().to_vec()) + .map_err(Into::::into)?; + EthConnectorContract::init(io)?.ft_transfer(&predecessor_account_id, &args)?; + Ok(()) + }) +} + +#[named] +pub fn ft_transfer_call( + io: I, + env: &E, + handler: &mut H, +) -> Result, ContractError> { + with_hashchain(io, env, function_name!(), |io| { + require_running(&state::get_state(&io)?)?; + // Check is payable + env.assert_one_yocto()?; + + let args: TransferCallCallArgs = serde_json::from_slice(&io.read_input().to_vec()) + .map_err(Into::::into)?; + let current_account_id = env.current_account_id(); + let predecessor_account_id = env.predecessor_account_id(); + let promise_args = EthConnectorContract::init(io)?.ft_transfer_call( + predecessor_account_id, + current_account_id, + args, + env.prepaid_gas(), + )?; + // Safety: this call is safe. It is required by the NEP-141 spec that `ft_transfer_call` + // creates a call to another contract's `ft_on_transfer` method. + let promise_id = unsafe { handler.promise_create_with_callback(&promise_args) }; + handler.promise_return(promise_id); + + Ok(Some(promise_args)) + }) +} + +#[named] +pub fn ft_on_transfer( + io: I, + env: &E, + handler: &mut H, +) -> Result<(), ContractError> { + with_hashchain(io, env, function_name!(), |io| { + let state = state::get_state(&io)?; + require_running(&state)?; + let current_account_id = env.current_account_id(); + let predecessor_account_id = env.predecessor_account_id(); + let mut engine: Engine<_, E, AuroraModExp> = Engine::new_with_state( + state, + predecessor_address(&predecessor_account_id), + current_account_id.clone(), + io, + env, + ); + + let args: NEP141FtOnTransferArgs = serde_json::from_slice(&io.read_input().to_vec()) + .map_err(Into::::into)?; + + if predecessor_account_id == current_account_id { + EthConnectorContract::init(io)?.ft_on_transfer(&engine, &args)?; + } else { + engine.receive_erc20_tokens( + &predecessor_account_id, + &args, + ¤t_account_id, + handler, + ); + } + Ok(()) + }) +} + +#[named] +pub fn ft_resolve_transfer( + io: I, + env: &E, + handler: &H, +) -> Result<(), ContractError> { + with_hashchain(io, env, function_name!(), |io| { + require_running(&state::get_state(&io)?)?; + + env.assert_private_call()?; + if handler.promise_results_count() != 1 { + return Err(crate::errors::ERR_PROMISE_COUNT.into()); + } + + let args: ResolveTransferCallArgs = io.read_input().to_value()?; + let promise_result = handler + .promise_result(0) + .ok_or(crate::errors::ERR_PROMISE_ENCODING)?; + + EthConnectorContract::init(io)?.ft_resolve_transfer(&args, promise_result); + Ok(()) + }) +} + +#[named] +pub fn storage_deposit( + io: I, + env: &E, + handler: &mut H, +) -> Result<(), ContractError> { + with_hashchain(io, env, function_name!(), |io| { + require_running(&state::get_state(&io)?)?; + let args: StorageDepositCallArgs = serde_json::from_slice(&io.read_input().to_vec()) + .map_err(Into::::into)?; + let predecessor_account_id = env.predecessor_account_id(); + let amount = Yocto::new(env.attached_deposit()); + let maybe_promise = EthConnectorContract::init(io)?.storage_deposit( + predecessor_account_id, + amount, + args, + )?; + if let Some(promise) = maybe_promise { + // Safety: This call is safe. It is only a transfer back to the user in the case + // that they over paid for their deposit. + unsafe { handler.promise_create_batch(&promise) }; + } + Ok(()) + }) +} + +#[named] +pub fn storage_unregister( + io: I, + env: &E, + handler: &mut H, +) -> Result<(), ContractError> { + with_hashchain(io, env, function_name!(), |io| { + require_running(&state::get_state(&io)?)?; + env.assert_one_yocto()?; + let predecessor_account_id = env.predecessor_account_id(); + let force = serde_json::from_slice::(&io.read_input().to_vec()) + .ok() + .and_then(|args| args["force"].as_bool()); + let maybe_promise = + EthConnectorContract::init(io)?.storage_unregister(predecessor_account_id, force)?; + if let Some(promise) = maybe_promise { + // Safety: This call is safe. It is only a transfer back to the user for their deposit. + unsafe { handler.promise_create_batch(&promise) }; + } + Ok(()) + }) +} + +#[named] +pub fn storage_withdraw(io: I, env: &E) -> Result<(), ContractError> { + with_hashchain(io, env, function_name!(), |io| { + require_running(&state::get_state(&io)?)?; + env.assert_one_yocto()?; + let args: StorageWithdrawCallArgs = serde_json::from_slice(&io.read_input().to_vec()) + .map_err(Into::::into)?; + let predecessor_account_id = env.predecessor_account_id(); + EthConnectorContract::init(io)?.storage_withdraw(&predecessor_account_id, &args)?; + Ok(()) + }) +} + +pub fn storage_balance_of(io: I) -> Result<(), ContractError> { + let args: StorageBalanceOfCallArgs = + serde_json::from_slice(&io.read_input().to_vec()).map_err(Into::::into)?; + EthConnectorContract::init(io)?.storage_balance_of(&args); + + Ok(()) +} + +#[named] +pub fn set_paused_flags(io: I, env: &E) -> Result<(), ContractError> { + with_hashchain(io, env, function_name!(), |io| { + let state = state::get_state(&io)?; + require_running(&state)?; + let is_private = env.assert_private_call(); + if is_private.is_err() { + require_owner_only(&state, &env.predecessor_account_id())?; + } + let args: PauseEthConnectorCallArgs = io.read_input_borsh()?; + EthConnectorContract::init(io)?.set_paused_flags(&args); + Ok(()) + }) +} + +pub fn get_paused_flags(mut io: I) -> Result<(), ContractError> { + let paused_flags = EthConnectorContract::init(io)?.get_paused_flags(); + let data = paused_flags.try_to_vec().unwrap(); + io.return_output(&data); + + Ok(()) +} + +pub fn is_used_proof(mut io: I) -> Result<(), ContractError> { + let args: IsUsedProofCallArgs = io.read_input_borsh()?; + + let is_used_proof = EthConnectorContract::init(io)?.is_used_proof(&args.proof); + let res = is_used_proof.try_to_vec().unwrap(); + io.return_output(&res); + + Ok(()) +} + +pub fn ft_total_eth_supply_on_near(io: I) -> Result<(), ContractError> { + EthConnectorContract::init(io)?.ft_total_eth_supply_on_near(); + Ok(()) +} + +pub fn ft_balance_of(io: I) -> Result<(), ContractError> { + let args: BalanceOfCallArgs = + serde_json::from_slice(&io.read_input().to_vec()).map_err(Into::::into)?; + EthConnectorContract::init(io)?.ft_balance_of(&args); + Ok(()) +} + +#[named] +pub fn finish_deposit( + io: I, + env: &E, + handler: &mut H, +) -> Result, ContractError> { + with_hashchain(io, env, function_name!(), |io| { + require_running(&state::get_state(&io)?)?; + env.assert_private_call()?; + + // Check result from proof verification call + if handler.promise_results_count() != 1 { + return Err(crate::errors::ERR_PROMISE_COUNT.into()); + } + let promise_result = match handler.promise_result(0) { + Some(PromiseResult::Successful(bytes)) => { + bool::try_from_slice(&bytes).map_err(|_| crate::errors::ERR_PROMISE_ENCODING)? + } + _ => return Err(crate::errors::ERR_PROMISE_FAILED.into()), + }; + if !promise_result { + return Err(crate::errors::ERR_VERIFY_PROOF.into()); + } + + let data = io.read_input_borsh()?; + let current_account_id = env.current_account_id(); + let predecessor_account_id = env.predecessor_account_id(); + let maybe_promise_args = EthConnectorContract::init(io)?.finish_deposit( + predecessor_account_id, + current_account_id, + data, + env.prepaid_gas(), + )?; + + if let Some(promise_args) = maybe_promise_args.as_ref() { + // Safety: this call is safe because it comes from the eth-connector, not users. + // The call will be to the Engine's ft_transfer_call`, which is needed as part + // of the bridge flow (if depositing ETH to an Aurora address). + let promise_id = unsafe { handler.promise_create_with_callback(promise_args) }; + handler.promise_return(promise_id); + } + + Ok(maybe_promise_args) + }) +} + +pub fn get_accounts_counter(io: I) -> Result<(), ContractError> { + EthConnectorContract::init(io)?.get_accounts_counter(); + Ok(()) +} + +pub fn ft_metadata(mut io: I) -> Result<(), ContractError> { + let metadata = get_metadata(&io).unwrap_or_default(); + io.return_output(&serde_json::to_vec(&metadata).unwrap_or_default()); + + Ok(()) +} + +/// Sets the contract data and returns it back +fn set_contract_data( + io: &mut I, + args: SetContractDataCallArgs, +) -> Result { + // Get initial contract arguments + let contract_data = EthConnector { + prover_account: args.prover_account, + eth_custodian_address: Address::decode(&args.eth_custodian_address)?, + }; + // Save eth-connector specific data + io.write_borsh( + &construct_contract_key(EthConnectorStorageId::Contract), + &contract_data, + ); + + io.write_borsh( + &construct_contract_key(EthConnectorStorageId::FungibleTokenMetadata), + &args.metadata, + ); + + Ok(contract_data) +} + +/// Return FT metadata. +fn get_metadata(io: &I) -> Option { + io.read_storage(&construct_contract_key( + EthConnectorStorageId::FungibleTokenMetadata, + )) + .and_then(|data| data.to_value().ok()) +} + /// Eth-connector contract data. It's stored in the storage. /// Contains: /// * connector specific data @@ -66,12 +496,15 @@ impl EthConnectorContract { /// Init Eth-connector contract instance. /// Load contract data from storage and init I/O handler. /// Used as single point of contract access for various contract actions - pub fn init_instance(io: I) -> Result { + pub fn init(io: I) -> Result { Ok(Self { - contract: get_contract_data(&io, EthConnectorStorageId::Contract)?, - ft: get_contract_data::(&io, EthConnectorStorageId::FungibleToken)? - .ops(io), - paused_mask: get_contract_data(&io, EthConnectorStorageId::PausedMask)?, + contract: super::get_contract_data(&io, EthConnectorStorageId::Contract)?, + ft: super::get_contract_data::( + &io, + EthConnectorStorageId::FungibleToken, + )? + .ops(io), + paused_mask: super::get_contract_data(&io, EthConnectorStorageId::PausedMask)?, io, }) } @@ -83,12 +516,12 @@ impl EthConnectorContract { mut io: I, owner_id: &AccountId, args: InitCallArgs, - ) -> Result<(), error::InitContractError> { + ) -> Result<(), errors::InitContractError> { // Check is it already initialized let contract_key_exists = io.storage_has_key(&construct_contract_key(EthConnectorStorageId::Contract)); if contract_key_exists { - return Err(error::InitContractError::AlreadyInitialized); + return Err(errors::InitContractError::AlreadyInitialized); } sdk::log!("[init contract]"); @@ -101,7 +534,7 @@ impl EthConnectorContract { metadata: args.metadata, }, ) - .map_err(error::InitContractError::InvalidCustodianAddress)?; + .map_err(errors::InitContractError::InvalidCustodianAddress)?; let mut ft = FungibleTokenOps::new(io); // Register FT account for current contract @@ -130,21 +563,21 @@ impl EthConnectorContract { raw_proof: Vec, current_account_id: AccountId, predecessor_account_id: AccountId, - ) -> Result { + ) -> Result { // Check if the current account is owner. let is_owner = current_account_id == predecessor_account_id; // Check if the deposit flow isn't paused. If it's owner just skip it. self.assert_not_paused(PAUSE_DEPOSIT, is_owner) - .map_err(|_| error::DepositError::Paused)?; + .map_err(|_| errors::DepositError::Paused)?; sdk::log!("[Deposit tokens]"); // Get incoming deposit arguments - let proof = - Proof::try_from_slice(&raw_proof).map_err(|_| error::DepositError::ProofParseFailed)?; + let proof = Proof::try_from_slice(&raw_proof) + .map_err(|_| errors::DepositError::ProofParseFailed)?; // Fetch event data from Proof let event = DepositedEvent::from_log_entry_data(&proof.log_entry_data) - .map_err(error::DepositError::EventParseFailed)?; + .map_err(errors::DepositError::EventParseFailed)?; sdk::log!( "Deposit started: from {} to recipient {:?} with amount: {:?} and fee {:?}", @@ -161,11 +594,11 @@ impl EthConnectorContract { ); if event.eth_custodian_address != self.contract.eth_custodian_address { - return Err(error::DepositError::CustodianAddressMismatch); + return Err(errors::DepositError::CustodianAddressMismatch); } if NEP141Wei::new(event.fee.as_u128()) >= event.amount { - return Err(error::DepositError::InsufficientAmountForFee); + return Err(errors::DepositError::InsufficientAmountForFee); } // Verify the proof data by sending cross-contract call to the prover smart contract. @@ -245,7 +678,7 @@ impl EthConnectorContract { /// Finish deposit flow (private method). /// NOTE: In the mint methods could occur an error while calculating the amount to be - /// credited and therefore we invoke `record_proof` after mint methods to avoid saving + /// credited, and therefore we invoke `record_proof` after mint methods to avoid saving /// proof before minting which could be potentially finished with error. pub fn finish_deposit( &mut self, @@ -253,7 +686,7 @@ impl EthConnectorContract { current_account_id: AccountId, data: FinishDepositCallArgs, prepaid_gas: NearGas, - ) -> Result, error::FinishDepositError> { + ) -> Result, errors::FinishDepositError> { sdk::log!("Finish deposit with the amount: {}", data.amount); // Mint tokens to recipient minus fee @@ -290,21 +723,18 @@ impl EthConnectorContract { } /// Internal `ETH` withdraw (ETH on Aurora). - pub(crate) fn internal_remove_eth( - &mut self, - amount: Wei, - ) -> Result<(), fungible_token::error::WithdrawError> { + pub(crate) fn internal_remove_eth(&mut self, amount: Wei) -> Result<(), errors::WithdrawError> { self.burn_eth_on_aurora(amount)?; self.save_ft_contract(); Ok(()) } /// Record hash of the used proof in the storage. - fn record_proof(&mut self, key: &str) -> Result<(), error::ProofUsed> { + fn record_proof(&mut self, key: &str) -> Result<(), errors::ProofUsed> { sdk::log!("Record proof: {}", key); if self.is_used_event(key) { - return Err(error::ProofUsed); + return Err(errors::ProofUsed); } self.save_used_event(key); @@ -316,7 +746,7 @@ impl EthConnectorContract { &mut self, owner_id: &AccountId, amount: NEP141Wei, - ) -> Result<(), fungible_token::error::DepositError> { + ) -> Result<(), errors::DepositError> { sdk::log!("Mint {} nETH tokens for: {}", amount, owner_id); if self.ft.get_account_eth_balance(owner_id).is_none() { @@ -330,16 +760,13 @@ impl EthConnectorContract { &mut self, address: Address, amount: Wei, - ) -> Result<(), fungible_token::error::DepositError> { + ) -> Result<(), errors::DepositError> { sdk::log!("Mint {} ETH tokens for: {}", amount, address.encode()); self.ft.internal_deposit_eth_to_aurora(address, amount) } /// Burn `ETH` tokens (ETH on Aurora). - fn burn_eth_on_aurora( - &mut self, - amount: Wei, - ) -> Result<(), fungible_token::error::WithdrawError> { + fn burn_eth_on_aurora(&mut self, amount: Wei) -> Result<(), errors::WithdrawError> { self.ft.internal_withdraw_eth_from_aurora(amount) } @@ -350,12 +777,12 @@ impl EthConnectorContract { current_account_id: &AccountId, predecessor_account_id: &AccountId, args: &WithdrawCallArgs, - ) -> Result { + ) -> Result { // Check if the current account id is owner. let is_owner = current_account_id == predecessor_account_id; // Check if the withdraw flow is paused. If it's owner just skip the assertion. self.assert_not_paused(PAUSE_WITHDRAW, is_owner) - .map_err(|_| error::WithdrawError::Paused)?; + .map_err(|_| errors::WithdrawError::Paused)?; // Burn tokens to recipient self.ft @@ -398,7 +825,7 @@ impl EthConnectorContract { pub fn ft_balance_of_eth_on_aurora( &mut self, args: &BalanceOfEthCallArgs, - ) -> Result<(), crate::prelude::types::balance::error::BalanceOverflowError> { + ) -> Result<(), BalanceOverflowError> { let balance = self .ft .internal_unwrap_balance_of_eth_on_aurora(&args.address); @@ -412,7 +839,7 @@ impl EthConnectorContract { &mut self, predecessor_account_id: &AccountId, args: &TransferCallArgs, - ) -> Result<(), fungible_token::error::TransferError> { + ) -> Result<(), errors::TransferError> { self.ft.internal_transfer_eth_on_near( predecessor_account_id, &args.receiver_id, @@ -462,7 +889,7 @@ impl EthConnectorContract { current_account_id: AccountId, args: TransferCallCallArgs, prepaid_gas: NearGas, - ) -> Result { + ) -> Result { sdk::log!( "Transfer call to {} amount {}", args.receiver_id, @@ -473,10 +900,10 @@ impl EthConnectorContract { // It's allowed empty message if `receiver_id =! current_account_id` if args.receiver_id == current_account_id { let message_data = FtTransferMessageData::parse_on_transfer_message(&args.msg) - .map_err(error::FtTransferCallError::MessageParseFailed)?; + .map_err(errors::FtTransferCallError::MessageParseFailed)?; // Check is transfer amount > fee if message_data.fee.as_u128() >= args.amount.as_u128() { - return Err(error::FtTransferCallError::InsufficientAmountForFee); + return Err(errors::FtTransferCallError::InsufficientAmountForFee); } // Additional check for overflowing before `ft_on_transfer` calling. @@ -490,8 +917,8 @@ impl EthConnectorContract { .checked_add(Wei::from(args.amount)) .is_none() { - return Err(error::FtTransferCallError::Transfer( - fungible_token::error::TransferError::BalanceOverflow, + return Err(errors::FtTransferCallError::Transfer( + errors::TransferError::BalanceOverflow, )); } if self @@ -500,8 +927,8 @@ impl EthConnectorContract { .checked_add(Wei::from(args.amount)) .is_none() { - return Err(error::FtTransferCallError::Transfer( - fungible_token::error::TransferError::TotalSupplyOverflow, + return Err(errors::FtTransferCallError::Transfer( + errors::TransferError::TotalSupplyOverflow, )); } } @@ -525,7 +952,7 @@ impl EthConnectorContract { predecessor_account_id: AccountId, amount: Yocto, args: StorageDepositCallArgs, - ) -> Result, fungible_token::error::StorageFundingError> { + ) -> Result, errors::StorageFundingError> { let account_id = args .account_id .unwrap_or_else(|| predecessor_account_id.clone()); @@ -545,13 +972,13 @@ impl EthConnectorContract { &mut self, account_id: AccountId, force: Option, - ) -> Result, fungible_token::error::StorageFundingError> { + ) -> Result, errors::StorageFundingError> { let promise = match self.ft.internal_storage_unregister(account_id, force) { Ok((_, p)) => { self.io.return_output(b"true"); Some(p) } - Err(fungible_token::error::StorageFundingError::NotRegistered) => { + Err(errors::StorageFundingError::NotRegistered) => { self.io.return_output(b"false"); None } @@ -565,7 +992,7 @@ impl EthConnectorContract { &mut self, account_id: &AccountId, args: &StorageWithdrawCallArgs, - ) -> Result<(), fungible_token::error::StorageFundingError> { + ) -> Result<(), errors::StorageFundingError> { let res = self.ft.storage_withdraw(account_id, args.amount)?; self.save_ft_contract(); self.io.return_output(&res.to_json_bytes()); @@ -583,25 +1010,24 @@ impl EthConnectorContract { &mut self, engine: &Engine, args: &NEP141FtOnTransferArgs, - ) -> Result<(), error::FtTransferCallError> { + ) -> Result<(), errors::FtTransferCallError> { sdk::log!("Call ft_on_transfer"); // Parse message with specific rules let message_data = FtTransferMessageData::parse_on_transfer_message(&args.msg) - .map_err(error::FtTransferCallError::MessageParseFailed)?; + .map_err(errors::FtTransferCallError::MessageParseFailed)?; let amount = Wei::new_u128(args.amount.as_u128()); // Special case when predecessor_account_id is current_account_id let fee = Wei::from(message_data.fee); // Mint fee to relayer let relayer = engine.get_relayer(message_data.relayer.as_bytes()); - let (amount, relayer_fee) = - relayer - .filter(|_| fee > ZERO_WEI) - .map_or(Ok((amount, None)), |address| { - amount.checked_sub(fee).map_or( - Err(error::FtTransferCallError::InsufficientAmountForFee), - |amount| Ok((amount, Some((address, fee)))), - ) - })?; + let (amount, relayer_fee) = relayer + .filter(|_| fee > aurora_engine_types::types::ZERO_WEI) + .map_or(Ok((amount, None)), |address| { + amount.checked_sub(fee).map_or( + Err(errors::FtTransferCallError::InsufficientAmountForFee), + |amount| Ok((amount, Some((address, fee)))), + ) + })?; if let Some((address, fee)) = relayer_fee { self.mint_eth_on_aurora(address, fee)?; @@ -683,298 +1109,3 @@ impl AdminControlled for EthConnectorContract { ); } } - -fn construct_contract_key(suffix: EthConnectorStorageId) -> Vec { - crate::prelude::bytes_to_key(KeyPrefix::EthConnector, &[u8::from(suffix)]) -} - -fn get_contract_data( - io: &I, - suffix: EthConnectorStorageId, -) -> Result { - io.read_storage(&construct_contract_key(suffix)) - .ok_or(error::StorageReadError::KeyNotFound) - .and_then(|x| { - x.to_value() - .map_err(|_| error::StorageReadError::BorshDeserialize) - }) -} - -/// Sets the contract data and returns it back -pub fn set_contract_data( - io: &mut I, - args: SetContractDataCallArgs, -) -> Result { - // Get initial contract arguments - let contract_data = EthConnector { - prover_account: args.prover_account, - eth_custodian_address: Address::decode(&args.eth_custodian_address)?, - }; - // Save eth-connector specific data - io.write_borsh( - &construct_contract_key(EthConnectorStorageId::Contract), - &contract_data, - ); - - io.write_borsh( - &construct_contract_key(EthConnectorStorageId::FungibleTokenMetadata), - &args.metadata, - ); - - Ok(contract_data) -} - -/// Return FT metadata. -pub fn get_metadata(io: &I) -> Option { - io.read_storage(&construct_contract_key( - EthConnectorStorageId::FungibleTokenMetadata, - )) - .and_then(|data| data.to_value().ok()) -} - -pub mod error { - use crate::errors; - use aurora_engine_types::types::address::error::AddressError; - use aurora_engine_types::types::balance::error::BalanceOverflowError; - - use crate::deposit_event::error::ParseOnTransferMessageError; - use crate::{deposit_event, fungible_token}; - - const PROOF_EXIST: &[u8; 15] = errors::ERR_PROOF_EXIST; - - #[cfg_attr(not(target_arch = "wasm32"), derive(Debug))] - pub enum StorageReadError { - KeyNotFound, - BorshDeserialize, - } - - impl AsRef<[u8]> for StorageReadError { - fn as_ref(&self) -> &[u8] { - match self { - Self::KeyNotFound => errors::ERR_CONNECTOR_STORAGE_KEY_NOT_FOUND, - Self::BorshDeserialize => errors::ERR_FAILED_DESERIALIZE_CONNECTOR_DATA, - } - } - } - - #[cfg_attr(not(target_arch = "wasm32"), derive(Debug))] - pub enum DepositError { - Paused, - ProofParseFailed, - EventParseFailed(deposit_event::error::ParseError), - CustodianAddressMismatch, - InsufficientAmountForFee, - InvalidAddress(AddressError), - } - - impl AsRef<[u8]> for DepositError { - fn as_ref(&self) -> &[u8] { - match self { - Self::Paused => crate::admin_controlled::ERR_PAUSED.as_bytes(), - Self::ProofParseFailed => super::ERR_FAILED_PARSE.as_bytes(), - Self::EventParseFailed(e) => e.as_ref(), - Self::CustodianAddressMismatch => errors::ERR_WRONG_EVENT_ADDRESS, - Self::InsufficientAmountForFee => super::ERR_NOT_ENOUGH_BALANCE_FOR_FEE.as_bytes(), - Self::InvalidAddress(e) => e.as_ref(), - } - } - } - - #[cfg_attr(not(target_arch = "wasm32"), derive(Debug))] - pub enum FinishDepositError { - TransferCall(FtTransferCallError), - ProofUsed, - } - - impl From for FinishDepositError { - fn from(_: ProofUsed) -> Self { - Self::ProofUsed - } - } - - impl From for FinishDepositError { - fn from(e: FtTransferCallError) -> Self { - Self::TransferCall(e) - } - } - - impl From for FinishDepositError { - fn from(e: fungible_token::error::DepositError) -> Self { - Self::TransferCall(FtTransferCallError::Transfer(e.into())) - } - } - - impl AsRef<[u8]> for FinishDepositError { - fn as_ref(&self) -> &[u8] { - match self { - Self::ProofUsed => PROOF_EXIST, - Self::TransferCall(e) => e.as_ref(), - } - } - } - - #[derive(Debug)] - pub enum WithdrawError { - Paused, - FT(fungible_token::error::WithdrawError), - } - - impl From for WithdrawError { - fn from(e: fungible_token::error::WithdrawError) -> Self { - Self::FT(e) - } - } - - impl AsRef<[u8]> for WithdrawError { - fn as_ref(&self) -> &[u8] { - match self { - Self::Paused => crate::admin_controlled::ERR_PAUSED.as_bytes(), - Self::FT(e) => e.as_ref(), - } - } - } - - #[cfg_attr(not(target_arch = "wasm32"), derive(Debug))] - pub enum FtTransferCallError { - BalanceOverflow(BalanceOverflowError), - MessageParseFailed(ParseOnTransferMessageError), - InsufficientAmountForFee, - Transfer(fungible_token::error::TransferError), - } - - impl From for FtTransferCallError { - fn from(e: fungible_token::error::TransferError) -> Self { - Self::Transfer(e) - } - } - - impl From for FtTransferCallError { - fn from(e: fungible_token::error::DepositError) -> Self { - Self::Transfer(e.into()) - } - } - - impl From for FtTransferCallError { - fn from(e: ParseOnTransferMessageError) -> Self { - Self::MessageParseFailed(e) - } - } - - impl AsRef<[u8]> for FtTransferCallError { - fn as_ref(&self) -> &[u8] { - match self { - Self::MessageParseFailed(e) => e.as_ref(), - Self::InsufficientAmountForFee => super::ERR_NOT_ENOUGH_BALANCE_FOR_FEE.as_bytes(), - Self::Transfer(e) => e.as_ref(), - Self::BalanceOverflow(e) => e.as_ref(), - } - } - } - - #[derive(Debug)] - pub enum InitContractError { - AlreadyInitialized, - InvalidCustodianAddress(AddressError), - } - - impl AsRef<[u8]> for InitContractError { - fn as_ref(&self) -> &[u8] { - match self { - Self::AlreadyInitialized => errors::ERR_CONTRACT_INITIALIZED, - Self::InvalidCustodianAddress(e) => e.as_ref(), - } - } - } - - pub struct ProofUsed; - - impl AsRef<[u8]> for ProofUsed { - fn as_ref(&self) -> &[u8] { - PROOF_EXIST - } - } -} - -#[must_use] -pub fn proof_key(proof: &Proof) -> String { - let mut data = proof.log_index.try_to_vec().unwrap(); - data.extend(proof.receipt_index.try_to_vec().unwrap()); - data.extend(proof.header_data.clone()); - sdk::sha256(&data) - .0 - .iter() - .map(ToString::to_string) - .collect() -} - -#[cfg(test)] -mod tests { - use super::*; - use crate::deposit_event::{DepositedEvent, TokenMessageData, DEPOSITED_EVENT}; - use aurora_engine_types::parameters::connector::LogEntry; - use aurora_engine_types::types::{make_address, Address, Fee, NEP141Wei, Wei}; - use aurora_engine_types::{H160, U256}; - - const ETH_CUSTODIAN_ADDRESS: Address = - make_address(0xd045f7e1, 0x9b2488924b97f9c145b5e51d0d895a65); - - #[test] - fn test_proof_key_generates_successfully() { - let recipient_address = Address::new(H160([22u8; 20])); - let deposit_amount = Wei::new_u64(123_456_789); - let proof = create_proof(recipient_address, deposit_amount); - - let expected_key = - "1297721518512077871939115641114233180253108247225100248224214775219368216419218177247"; - let actual_key = proof_key(&proof); - - assert_eq!(expected_key, actual_key); - } - - fn create_proof(recipient_address: Address, deposit_amount: Wei) -> Proof { - let eth_custodian_address = ETH_CUSTODIAN_ADDRESS; - - let fee = Fee::new(NEP141Wei::new(0)); - let message = ["aurora", ":", recipient_address.encode().as_str()].concat(); - let token_message_data: TokenMessageData = - TokenMessageData::parse_event_message_and_prepare_token_message_data(&message, fee) - .unwrap(); - - let deposit_event = DepositedEvent { - eth_custodian_address, - sender: Address::new(H160([0u8; 20])), - token_message_data, - amount: NEP141Wei::new(deposit_amount.raw().as_u128()), - fee, - }; - - let event_schema = ethabi::Event { - name: DEPOSITED_EVENT.into(), - inputs: DepositedEvent::event_params(), - anonymous: false, - }; - let log_entry = LogEntry { - address: eth_custodian_address.raw(), - topics: vec![ - event_schema.signature(), - // the sender is not important - crate::prelude::H256::zero(), - ], - data: ethabi::encode(&[ - ethabi::Token::String(message), - ethabi::Token::Uint(U256::from(deposit_event.amount.as_u128())), - ethabi::Token::Uint(U256::from(deposit_event.fee.as_u128())), - ]), - }; - - Proof { - log_index: 1, - // Only this field matters for the purpose of this test - log_entry_data: rlp::encode(&log_entry).to_vec(), - receipt_index: 1, - receipt_data: Vec::new(), - header_data: Vec::new(), - proof: Vec::new(), - } - } -} diff --git a/engine/src/contract_methods/connector/mod.rs b/engine/src/contract_methods/connector/mod.rs new file mode 100644 index 000000000..41d58dc70 --- /dev/null +++ b/engine/src/contract_methods/connector/mod.rs @@ -0,0 +1,652 @@ +#![allow(clippy::missing_const_for_fn)] + +use crate::contract_methods::{ + predecessor_address, require_owner_only, require_running, ContractError, +}; +use crate::engine::Engine; +use crate::hashchain::with_hashchain; +use crate::prelude::{vec, ToString, Vec}; +use crate::{engine, state}; +use aurora_engine_modexp::AuroraModExp; +use aurora_engine_sdk::env::Env; +use aurora_engine_sdk::io::{StorageIntermediate, IO}; +use aurora_engine_sdk::promise::PromiseHandler; +use aurora_engine_types::borsh::{BorshDeserialize, BorshSerialize}; +use aurora_engine_types::parameters::connector::{ + Erc20Identifier, MirrorErc20TokenArgs, SetErc20MetadataArgs, +}; +use aurora_engine_types::parameters::engine::errors::ParseArgsError; +use aurora_engine_types::parameters::engine::{ + DeployErc20TokenArgs, GetErc20FromNep141CallArgs, SubmitResult, +}; +use aurora_engine_types::parameters::{ + ExitToNearPrecompileCallbackCallArgs, PromiseAction, PromiseBatchAction, +}; +use aurora_engine_types::parameters::{PromiseCreateArgs, PromiseWithCallbackArgs}; +use aurora_engine_types::storage::{EthConnectorStorageId, KeyPrefix}; +use aurora_engine_types::types::{Address, NearGas, PromiseResult, Yocto}; +use function_name::named; + +#[cfg(feature = "ext-connector")] +pub use external::{AdminControlled, EthConnector, EthConnectorContract}; +#[cfg(not(feature = "ext-connector"))] +pub use internal::{EthConnector, EthConnectorContract}; + +pub mod admin_controlled; +pub mod deposit_event; +pub mod errors; +#[cfg(feature = "ext-connector")] +pub mod external; +pub mod fungible_token; +#[cfg(not(feature = "ext-connector"))] +pub mod internal; + +pub const ERR_NOT_ENOUGH_BALANCE_FOR_FEE: &str = "ERR_NOT_ENOUGH_BALANCE_FOR_FEE"; +/// Indicate zero attached balance for promise call +pub const ZERO_ATTACHED_BALANCE: Yocto = Yocto::new(0); +/// Amount of attached gas for read-only promises. +const READ_PROMISE_ATTACHED_GAS: NearGas = NearGas::new(5_000_000_000_000); + +/// Create new eth-connector; +pub fn new_eth_connector(io: I, env: &E) -> Result<(), ContractError> { + #[cfg(not(feature = "ext-connector"))] + internal::new_eth_connector(io, env)?; + #[cfg(feature = "ext-connector")] + let (_, _) = (io, env); + + Ok(()) +} + +/// Set eth-connector data. +pub fn set_eth_connector_contract_data( + io: I, + env: &E, +) -> Result<(), ContractError> { + #[cfg(not(feature = "ext-connector"))] + internal::set_eth_connector_contract_data(io, env)?; + #[cfg(feature = "ext-connector")] + let (_, _) = (io, env); + + Ok(()) +} + +pub fn withdraw< + #[cfg(not(feature = "ext-connector"))] I: IO + Copy, + #[cfg(feature = "ext-connector")] I: IO + Copy + PromiseHandler, + E: Env, +>( + io: I, + env: &E, +) -> Result<(), ContractError> { + #[cfg(not(feature = "ext-connector"))] + internal::withdraw(io, env)?; + #[cfg(feature = "ext-connector")] + external::withdraw(io, env)?; + + Ok(()) +} + +pub fn deposit( + io: I, + env: &E, + handler: &mut H, +) -> Result, ContractError> { + #[cfg(not(feature = "ext-connector"))] + let result = internal::deposit(io, env, handler)?; + #[cfg(feature = "ext-connector")] + let result = external::deposit(io, env, handler)?; + + Ok(result) +} + +pub fn ft_on_transfer( + io: I, + env: &E, + handler: &mut H, +) -> Result<(), ContractError> { + #[cfg(not(feature = "ext-connector"))] + internal::ft_on_transfer(io, env, handler)?; + #[cfg(feature = "ext-connector")] + external::ft_on_transfer(io, env, handler)?; + + Ok(()) +} + +#[named] +pub fn deploy_erc20_token( + io: I, + env: &E, + handler: &mut H, +) -> Result { + with_hashchain(io, env, function_name!(), |mut io| { + require_running(&state::get_state(&io)?)?; + // AccountId of NEP-141 token on NEAR + let args: DeployErc20TokenArgs = io.read_input_borsh()?; + let address = engine::deploy_erc20_token(args, io, env, handler)?; + + io.return_output( + &address + .as_bytes() + .try_to_vec() + .map_err(|_| crate::errors::ERR_SERIALIZE)?, + ); + Ok(address) + }) +} + +#[named] +pub fn exit_to_near_precompile_callback( + io: I, + env: &E, + handler: &mut H, +) -> Result, ContractError> { + with_hashchain(io, env, function_name!(), |io| { + let state = state::get_state(&io)?; + require_running(&state)?; + env.assert_private_call()?; + + // This function should only be called as the callback of + // exactly one promise. + if handler.promise_results_count() != 1 { + return Err(crate::errors::ERR_PROMISE_COUNT.into()); + } + + let args: ExitToNearPrecompileCallbackCallArgs = io.read_input_borsh()?; + + let maybe_result = if let Some(PromiseResult::Successful(_)) = handler.promise_result(0) { + if let Some(args) = args.transfer_near { + let action = PromiseAction::Transfer { + amount: Yocto::new(args.amount), + }; + let promise = PromiseBatchAction { + target_account_id: args.target_account_id, + actions: vec![action], + }; + + // Safety: this call is safe because it comes from the exit to near precompile, not users. + // The call is to transfer the unwrapped wNEAR tokens. + let promise_id = unsafe { handler.promise_create_batch(&promise) }; + handler.promise_return(promise_id); + } + + None + } else if let Some(args) = args.refund { + // Exit call failed; need to refund tokens + let refund_result = engine::refund_on_error(io, env, state, &args, handler)?; + + if !refund_result.status.is_ok() { + return Err(crate::errors::ERR_REFUND_FAILURE.into()); + } + + Some(refund_result) + } else { + None + }; + + Ok(maybe_result) + }) +} + +pub fn finish_deposit( + io: I, + env: &E, + handler: &mut H, +) -> Result, ContractError> { + #[cfg(not(feature = "ext-connector"))] + let result = internal::finish_deposit(io, env, handler)?; + #[cfg(feature = "ext-connector")] + let result = external::finish_deposit(io, env, handler)?; + + Ok(result) +} + +pub fn ft_transfer< + #[cfg(not(feature = "ext-connector"))] I: IO + Copy, + #[cfg(feature = "ext-connector")] I: IO + Copy + PromiseHandler, + E: Env, +>( + io: I, + env: &E, +) -> Result<(), ContractError> { + #[cfg(not(feature = "ext-connector"))] + internal::ft_transfer(io, env)?; + #[cfg(feature = "ext-connector")] + external::ft_transfer(io, env)?; + + Ok(()) +} + +pub fn ft_transfer_call( + io: I, + env: &E, + handler: &mut H, +) -> Result, ContractError> { + #[cfg(not(feature = "ext-connector"))] + let result = internal::ft_transfer_call(io, env, handler)?; + #[cfg(feature = "ext-connector")] + let result = external::ft_transfer_call(io, env, handler)?; + + Ok(result) +} + +pub fn ft_resolve_transfer( + io: I, + env: &E, + handler: &H, +) -> Result<(), ContractError> { + #[cfg(not(feature = "ext-connector"))] + internal::ft_resolve_transfer(io, env, handler)?; + #[cfg(feature = "ext-connector")] + let (_, _, _) = (io, env, handler); + + Ok(()) +} + +pub fn storage_deposit( + io: I, + env: &E, + handler: &mut H, +) -> Result<(), ContractError> { + #[cfg(not(feature = "ext-connector"))] + internal::storage_deposit(io, env, handler)?; + #[cfg(feature = "ext-connector")] + external::storage_deposit(io, env, handler)?; + + Ok(()) +} + +pub fn storage_unregister( + io: I, + env: &E, + handler: &mut H, +) -> Result<(), ContractError> { + #[cfg(not(feature = "ext-connector"))] + internal::storage_unregister(io, env, handler)?; + #[cfg(feature = "ext-connector")] + external::storage_unregister(io, env, handler)?; + + Ok(()) +} + +pub fn storage_withdraw< + #[cfg(not(feature = "ext-connector"))] I: IO + Copy, + #[cfg(feature = "ext-connector")] I: IO + Copy + PromiseHandler, + E: Env, +>( + io: I, + env: &E, +) -> Result<(), ContractError> { + #[cfg(not(feature = "ext-connector"))] + internal::storage_withdraw(io, env)?; + #[cfg(feature = "ext-connector")] + external::storage_withdraw(io, env)?; + + Ok(()) +} + +pub fn storage_balance_of(io: I) -> Result<(), ContractError> { + #[cfg(not(feature = "ext-connector"))] + internal::storage_balance_of(io)?; + #[cfg(feature = "ext-connector")] + external::storage_balance_of(io)?; + + Ok(()) +} + +pub fn set_paused_flags(io: I, env: &E) -> Result<(), ContractError> { + #[cfg(not(feature = "ext-connector"))] + internal::set_paused_flags(io, env)?; + #[cfg(feature = "ext-connector")] + let (_, _) = (io, env); + + Ok(()) +} + +pub fn get_paused_flags(io: I) -> Result<(), ContractError> { + #[cfg(not(feature = "ext-connector"))] + internal::get_paused_flags(io)?; + #[cfg(feature = "ext-connector")] + external::get_paused_flags(io)?; + + Ok(()) +} + +pub fn is_used_proof(io: I) -> Result<(), ContractError> { + #[cfg(not(feature = "ext-connector"))] + internal::is_used_proof(io)?; + #[cfg(feature = "ext-connector")] + external::is_used_proof(io)?; + + Ok(()) +} + +pub fn ft_total_eth_supply_on_near( + io: I, +) -> Result<(), ContractError> { + #[cfg(not(feature = "ext-connector"))] + internal::ft_total_eth_supply_on_near(io)?; + #[cfg(feature = "ext-connector")] + external::ft_total_eth_supply_on_near(io)?; + + Ok(()) +} + +pub fn ft_total_eth_supply_on_aurora(io: I) -> Result<(), ContractError> { + #[cfg(not(feature = "ext-connector"))] + EthConnectorContract::init(io)?.ft_total_eth_supply_on_aurora(); + #[cfg(feature = "ext-connector")] + let _ = io; + + Ok(()) +} + +pub fn ft_balance_of(io: I) -> Result<(), ContractError> { + #[cfg(not(feature = "ext-connector"))] + internal::ft_balance_of(io)?; + #[cfg(feature = "ext-connector")] + external::ft_balance_of(io)?; + + Ok(()) +} + +pub fn ft_balance_of_eth(io: I) -> Result<(), ContractError> { + let args = io.read_input_borsh()?; + EthConnectorContract::init(io)?.ft_balance_of_eth_on_aurora(&args)?; + Ok(()) +} + +#[cfg(not(feature = "ext-connector"))] +pub fn get_accounts_counter(io: I) -> Result<(), ContractError> { + internal::get_accounts_counter(io)?; + Ok(()) +} + +#[named] +pub fn set_erc20_metadata( + io: I, + env: &E, + handler: &mut H, +) -> Result { + with_hashchain(io, env, function_name!(), |io| { + let state = state::get_state(&io)?; + require_running(&state)?; + // TODO: Define special role for this transaction. Potentially via multisig? + let is_private = env.assert_private_call(); + if is_private.is_err() { + require_owner_only(&state, &env.predecessor_account_id())?; + } + + let args: SetErc20MetadataArgs = serde_json::from_slice(&io.read_input().to_vec()) + .map_err(Into::::into)?; + let current_account_id = env.current_account_id(); + let mut engine: Engine<_, E, AuroraModExp> = Engine::new_with_state( + state, + predecessor_address(&env.predecessor_account_id()), + current_account_id, + io, + env, + ); + let result = engine.set_erc20_metadata(&args.erc20_identifier, args.metadata, handler)?; + + Ok(result) + }) +} + +pub fn get_erc20_metadata(mut io: I, env: &E) -> Result<(), ContractError> { + let erc20_identifier = + serde_json::from_slice(&io.read_input().to_vec()).map_err(Into::::into)?; + let state = state::get_state(&io)?; + let current_account_id = env.current_account_id(); + let engine: Engine<_, E, AuroraModExp> = Engine::new_with_state( + state, + predecessor_address(&env.predecessor_account_id()), + current_account_id, + io, + env, + ); + let metadata = engine.get_erc20_metadata(&erc20_identifier)?; + + io.return_output(&serde_json::to_vec(&metadata).map_err(|_| crate::errors::ERR_SERIALIZE)?); + Ok(()) +} + +pub fn set_eth_connector_contract_account( + io: I, + env: &E, +) -> Result<(), ContractError> { + #[cfg(feature = "ext-connector")] + external::set_eth_connector_account_id(io, env)?; + #[cfg(not(feature = "ext-connector"))] + let (_, _) = (io, env); + + Ok(()) +} + +pub fn get_eth_connector_contract_account(io: I) -> Result<(), ContractError> { + #[cfg(feature = "ext-connector")] + external::get_eth_connector_account_id(io)?; + #[cfg(not(feature = "ext-connector"))] + let _ = io; + + Ok(()) +} + +pub fn ft_metadata< + #[cfg(not(feature = "ext-connector"))] I: IO + Copy, + #[cfg(feature = "ext-connector")] I: IO + Copy + PromiseHandler, +>( + io: I, +) -> Result<(), ContractError> { + #[cfg(not(feature = "ext-connector"))] + internal::ft_metadata(io)?; + #[cfg(feature = "ext-connector")] + external::ft_metadata(io)?; + + Ok(()) +} + +pub fn mirror_erc20_token( + io: I, + handler: &mut H, +) -> Result<(), ContractError> { + let state = state::get_state(&io)?; + require_running(&state)?; + // TODO: Add an admin access list of accounts allowed to do it. + require_owner_only(&state, &io.predecessor_account_id())?; + + if !crate::contract_methods::silo::is_silo_mode_on(&io) { + return Err(crate::errors::ERR_ALLOWED_IN_SILO_MODE_ONLY.into()); + } + + let input = io.read_input().to_vec(); + let args = MirrorErc20TokenArgs::try_from_slice(&input) + .map_err(|_| crate::errors::ERR_BORSH_DESERIALIZE)?; + + let promise = vec![ + PromiseCreateArgs { + target_account_id: args.contract_id.clone(), + method: "get_erc20_from_nep141".to_string(), + args: GetErc20FromNep141CallArgs { + nep141: args.nep141.clone(), + } + .try_to_vec() + .map_err(|_| crate::errors::ERR_SERIALIZE)?, + attached_balance: Yocto::new(0), + attached_gas: READ_PROMISE_ATTACHED_GAS, + }, + PromiseCreateArgs { + target_account_id: args.contract_id, + method: "get_erc20_metadata".into(), + args: serde_json::to_vec(&Erc20Identifier::from(args.nep141)) + .map_err(|_| crate::errors::ERR_SERIALIZE)?, + attached_balance: Yocto::new(0), + attached_gas: READ_PROMISE_ATTACHED_GAS, + }, + ]; + + let callback = PromiseCreateArgs { + target_account_id: io.current_account_id(), + method: "mirror_erc20_token_callback".to_string(), + args: input, + attached_balance: Yocto::new(0), + attached_gas: READ_PROMISE_ATTACHED_GAS, + }; + // Safe because these promises are read-only calls to the main engine contract + // and this transaction could be executed by the owner of the contract only. + let promise_id = unsafe { + let promise_id = handler.promise_create_and_combine(&promise); + handler.promise_attach_callback(promise_id, &callback) + }; + + handler.promise_return(promise_id); + + Ok(()) +} + +#[named] +pub fn mirror_erc20_token_callback( + io: I, + env: &E, + handler: &mut H, +) -> Result<(), ContractError> { + with_hashchain(io, env, function_name!(), |mut io| { + let state = state::get_state(&io)?; + + require_running(&state)?; + env.assert_private_call()?; + + if handler.promise_results_count() != 2 { + return Err(crate::errors::ERR_PROMISE_COUNT.into()); + } + + let args: MirrorErc20TokenArgs = io.read_input_borsh()?; + let erc20_address = + if let Some(PromiseResult::Successful(bytes)) = handler.promise_result(0) { + Address::try_from_slice(&bytes)? + } else { + return Err(crate::errors::ERR_GETTING_ERC20_FROM_NEP141.into()); + }; + + let erc20_metadata = + if let Some(PromiseResult::Successful(bytes)) = handler.promise_result(1) { + serde_json::from_slice(&bytes).map_err(Into::::into)? + } else { + return Err(crate::errors::ERR_GETTING_ERC20_FROM_NEP141.into()); + }; + + let address = + engine::mirror_erc20_token(args, erc20_address, erc20_metadata, io, env, handler)?; + + io.return_output( + &address + .as_bytes() + .try_to_vec() + .map_err(|_| crate::errors::ERR_SERIALIZE)?, + ); + + Ok(()) + }) +} + +fn construct_contract_key(suffix: EthConnectorStorageId) -> Vec { + crate::prelude::bytes_to_key(KeyPrefix::EthConnector, &[u8::from(suffix)]) +} + +fn get_contract_data( + io: &I, + suffix: EthConnectorStorageId, +) -> Result { + io.read_storage(&construct_contract_key(suffix)) + .ok_or(errors::StorageReadError::KeyNotFound) + .and_then(|x| { + x.to_value() + .map_err(|_| errors::StorageReadError::BorshDeserialize) + }) +} + +#[cfg(any(not(feature = "ext-connector"), test))] +#[must_use] +fn proof_key(proof: &aurora_engine_types::parameters::connector::Proof) -> crate::prelude::String { + let mut data = proof.log_index.try_to_vec().unwrap(); + data.extend(proof.receipt_index.try_to_vec().unwrap()); + data.extend(proof.header_data.clone()); + aurora_engine_sdk::sha256(&data) + .0 + .iter() + .map(ToString::to_string) + .collect() +} + +#[cfg(test)] +mod tests { + use super::proof_key; + use crate::contract_methods::connector::deposit_event::{ + DepositedEvent, TokenMessageData, DEPOSITED_EVENT, + }; + use aurora_engine_types::parameters::connector::{LogEntry, Proof}; + use aurora_engine_types::types::{make_address, Address, Fee, NEP141Wei, Wei}; + use aurora_engine_types::{H160, U256}; + + const ETH_CUSTODIAN_ADDRESS: Address = + make_address(0xd045f7e1, 0x9b2488924b97f9c145b5e51d0d895a65); + + #[test] + fn test_proof_key_generates_successfully() { + let recipient_address = Address::new(H160([22u8; 20])); + let deposit_amount = Wei::new_u64(123_456_789); + let proof = create_proof(recipient_address, deposit_amount); + + let expected_key = + "1297721518512077871939115641114233180253108247225100248224214775219368216419218177247"; + let actual_key = proof_key(&proof); + + assert_eq!(expected_key, actual_key); + } + + fn create_proof(recipient_address: Address, deposit_amount: Wei) -> Proof { + let eth_custodian_address = ETH_CUSTODIAN_ADDRESS; + + let fee = Fee::new(NEP141Wei::new(0)); + let message = ["aurora", ":", recipient_address.encode().as_str()].concat(); + let token_message_data: TokenMessageData = + TokenMessageData::parse_event_message_and_prepare_token_message_data(&message, fee) + .unwrap(); + + let deposit_event = DepositedEvent { + eth_custodian_address, + sender: Address::new(H160([0u8; 20])), + token_message_data, + amount: NEP141Wei::new(deposit_amount.raw().as_u128()), + fee, + }; + + let event_schema = ethabi::Event { + name: DEPOSITED_EVENT.into(), + inputs: DepositedEvent::event_params(), + anonymous: false, + }; + let log_entry = LogEntry { + address: eth_custodian_address.raw(), + topics: vec![ + event_schema.signature(), + // the sender is not important + crate::prelude::H256::zero(), + ], + data: ethabi::encode(&[ + ethabi::Token::String(message), + ethabi::Token::Uint(U256::from(deposit_event.amount.as_u128())), + ethabi::Token::Uint(U256::from(deposit_event.fee.as_u128())), + ]), + }; + + Proof { + log_index: 1, + // Only this field matters for the purpose of this test + log_entry_data: rlp::encode(&log_entry).to_vec(), + receipt_index: 1, + receipt_data: Vec::new(), + header_data: Vec::new(), + proof: Vec::new(), + } + } +} diff --git a/engine/src/contract_methods/evm_transactions.rs b/engine/src/contract_methods/evm_transactions.rs index fe713dca8..f773ee6d6 100644 --- a/engine/src/contract_methods/evm_transactions.rs +++ b/engine/src/contract_methods/evm_transactions.rs @@ -35,7 +35,7 @@ pub fn deploy_code( io, env, ); - let result = engine.deploy_code_with_input(input, handler)?; + let result = engine.deploy_code_with_input(input, None, handler)?; let result_bytes = result.try_to_vec().map_err(|_| errors::ERR_SERIALIZE)?; io.return_output(&result_bytes); Ok(result) diff --git a/engine/src/contract_methods/mod.rs b/engine/src/contract_methods/mod.rs index 3a0c949e9..8f615e04b 100644 --- a/engine/src/contract_methods/mod.rs +++ b/engine/src/contract_methods/mod.rs @@ -12,6 +12,7 @@ use aurora_engine_types::{account_id::AccountId, fmt, types::Address, Box}; pub mod admin; pub mod connector; pub mod evm_transactions; +pub mod silo; pub mod xcc; pub struct ContractError { diff --git a/engine/src/contract_methods/silo/mod.rs b/engine/src/contract_methods/silo/mod.rs new file mode 100644 index 000000000..743dbe291 --- /dev/null +++ b/engine/src/contract_methods/silo/mod.rs @@ -0,0 +1,254 @@ +use aurora_engine_sdk::io::{StorageIntermediate, IO}; +#[cfg(feature = "contract")] +use aurora_engine_sdk::{env::Env, types::SdkUnwrap}; +use aurora_engine_types::account_id::AccountId; +use aurora_engine_types::parameters::silo::{ + SiloParamsArgs, WhitelistArgs, WhitelistKind, WhitelistKindArgs, WhitelistStatusArgs, +}; +use aurora_engine_types::storage::{bytes_to_key, KeyPrefix}; +use aurora_engine_types::types::{Address, Wei}; +use aurora_engine_types::AsBytes; + +#[cfg(feature = "contract")] +use crate::engine::EngineErrorKind; +use crate::prelude::Vec; + +use whitelist::Whitelist; + +mod whitelist; + +const GAS_COST_KEY: &[u8] = b"GAS_COST_KEY"; +const ERC20_FALLBACK_KEY: &[u8] = b"ERC20_FALLBACK_KEY"; + +/// Return SILO parameters. +pub fn get_silo_params(io: &I) -> Option { + let params = get_fixed_gas_cost(io) + .and_then(|cost| get_erc20_fallback_address(io).map(|address| (cost, address))); + + params.map(|(cost, address)| SiloParamsArgs { + fixed_gas_cost: cost, + erc20_fallback_address: address, + }) +} + +/// Set SILO parameters. +pub fn set_silo_params(io: &mut I, args: Option) { + let (cost, address) = args.map_or((None, None), |params| { + ( + Some(params.fixed_gas_cost), + Some(params.erc20_fallback_address), + ) + }); + + set_fixed_gas_cost(io, cost); + set_erc20_fallback_address(io, address); +} + +/// Return true if the Silo mode is on (`fixed_gas_cost` is set). +pub fn is_silo_mode_on(io: &I) -> bool { + get_fixed_gas_cost(io).is_some() +} + +/// Return fixed gas cost. +pub fn get_fixed_gas_cost(io: &I) -> Option { + let key = fixed_gas_cost_key(); + io.read_u256(&key).ok().map(Wei::new) +} + +/// Set fixed gas cost. +pub fn set_fixed_gas_cost(io: &mut I, cost: Option) { + let key = fixed_gas_cost_key(); + + if let Some(cost) = cost { + io.write_storage(&key, &cost.to_bytes()); + } else { + io.remove_storage(&key); + } +} + +/// Return ERC-20 fallback address. +pub fn get_erc20_fallback_address(io: &I) -> Option
{ + let key = erc20_fallback_address_key(); + io.read_storage(&key)?.to_value().ok() +} + +/// Set ERC-20 fallback address. +pub fn set_erc20_fallback_address(io: &mut I, address: Option
) { + let key = erc20_fallback_address_key(); + + if let Some(address) = address { + io.write_storage(&key, address.as_bytes()); + } else { + io.remove_storage(&key); + } +} + +/// Add an entry to a white list depending on a kind of list types in provided arguments. +pub fn add_entry_to_whitelist(io: &I, args: &WhitelistArgs) { + let (kind, entry) = get_kind_and_entry(args); + Whitelist::init(io, kind).add(entry); +} + +/// Add an entries to a white list depending on a kind of list types in provided arguments. +pub fn add_entry_to_whitelist_batch>( + io: &I, + entries: A, +) { + for entry in entries { + add_entry_to_whitelist(io, &entry); + } +} + +/// Remove an entries to a white list depending on a kind of list types in provided arguments. +pub fn remove_entry_from_whitelist(io: &I, args: &WhitelistArgs) { + let (kind, entry) = get_kind_and_entry(args); + Whitelist::init(io, kind).remove(entry); +} + +/// Set status of the provided white list. +pub fn set_whitelist_status(io: &I, args: &WhitelistStatusArgs) { + whitelist::set_whitelist_status(io, args); +} + +/// Return status of the provided white list. +pub fn get_whitelist_status(io: &I, args: &WhitelistKindArgs) -> WhitelistStatusArgs { + whitelist::get_whitelist_status(io, args) +} + +/// Check if the calling user is admin or owner of the contract. +#[cfg(feature = "contract")] +pub fn assert_admin(io: &I) -> Result<(), EngineErrorKind> { + let predecessor = io.predecessor_account_id(); + + if is_owner(io, &predecessor) || is_admin(io, &predecessor) { + return Ok(()); + } + + Err(EngineErrorKind::NotAllowed) +} + +/// Check if a user has the right to deploy EVM code. +pub fn is_allow_deploy(io: &I, account: &AccountId, address: &Address) -> bool { + is_admin(io, account) && is_evm_admin(io, address) +} + +/// Check if a user has the right to submit transactions. +pub fn is_allow_submit(io: &I, account: &AccountId, address: &Address) -> bool { + is_address_allowed(io, address) && is_account_allowed(io, account) +} + +/// Check if a user has the right to receive erc20 tokens. +pub fn is_allow_receive_erc20_tokens(io: &I, address: &Address) -> bool { + is_address_allowed(io, address) +} + +fn is_admin(io: &I, account_id: &AccountId) -> bool { + let list = Whitelist::init(io, WhitelistKind::Admin); + !list.is_enabled() || list.is_exist(account_id) +} + +fn is_evm_admin(io: &I, address: &Address) -> bool { + let list = Whitelist::init(io, WhitelistKind::EvmAdmin); + !list.is_enabled() || list.is_exist(address) +} + +#[cfg(feature = "contract")] +fn is_owner(io: &I, account_id: &AccountId) -> bool { + let state = crate::state::get_state(io).sdk_unwrap(); + &state.owner_id == account_id +} + +fn is_address_allowed(io: &I, address: &Address) -> bool { + let list = Whitelist::init(io, WhitelistKind::Address); + !list.is_enabled() || list.is_exist(address) +} + +fn is_account_allowed(io: &I, account: &AccountId) -> bool { + let list = Whitelist::init(io, WhitelistKind::Account); + !list.is_enabled() || list.is_exist(account) +} + +fn fixed_gas_cost_key() -> Vec { + bytes_to_key(KeyPrefix::Silo, GAS_COST_KEY) +} + +fn erc20_fallback_address_key() -> Vec { + bytes_to_key(KeyPrefix::Silo, ERC20_FALLBACK_KEY) +} + +fn get_kind_and_entry(args: &WhitelistArgs) -> (WhitelistKind, &dyn AsBytes) { + match args { + WhitelistArgs::WhitelistAddressArgs(args) => (args.kind, &args.address), + WhitelistArgs::WhitelistAccountArgs(args) => (args.kind, &args.account_id), + } +} + +#[cfg(test)] +mod access_test { + use super::*; + use aurora_engine_test_doubles::io::{Storage, StoragePointer}; + use std::cell::RefCell; + + #[test] + fn test_set_fixed_gas_cost() { + let cost = Some(Wei::new_u64(1000)); + let storage = RefCell::new(Storage::default()); + let mut io = StoragePointer(&storage); + + assert_eq!(get_fixed_gas_cost(&io), None); + set_fixed_gas_cost(&mut io, cost); + assert_eq!(get_fixed_gas_cost(&io), cost); + } + + #[test] + fn test_adding_entry_to_whitelist() { + let storage = RefCell::new(Storage::default()); + let io = StoragePointer(&storage); + let account_id = "some-account.near".parse().unwrap(); + let address = Address::zero(); + let mut list = Whitelist::init(&io, WhitelistKind::Account); + + assert!(!is_account_allowed(&io, &account_id)); + list.add(&account_id); + assert!(is_account_allowed(&io, &account_id)); + + let mut list = Whitelist::init(&io, WhitelistKind::Address); + assert!(!is_address_allowed(&io, &address)); + list.add(&address); + assert!(is_address_allowed(&io, &address)); + + assert!(is_allow_submit(&io, &account_id, &address)); + } + + #[test] + fn test_check_set_whitelist_status() { + let storage = RefCell::new(Storage::default()); + let io = StoragePointer(&storage); + + let status = get_whitelist_status( + &io, + &WhitelistKindArgs { + kind: WhitelistKind::Admin, + }, + ); + + assert!(status.active); + + set_whitelist_status( + &io, + &WhitelistStatusArgs { + kind: WhitelistKind::Admin, + active: false, + }, + ); + + let status = get_whitelist_status( + &io, + &WhitelistKindArgs { + kind: WhitelistKind::Admin, + }, + ); + + assert!(!status.active); + } +} diff --git a/engine/src/contract_methods/silo/whitelist.rs b/engine/src/contract_methods/silo/whitelist.rs new file mode 100644 index 000000000..122430441 --- /dev/null +++ b/engine/src/contract_methods/silo/whitelist.rs @@ -0,0 +1,147 @@ +use aurora_engine_sdk::io::{StorageIntermediate, IO}; +use aurora_engine_types::parameters::silo::{ + WhitelistKind, WhitelistKindArgs, WhitelistStatusArgs, +}; +use aurora_engine_types::storage::{bytes_to_key, KeyPrefix}; +use aurora_engine_types::AsBytes; + +use crate::prelude::Vec; + +const STATUS: &[u8] = b"LIST_STATUS"; + +/// `Whitelist` for checking access before interacting with the Aurora EVM. +/// * io - I/O trait handler +pub struct Whitelist { + io: I, + kind: WhitelistKind, +} + +impl Whitelist +where + I: IO + Copy, +{ + /// Init a new whitelist of `WhitelistKind`. + pub const fn init(io: &I, kind: WhitelistKind) -> Self { + Self { io: *io, kind } + } + + /// Enable a whitelist. (A whitelist is enabled after creation). + pub fn enable(&mut self) { + let key = self.key(STATUS); + self.io.write_storage(&key, &[1]); + } + + /// Disable a whitelist. + pub fn disable(&mut self) { + let key = self.key(STATUS); + self.io.write_storage(&key, &[0]); + } + + /// Check if the whitelist is enabled. + pub fn is_enabled(&self) -> bool { + // White list is enabled by default. So return `true` if the key doesn't exist. + let key = self.key(STATUS); + self.io + .read_storage(&key) + .map_or(true, |value| value.to_vec() == [1]) + } + + fn key(&self, value: &[u8]) -> Vec { + let mut bytes = Vec::with_capacity(1 + value.len()); + + bytes.push(u8::from(self.kind)); + bytes.extend_from_slice(value); + bytes_to_key(KeyPrefix::Whitelist, &bytes) + } + + /// Add a new element to the whitelist. + pub fn add(&mut self, element: &A) { + let key = self.key(element.as_bytes()); + self.io.write_storage(&key, &[]); + } + + /// Remove a new element from the whitelist. + pub fn remove(&mut self, element: &A) { + let key = self.key(element.as_bytes()); + self.io.remove_storage(&key); + } + + /// Check if the element is present in the whitelist. + pub fn is_exist(&self, element: &A) -> bool { + let key = self.key(element.as_bytes()); + self.io.storage_has_key(&key) + } +} + +/// Set status of the whitelist. +pub fn set_whitelist_status(io: &I, args: &WhitelistStatusArgs) { + let mut list = Whitelist::init(io, args.kind); + + if args.active { + list.enable(); + } else { + list.disable(); + } +} + +/// Get status of the whitelist. +pub fn get_whitelist_status(io: &I, args: &WhitelistKindArgs) -> WhitelistStatusArgs { + WhitelistStatusArgs { + kind: args.kind, + active: Whitelist::init(io, args.kind).is_enabled(), + } +} + +#[cfg(test)] +mod tests { + use super::{Whitelist, WhitelistKind}; + use aurora_engine_test_doubles::io::{Storage, StoragePointer}; + use aurora_engine_types::account_id::AccountId; + use aurora_engine_types::types::Address; + use std::cell::RefCell; + + #[test] + fn test_init_white_list() { + let storage = RefCell::new(Storage::default()); + let io = StoragePointer(&storage); + let mut white_list = Whitelist::init(&io, WhitelistKind::Admin); + let account: AccountId = "aurora".parse().unwrap(); + let address = Address::zero(); + + white_list.add(&account); + assert!(white_list.is_exist(&account)); + white_list.remove(&account); + assert!(!white_list.is_exist(&account)); + + let mut white_list = Whitelist::init(&io, WhitelistKind::Account); + white_list.add(&account); + assert!(white_list.is_exist(&account)); + white_list.remove(&account); + assert!(!white_list.is_exist(&account)); + + let mut white_list = Whitelist::init(&io, WhitelistKind::EvmAdmin); + + white_list.add(&address); + assert!(white_list.is_exist(&address)); + white_list.remove(&address); + assert!(!white_list.is_exist(&address)); + + let mut white_list = Whitelist::init(&io, WhitelistKind::Address); + + white_list.add(&address); + assert!(white_list.is_exist(&address)); + white_list.remove(&address); + assert!(!white_list.is_exist(&address)); + } + + #[test] + fn test_disable_whitelist() { + let storage = RefCell::new(Storage::default()); + let io = StoragePointer(&storage); + let mut white_list = Whitelist::init(&io, WhitelistKind::Account); + // Whitelist is enabled after creation. + assert!(white_list.is_enabled()); + white_list.disable(); + assert!(!white_list.is_enabled()); + } +} diff --git a/engine/src/engine.rs b/engine/src/engine.rs index abee82159..31a48f3c4 100644 --- a/engine/src/engine.rs +++ b/engine/src/engine.rs @@ -8,7 +8,6 @@ use evm::backend::{Apply, ApplyBackend, Backend, Basic, Log}; use evm::executor; use evm::{Config, CreateScheme, ExitError, ExitFatal, ExitReason}; -use crate::connector::EthConnectorContract; use crate::map::BijectionMap; use crate::{errors, state}; use aurora_engine_sdk::caching::FullCache; @@ -17,6 +16,9 @@ use aurora_engine_sdk::io::{StorageIntermediate, IO}; use aurora_engine_sdk::promise::{PromiseHandler, PromiseId, ReadOnlyPromiseHandler}; use crate::accounting; +#[cfg(not(feature = "ext-connector"))] +use crate::contract_methods::connector; +use crate::contract_methods::silo; use crate::parameters::{DeployErc20TokenArgs, TransactionStatus}; use crate::pausables::{ EngineAuthorizer, EnginePrecompilesPauser, PausedPrecompilesChecker, PrecompileFlags, @@ -35,7 +37,9 @@ use crate::prelude::{ use crate::state::EngineState; use aurora_engine_modexp::{AuroraModExp, ModExpAlgorithm}; use aurora_engine_precompiles::PrecompileConstructorContext; -use aurora_engine_types::parameters::connector::Erc20Metadata; +use aurora_engine_types::parameters::connector::{ + Erc20Identifier, Erc20Metadata, MirrorErc20TokenArgs, +}; use aurora_engine_types::parameters::engine::FunctionCallArgsV2; use core::cell::RefCell; use core::iter::once; @@ -116,7 +120,11 @@ pub enum EngineErrorKind { MaxPriorityGasFeeTooLarge, GasPayment(GasPaymentError), GasOverflow, + NotAllowed, + SameOwner, + NotOwner, NonExistedKey, + Erc20FromNep141, } impl EngineErrorKind { @@ -150,7 +158,11 @@ impl EngineErrorKind { Self::MaxPriorityGasFeeTooLarge => errors::ERR_MAX_PRIORITY_FEE_GREATER, Self::GasPayment(e) => e.as_ref(), Self::GasOverflow => errors::ERR_GAS_OVERFLOW, + Self::NotAllowed => errors::ERR_NOT_ALLOWED, + Self::SameOwner => errors::ERR_SAME_OWNER, + Self::NotOwner => errors::ERR_NOT_OWNER, Self::NonExistedKey => errors::ERR_FUNCTION_CALL_KEY_NOT_FOUND, + Self::Erc20FromNep141 => errors::ERR_GETTING_ERC20_FROM_NEP141, Self::EvmFatal(_) | Self::EvmError(_) => unreachable!(), // unused misc } } @@ -299,27 +311,20 @@ impl TryFrom> for NEP141Account { } } -pub const ERR_INVALID_NEP141_ACCOUNT_ID: &str = "ERR_INVALID_NEP141_ACCOUNT_ID"; - #[derive(Debug)] pub enum GetErc20FromNep141Error { InvalidNep141AccountId, Nep141NotFound, -} - -impl GetErc20FromNep141Error { - #[must_use] - pub const fn to_str(&self) -> &str { - match self { - Self::InvalidNep141AccountId => ERR_INVALID_NEP141_ACCOUNT_ID, - Self::Nep141NotFound => "ERR_NEP141_NOT_FOUND", - } - } + InvalidAddress, } impl AsRef<[u8]> for GetErc20FromNep141Error { fn as_ref(&self) -> &[u8] { - self.to_str().as_bytes() + match self { + Self::InvalidNep141AccountId => errors::ERR_INVALID_NEP141_ACCOUNT_ID, + Self::Nep141NotFound => errors::ERR_NEP141_NOT_FOUND, + Self::InvalidAddress => errors::ERR_PARSE_ADDRESS, + } } } @@ -327,21 +332,16 @@ impl AsRef<[u8]> for GetErc20FromNep141Error { pub enum RegisterTokenError { InvalidNep141AccountId, TokenAlreadyRegistered, -} - -impl RegisterTokenError { - #[must_use] - pub const fn to_str(&self) -> &str { - match self { - Self::InvalidNep141AccountId => ERR_INVALID_NEP141_ACCOUNT_ID, - Self::TokenAlreadyRegistered => "ERR_NEP141_TOKEN_ALREADY_REGISTERED", - } - } + InvalidAddress, } impl AsRef<[u8]> for RegisterTokenError { fn as_ref(&self) -> &[u8] { - self.to_str().as_bytes() + match self { + Self::InvalidNep141AccountId => errors::ERR_INVALID_NEP141_ACCOUNT_ID, + Self::TokenAlreadyRegistered => errors::ERR_NEP141_TOKEN_ALREADY_REGISTERED, + Self::InvalidAddress => errors::ERR_PARSE_ADDRESS, + } } } @@ -350,6 +350,7 @@ pub enum ReadMetadataError { DecodeError, WrongType, NoValue, + Nep141NotFound, EngineError(EngineErrorKind), } @@ -359,6 +360,7 @@ impl AsRef<[u8]> for ReadMetadataError { Self::DecodeError => errors::ERR_DECODING_TOKEN, Self::WrongType => errors::ERR_WRONG_TOKEN_TYPE, Self::NoValue => errors::ERR_TOKEN_NO_VALUE, + Self::Nep141NotFound => errors::ERR_NEP141_NOT_FOUND, Self::EngineError(e) => e.as_ref(), } } @@ -454,23 +456,31 @@ impl<'env, I: IO + Copy, E: Env, M: ModExpAlgorithm> Engine<'env, I, E, M> { sender: &Address, transaction: &NormalizedEthTransaction, max_gas_price: Option, + fixed_gas_cost: Option, ) -> Result { - if transaction.max_fee_per_gas.is_zero() { + if transaction.max_fee_per_gas.is_zero() && fixed_gas_cost.is_none() { return Ok(GasPaymentResult::default()); } - let priority_fee_per_gas = transaction - .max_priority_fee_per_gas - .min(transaction.max_fee_per_gas - self.block_base_fee_per_gas()); - let priority_fee_per_gas = max_gas_price.map_or(priority_fee_per_gas, |price| { - price.min(priority_fee_per_gas) - }); - let effective_gas_price = priority_fee_per_gas + self.block_base_fee_per_gas(); - let gas_limit = transaction.gas_limit; - let prepaid_amount = gas_limit - .checked_mul(effective_gas_price) - .map(Wei::new) - .ok_or(GasPaymentError::EthAmountOverflow)?; + let (prepaid_amount, effective_gas_price, priority_fee_per_gas) = + if let Some(cost) = fixed_gas_cost { + (cost, cost.raw(), cost.raw()) + } else { + let priority_fee_per_gas = transaction + .max_priority_fee_per_gas + .min(transaction.max_fee_per_gas - self.block_base_fee_per_gas()); + let priority_fee_per_gas = max_gas_price.map_or(priority_fee_per_gas, |price| { + price.min(priority_fee_per_gas) + }); + let effective_gas_price = priority_fee_per_gas + self.block_base_fee_per_gas(); + let prepaid_amount = transaction + .gas_limit + .checked_mul(effective_gas_price) + .map(Wei::new) + .ok_or(GasPaymentError::EthAmountOverflow)?; + + (prepaid_amount, effective_gas_price, priority_fee_per_gas) + }; let new_balance = get_balance(&self.io, sender) .checked_sub(prepaid_amount) @@ -490,18 +500,21 @@ impl<'env, I: IO + Copy, E: Env, M: ModExpAlgorithm> Engine<'env, I, E, M> { pub fn deploy_code_with_input( &mut self, input: Vec, + address: Option
, handler: &mut P, ) -> EngineResult { let origin = Address::new(self.origin()); let value = Wei::zero(); - self.deploy_code(origin, value, input, u64::MAX, Vec::new(), handler) + self.deploy_code(origin, value, input, address, u64::MAX, Vec::new(), handler) } + #[allow(clippy::too_many_arguments)] pub fn deploy_code( &mut self, origin: Address, value: Wei, input: Vec, + address: Option
, gas_limit: u64, access_list: Vec<(H160, Vec)>, // See EIP-2930 handler: &mut P, @@ -511,11 +524,27 @@ impl<'env, I: IO + Copy, E: Env, M: ModExpAlgorithm> Engine<'env, I, E, M> { let executor_params = StackExecutorParams::new(gas_limit, precompiles); let mut executor = executor_params.make_executor(self); - let address = executor.create_address(CreateScheme::Legacy { - caller: origin.raw(), - }); - let (exit_reason, return_value) = - executor.transact_create(origin.raw(), value.raw(), input, gas_limit, access_list); + let scheme = address.map_or_else( + || CreateScheme::Legacy { + caller: origin.raw(), + }, + |address| CreateScheme::Fixed(address.raw()), + ); + let address = executor.create_address(scheme); + let (exit_reason, return_value) = match scheme { + CreateScheme::Legacy { caller } => { + executor.transact_create(caller, value.raw(), input, gas_limit, access_list) + } + CreateScheme::Fixed(address) => executor.transact_create_fixed( + origin.raw(), + address, + value.raw(), + input, + gas_limit, + access_list, + ), + CreateScheme::Create2 { .. } => unreachable!(), + }; let result = if exit_reason.is_succeed() { address.0.to_vec() } else { @@ -672,6 +701,9 @@ impl<'env, I: IO + Copy, E: Env, M: ModExpAlgorithm> Engine<'env, I, E, M> { Err(GetErc20FromNep141Error::InvalidNep141AccountId) => { return Err(RegisterTokenError::InvalidNep141AccountId); } + Err(GetErc20FromNep141Error::InvalidAddress) => { + return Err(RegisterTokenError::InvalidAddress); + } Ok(_) => return Err(RegisterTokenError::TokenAlreadyRegistered), } @@ -682,7 +714,7 @@ impl<'env, I: IO + Copy, E: Env, M: ModExpAlgorithm> Engine<'env, I, E, M> { } /// Transfers an amount from a given sender to a receiver, provided that - /// the have enough in their balance. + /// they have enough in their balance. /// /// If the sender can send, and the receiver can receive, then the transfer /// will execute successfully. @@ -705,7 +737,7 @@ impl<'env, I: IO + Copy, E: Env, M: ModExpAlgorithm> Engine<'env, I, E, M> { ) } - /// Mint tokens for recipient on a particular ERC20 token + /// Mint tokens for recipient on a particular ERC-20 token /// This function should return the amount of tokens unused, /// which will be always all () if there is any problem /// with the input, or 0 if tokens were minted successfully. @@ -726,7 +758,7 @@ impl<'env, I: IO + Copy, E: Env, M: ModExpAlgorithm> Engine<'env, I, E, M> { let output_on_fail = str_amount.as_bytes(); // Parse message to determine recipient - let recipient = { + let mut recipient = { // Message format: // Recipient of the transaction - 40 characters (Address in hex) let message = args.msg.as_bytes(); @@ -741,6 +773,12 @@ impl<'env, I: IO + Copy, E: Env, M: ModExpAlgorithm> Engine<'env, I, E, M> { ))) }; + if let Some(fallback_address) = silo::get_erc20_fallback_address(&self.io) { + if !silo::is_allow_receive_erc20_tokens(&self.io, &recipient) { + recipient = fallback_address; + } + }; + let erc20_token = Address::from_array(unwrap_res_or_finish!( unwrap_res_or_finish!( get_erc20_from_nep141(&self.io, token), @@ -809,8 +847,11 @@ impl<'env, I: IO + Copy, E: Env, M: ModExpAlgorithm> Engine<'env, I, E, M> { /// Read metadata of ERC-20 contract. pub fn get_erc20_metadata( &self, - erc20_address: Address, + erc20_identifier: &Erc20Identifier, ) -> Result { + let erc20_address = self + .identifier_to_address(erc20_identifier) + .map_err(|_| ReadMetadataError::Nep141NotFound)?; let name = self .view_with_selector( erc20_address, @@ -848,10 +889,13 @@ impl<'env, I: IO + Copy, E: Env, M: ModExpAlgorithm> Engine<'env, I, E, M> { /// Set metadata of ERC-20 contract. pub fn set_erc20_metadata( &mut self, - erc20_address: Address, + erc20_identifier: &Erc20Identifier, erc20_metadata: Erc20Metadata, handler: &mut P, ) -> EngineResult { + let erc20_address = self + .identifier_to_address(erc20_identifier) + .map_err(|_| EngineErrorKind::Erc20FromNep141)?; let args = ethabi::encode(&[ ethabi::Token::String(erc20_metadata.name), ethabi::Token::String(erc20_metadata.symbol), @@ -930,6 +974,20 @@ impl<'env, I: IO + Copy, E: Env, M: ModExpAlgorithm> Engine<'env, I, E, M> { .pop() .ok_or(ReadMetadataError::NoValue) } + + fn identifier_to_address( + &self, + identifier: &Erc20Identifier, + ) -> Result { + match identifier { + Erc20Identifier::Erc20 { address } => Ok(*address), + Erc20Identifier::Nep141 { account_id } => get_erc20_from_nep141(&self.io, account_id) + .and_then(|bytes| { + Address::try_from_slice(&bytes) + .map_err(|_| GetErc20FromNep141Error::InvalidAddress) + }), + } + } } pub fn submit( @@ -952,6 +1010,7 @@ pub fn submit( ) } +#[allow(clippy::too_many_lines)] pub fn submit_with_alt_modexp< I: IO + Copy, E: Env, @@ -990,6 +1049,21 @@ pub fn submit_with_alt_modexp< .map_err(|_e| EngineErrorKind::InvalidSignature)? }; + let fixed_gas_cost = silo::get_fixed_gas_cost(&io); + let transaction = if fixed_gas_cost.is_some() { + let mut tx = transaction; + // In the case of SILO, we don't care about gas value because the price is fixed. + // So we can change `gas_limit` to the max value for EVM. It excludes the ERR_INTRINSIC_GAS + // error if a user sets a gas limit value lower than needed for transaction execution. + tx.gas_limit = u64::MAX.into(); + tx + } else { + transaction + }; + + // Check if the sender has rights to submit transactions or deploy code on SILO mode. + assert_access(&io, env, &fixed_gas_cost, &transaction)?; + // Validate the chain ID, if provided inside the signature: if let Some(chain_id) = transaction.chain_id { if U256::from(chain_id) != U256::from(state.chain_id) { @@ -1004,14 +1078,16 @@ pub fn submit_with_alt_modexp< check_nonce(&io, &sender, &transaction.nonce)?; - // Check intrinsic gas is covered by transaction gas limit - match transaction.intrinsic_gas(CONFIG) { - Err(_e) => { - return Err(EngineErrorKind::GasOverflow.into()); - } - Ok(intrinsic_gas) => { - if transaction.gas_limit < intrinsic_gas.into() { - return Err(EngineErrorKind::IntrinsicGasNotMet.into()); + if fixed_gas_cost.is_none() { + // Check intrinsic gas is covered by transaction gas limit + match transaction.intrinsic_gas(CONFIG) { + Err(_e) => { + return Err(EngineErrorKind::GasOverflow.into()); + } + Ok(intrinsic_gas) => { + if transaction.gas_limit < intrinsic_gas.into() { + return Err(EngineErrorKind::IntrinsicGasNotMet.into()); + } } } } @@ -1022,14 +1098,15 @@ pub fn submit_with_alt_modexp< let mut engine: Engine<_, _, M> = Engine::new_with_state(state, sender, current_account_id, io, env); + let max_gas_price = args.max_gas_price.map(Into::into); let prepaid_amount = - match engine.charge_gas(&sender, &transaction, args.max_gas_price.map(Into::into)) { + match engine.charge_gas(&sender, &transaction, max_gas_price, fixed_gas_cost) { Ok(gas_result) => gas_result, Err(err) => { return Err(EngineErrorKind::GasPayment(err).into()); } }; - let gas_limit: u64 = transaction + let gas_limit = transaction .gas_limit .try_into() .map_err(|_| EngineErrorKind::GasOverflow)?; @@ -1055,6 +1132,7 @@ pub fn submit_with_alt_modexp< sender, transaction.value, transaction.data, + None, gas_limit, access_list, handler, @@ -1062,17 +1140,19 @@ pub fn submit_with_alt_modexp< // TODO: charge for storage }; - // Give refund + // Give refund. let gas_used = match &result { Ok(submit_result) => submit_result.gas_used, Err(engine_err) => engine_err.gas_used, }; + refund_unused_gas( &mut io, &sender, gas_used, &prepaid_amount, &relayer_address, + fixed_gas_cost, ) .map_err(|e| EngineError { gas_used, @@ -1184,28 +1264,40 @@ pub fn refund_unused_gas( gas_used: u64, gas_result: &GasPaymentResult, relayer: &Address, + fixed_gas_cost: Option, ) -> Result<(), GasPaymentError> { if gas_result.effective_gas_price.is_zero() { return Ok(()); } - let gas_to_wei = |price: U256| { - U256::from(gas_used) - .checked_mul(price) - .map(Wei::new) - .ok_or(GasPaymentError::EthAmountOverflow) - }; + let (refund, relayer_reward) = if let Some(fixed_cost) = fixed_gas_cost { + (Wei::zero(), fixed_cost) + } else { + let gas_to_wei = |price: U256| { + U256::from(gas_used) + .checked_mul(price) + .map(Wei::new) + .ok_or(GasPaymentError::EthAmountOverflow) + }; - let spent_amount = gas_to_wei(gas_result.effective_gas_price)?; - let reward_amount = gas_to_wei(gas_result.priority_fee_per_gas)?; + let spent_amount = gas_to_wei(gas_result.effective_gas_price)?; + let reward_amount = gas_to_wei(gas_result.priority_fee_per_gas)?; - let refund = gas_result - .prepaid_amount - .checked_sub(spent_amount) - .ok_or(GasPaymentError::EthAmountOverflow)?; + let refund = gas_result + .prepaid_amount + .checked_sub(spent_amount) + .ok_or(GasPaymentError::EthAmountOverflow)?; - add_balance(io, sender, refund)?; - add_balance(io, relayer, reward_amount)?; + (refund, reward_amount) + }; + + if !refund.is_zero() { + add_balance(io, sender, refund)?; + } + + if !relayer_reward.is_zero() { + add_balance(io, relayer, relayer_reward)?; + } Ok(()) } @@ -1225,14 +1317,17 @@ pub fn setup_receive_erc20_tokens_input( } #[must_use] -pub fn setup_deploy_erc20_input(current_account_id: &AccountId) -> Vec { +pub fn setup_deploy_erc20_input( + current_account_id: &AccountId, + erc20_metadata: Option, +) -> Vec { #[cfg(feature = "error_refund")] let erc20_contract = include_bytes!("../../etc/eth-contracts/res/EvmErc20V2.bin"); #[cfg(not(feature = "error_refund"))] let erc20_contract = include_bytes!("../../etc/eth-contracts/res/EvmErc20.bin"); let erc20_admin_address = current_address(current_account_id); - let erc20_metadata = Erc20Metadata::default(); + let erc20_metadata = erc20_metadata.unwrap_or_default(); let deploy_args = ethabi::encode(&[ ethabi::Token::String(erc20_metadata.name), @@ -1252,7 +1347,7 @@ pub fn deploy_erc20_token( handler: &mut P, ) -> Result { let current_account_id = env.current_account_id(); - let input = setup_deploy_erc20_input(¤t_account_id); + let input = setup_deploy_erc20_input(¤t_account_id, None); let mut engine: Engine<_, _> = Engine::new( aurora_engine_sdk::types::near_account_to_evm_address( env.predecessor_account_id().as_bytes(), @@ -1263,7 +1358,7 @@ pub fn deploy_erc20_token( ) .map_err(DeployErc20Error::State)?; - let address = match Engine::deploy_code_with_input(&mut engine, input, handler) { + let address = match engine.deploy_code_with_input(input, None, handler) { Ok(result) => match result.status { TransactionStatus::Succeed(ret) => { Address::new(H160(ret.as_slice().try_into().unwrap())) @@ -1281,6 +1376,51 @@ pub fn deploy_erc20_token( Ok(address) } +/// Used to mirror deployed ERC-20 contract on main contract to silo. +pub fn mirror_erc20_token( + args: MirrorErc20TokenArgs, + erc20_address: Address, + erc20_metadata: Erc20Metadata, + io: I, + env: &E, + handler: &mut P, +) -> Result { + let current_account_id = env.current_account_id(); + let input = setup_deploy_erc20_input(¤t_account_id, Some(erc20_metadata)); + let mut engine: Engine<_, _> = Engine::new( + aurora_engine_sdk::types::near_account_to_evm_address( + env.predecessor_account_id().as_bytes(), + ), + current_account_id, + io, + env, + ) + .map_err(DeployErc20Error::State)?; + + let address = match engine.deploy_code_with_input(input, Some(erc20_address), handler) { + Ok(result) => match result.status { + TransactionStatus::Succeed(ret) => { + Address::new(H160(ret.as_slice().try_into().unwrap())) + } + other => return Err(DeployErc20Error::Failed(other)), + }, + Err(e) => return Err(DeployErc20Error::Engine(e)), + }; + + assert_eq!(address, erc20_address); + + sdk::log!( + "ERC-20 on: {} at address: {} has been mirrored", + args.contract_id.as_ref(), + address.encode() + ); + engine + .register_token(address, args.nep141) + .map_err(DeployErc20Error::Register)?; + + Ok(address) +} + pub fn set_code(io: &mut I, address: &Address, code: &[u8]) { io.write_storage(&address_to_key(KeyPrefix::Code, address), code); } @@ -1591,6 +1731,30 @@ unsafe fn schedule_promise_callback( handler.promise_attach_callback(base_id, promise) } +fn assert_access( + io: &I, + env: &E, + fixed_gas_cost: &Option, + transaction: &NormalizedEthTransaction, +) -> Result<(), EngineError> { + if fixed_gas_cost.is_some() { + let allowed = if transaction.to.is_some() { + silo::is_allow_submit(io, &env.predecessor_account_id(), &transaction.address) + } else { + silo::is_allow_deploy(io, &env.predecessor_account_id(), &transaction.address) + }; + + if !allowed { + return Err(EngineError { + kind: EngineErrorKind::NotAllowed, + gas_used: 0, + }); + } + } + + Ok(()) +} + impl<'env, I: IO + Copy, E: Env, M: ModExpAlgorithm> Backend for Engine<'env, I, E, M> { /// Returns the "effective" gas price (as defined by EIP-1559) fn gas_price(&self) -> U256 { @@ -1844,10 +2008,12 @@ impl<'env, J: IO + Copy, E: Env, M: ModExpAlgorithm> ApplyBackend for Engine<'en match accounting.net() { // Net loss is possible if `SELFDESTRUCT(self)` calls are made. accounting::Net::Lost(amount) => { + let _ = amount; sdk::log!("Burn {} ETH due to SELFDESTRUCT", amount); // Apply changes for eth-connector. We ignore the `StorageReadError` intentionally since // if we cannot read the storage then there is nothing to remove. - EthConnectorContract::init_instance(self.io) + #[cfg(not(feature = "ext-connector"))] + connector::EthConnectorContract::init(self.io) .map(|mut connector| { // The `unwrap` is safe here because (a) if the connector // is implemented correctly then the total supply will never underflow and (b) we are passing @@ -1859,7 +2025,7 @@ impl<'env, J: IO + Copy, E: Env, M: ModExpAlgorithm> ApplyBackend for Engine<'en accounting::Net::Zero => (), accounting::Net::Gained(_) => { // It should be impossible to gain ETH using normal EVM operations in production. - // In tests we have convenience functions that can poof addresses with ETH out of nowhere. + // In tests, we have convenience functions that can poof addresses with ETH out of nowhere. #[cfg(all(not(feature = "integration-test"), feature = "contract"))] { panic!("ERR_INVALID_ETH_SUPPLY_INCREASE"); @@ -1867,7 +2033,7 @@ impl<'env, J: IO + Copy, E: Env, M: ModExpAlgorithm> ApplyBackend for Engine<'en } } // These variable are only used if logging feature is enabled. - // In production logging is always enabled so we can ignore the warnings. + // In production logging is always enabled, so we can ignore the warnings. #[allow(unused_variables)] let total_bytes = 32 * writes_counter + code_bytes_written; #[allow(unused_assignments)] @@ -1933,7 +2099,9 @@ mod tests { let input = vec![]; let mut handler = Noop; - let actual_result = engine.deploy_code_with_input(input, &mut handler).unwrap(); + let actual_result = engine + .deploy_code_with_input(input, None, &mut handler) + .unwrap(); let nonce = U256::zero(); let expected_address = create_legacy_address(&origin, &nonce).as_bytes().to_vec(); @@ -1945,6 +2113,32 @@ mod tests { assert_eq!(expected_result, actual_result); } + #[test] + fn test_deploying_code_with_address_succeeds() { + let origin = Address::zero(); + let current_account_id = AccountId::default(); + let env = Fixed::default(); + let storage = RefCell::new(Storage::default()); + let io = StoragePointer(&storage); + let mut engine: Engine<_, _> = + Engine::new_with_state(EngineState::default(), origin, current_account_id, io, &env); + + let input = vec![]; + let mut handler = Noop; + + let address = Address::from_array([1; 20]); + let actual_result = engine + .deploy_code_with_input(input, Some(address), &mut handler) + .unwrap(); + + let expected_status = TransactionStatus::Succeed(address.as_bytes().to_vec()); + let expected_gas_used = 53000; + let expected_logs = Vec::new(); + let expected_result = SubmitResult::new(expected_status, expected_gas_used, expected_logs); + + assert_eq!(expected_result, actual_result); + } + #[test] fn test_call_to_empty_contract_returns_empty_data() { let origin = Address::zero(); @@ -2157,7 +2351,11 @@ mod tests { let mut handler = Noop; let args = DeployErc20TokenArgs { nep141 }; let erc20_address = deploy_erc20_token(args, io, &env, &mut handler).unwrap(); - let metadata = engine.get_erc20_metadata(erc20_address).unwrap(); + let metadata = engine + .get_erc20_metadata(&Erc20Identifier::Erc20 { + address: erc20_address, + }) + .unwrap(); assert_eq!(metadata, Erc20Metadata::default()); } @@ -2185,7 +2383,9 @@ mod tests { data: vec![], access_list: vec![], }; - let actual_result = engine.charge_gas(&origin, &transaction, None).unwrap(); + let actual_result = engine + .charge_gas(&origin, &transaction, None, None) + .unwrap(); let expected_result = GasPaymentResult { prepaid_amount: Wei::zero(), @@ -2196,6 +2396,54 @@ mod tests { assert_eq!(expected_result, actual_result); } + #[test] + fn test_gas_charge_for_non_empty_transaction() { + let origin = Address::zero(); + let current_account_id = AccountId::default(); + let env = Fixed::default(); + let storage = RefCell::new(Storage::default()); + let mut io = StoragePointer(&storage); + add_balance(&mut io, &origin, Wei::new_u64(2_000_000)).unwrap(); + let mut engine: Engine<_, _> = + Engine::new_with_state(EngineState::default(), origin, current_account_id, io, &env); + + let transaction = NormalizedEthTransaction { + address: Address::default(), + chain_id: None, + nonce: U256::default(), + gas_limit: 67_000.into(), + max_priority_fee_per_gas: 20.into(), + max_fee_per_gas: 10.into(), + to: None, + value: Wei::default(), + data: vec![], + access_list: vec![], + }; + let actual_result = engine + .charge_gas(&origin, &transaction, None, None) + .unwrap(); + + let expected_result = GasPaymentResult { + prepaid_amount: Wei::new_u64(67_000 * 10), + effective_gas_price: 10.into(), + priority_fee_per_gas: 10.into(), + }; + + assert_eq!(expected_result, actual_result); + + let actual_result = engine + .charge_gas(&origin, &transaction, Some(5.into()), None) + .unwrap(); + + let expected_result = GasPaymentResult { + prepaid_amount: Wei::new_u64(67_000 * 5), + effective_gas_price: 5.into(), + priority_fee_per_gas: 5.into(), + }; + + assert_eq!(expected_result, actual_result); + } + #[test] fn test_scheduling_promise_creates_it() { use aurora_engine_test_doubles::promise::PromiseArgs; @@ -2346,7 +2594,7 @@ mod tests { priority_fee_per_gas: U256::zero(), }; - refund_unused_gas(&mut io, &origin, 1000, &gas_result, &relayer).unwrap(); + refund_unused_gas(&mut io, &origin, 1000, &gas_result, &relayer, None).unwrap(); } #[test] @@ -2359,16 +2607,19 @@ mod tests { let relayer = make_address(1, 1); let gas_result = GasPaymentResult { prepaid_amount: Wei::new_u64(8000), - effective_gas_price: Wei::new_u64(1).raw(), - priority_fee_per_gas: U256::zero(), + effective_gas_price: 1.into(), + priority_fee_per_gas: 2.into(), }; let gas_used = 4000; - refund_unused_gas(&mut io, &origin, gas_used, &gas_result, &relayer).unwrap(); + refund_unused_gas(&mut io, &origin, gas_used, &gas_result, &relayer, None).unwrap(); let actual_refund = get_balance(&io, &origin); let expected_refund = Wei::new_u64(gas_used); + assert_eq!(expected_refund, actual_refund); + let actual_refund = get_balance(&io, &relayer); + let expected_refund = Wei::new_u64(gas_used * 2); assert_eq!(expected_refund, actual_refund); } diff --git a/engine/src/errors.rs b/engine/src/errors.rs index 7c6823da7..0634a3f90 100644 --- a/engine/src/errors.rs +++ b/engine/src/errors.rs @@ -22,6 +22,7 @@ pub const ERR_VERIFY_PROOF: &[u8; 16] = b"ERR_VERIFY_PROOF"; pub const ERR_INVALID_UPGRADE: &[u8; 19] = b"ERR_INVALID_UPGRADE"; pub const ERR_NO_UPGRADE: &[u8; 14] = b"ERR_NO_UPGRADE"; pub const ERR_NOT_ALLOWED: &[u8; 15] = b"ERR_NOT_ALLOWED"; +pub const ERR_NOT_OWNER: &[u8; 13] = b"ERR_NOT_OWNER"; pub const ERR_PAUSED: &[u8; 10] = b"ERR_PAUSED"; pub const ERR_RUNNING: &[u8; 11] = b"ERR_RUNNING"; @@ -67,7 +68,9 @@ pub const ERR_WRONG_EVENT_ADDRESS: &[u8; 23] = b"ERR_WRONG_EVENT_ADDRESS"; pub const ERR_CONTRACT_INITIALIZED: &[u8; 24] = b"ERR_CONTRACT_INITIALIZED"; pub const ERR_RLP_FAILED: &[u8; 14] = b"ERR_RLP_FAILED"; +pub const ERR_PARSE_ARGS: &[u8; 14] = b"ERR_PARSE_ARGS"; pub const ERR_PARSE_DEPOSIT_EVENT: &[u8; 23] = b"ERR_PARSE_DEPOSIT_EVENT"; +pub const ERR_PARSE_WITHDRAW_EVENT: &[u8; 24] = b"ERR_PARSE_WITHDRAW_EVENT"; pub const ERR_INVALID_EVENT_MESSAGE_FORMAT: &[u8; 32] = b"ERR_INVALID_EVENT_MESSAGE_FORMAT"; pub const ERR_INVALID_SENDER: &[u8; 18] = b"ERR_INVALID_SENDER"; pub const ERR_INVALID_AMOUNT: &[u8; 18] = b"ERR_INVALID_AMOUNT"; @@ -98,3 +101,9 @@ pub const ERR_DECODING_TOKEN: &[u8] = b"ERR_DECODING_TOKEN"; pub const ERR_GETTING_TOKEN: &[u8] = b"ERR_GETTING_TOKEN"; pub const ERR_WRONG_TOKEN_TYPE: &[u8] = b"ERR_WRONG_TOKEN_TYPE"; pub const ERR_TOKEN_NO_VALUE: &[u8] = b"ERR_TOKEN_NO_VALUE"; +pub const ERR_NOT_ENOUGH_BALANCE_FOR_FEE: &[u8] = b"ERR_NOT_ENOUGH_BALANCE_FOR_FEE"; +pub const ERR_GETTING_ERC20_FROM_NEP141: &[u8] = b"ERR_GETTING_ERC20_FROM_NEP141"; +pub const ERR_ALLOWED_IN_SILO_MODE_ONLY: &[u8] = b"ERR_ALLOWED_IN_SILO_MODE_ONLY"; +pub const ERR_INVALID_NEP141_ACCOUNT_ID: &[u8] = b"ERR_INVALID_NEP141_ACCOUNT_ID"; +pub const ERR_NEP141_NOT_FOUND: &[u8] = b"ERR_NEP141_NOT_FOUND"; +pub const ERR_NEP141_TOKEN_ALREADY_REGISTERED: &[u8] = b"ERR_NEP141_TOKEN_ALREADY_REGISTERED"; diff --git a/engine/src/lib.rs b/engine/src/lib.rs index 13aa7702f..6d7316fa4 100644 --- a/engine/src/lib.rs +++ b/engine/src/lib.rs @@ -28,14 +28,10 @@ pub mod proof { pub use aurora_engine_types::parameters::connector::Proof; } pub mod accounting; -pub mod admin_controlled; #[cfg_attr(feature = "contract", allow(dead_code))] -pub mod connector; pub mod contract_methods; -pub mod deposit_event; pub mod engine; pub mod errors; -pub mod fungible_token; pub mod hashchain; pub mod pausables; mod prelude; @@ -79,29 +75,24 @@ pub unsafe fn on_alloc_error(_: core::alloc::Layout) -> ! { #[cfg(feature = "contract")] mod contract { - use crate::connector::{self, EthConnectorContract}; use crate::engine::{self, Engine}; - use crate::parameters::{ - self, FungibleTokenMetadata, GetErc20FromNep141CallArgs, GetStorageAtArgs, - IsUsedProofCallArgs, ViewCallArgs, - }; #[cfg(feature = "evm_bully")] use crate::parameters::{BeginBlockArgs, BeginChainArgs}; + use crate::parameters::{GetErc20FromNep141CallArgs, GetStorageAtArgs, ViewCallArgs}; use crate::prelude::sdk::types::{SdkExpect, SdkUnwrap}; use crate::prelude::storage::{bytes_to_key, KeyPrefix}; - use crate::prelude::{sdk, u256_to_arr, Address, ERR_FAILED_PARSE, H256}; + use crate::prelude::{sdk, u256_to_arr, Address, ToString, Vec, H256}; use crate::{ - contract_methods::{self, ContractError}, + contract_methods::{self, silo, ContractError}, errors, state, }; use aurora_engine_sdk::env::Env; use aurora_engine_sdk::io::{StorageIntermediate, IO}; use aurora_engine_sdk::near_runtime::{Runtime, ViewEnv}; use aurora_engine_types::borsh::BorshSerialize; - use aurora_engine_types::parameters::engine::errors::ParseTypeFromJsonError; - - #[cfg(feature = "integration-test")] - use crate::prelude::NearGas; + use aurora_engine_types::parameters::silo::{ + FixedGasCostArgs, SiloParamsArgs, WhitelistArgs, WhitelistKindArgs, WhitelistStatusArgs, + }; const CODE_KEY: &[u8; 4] = b"CODE"; const CODE_STAGE_KEY: &[u8; 10] = b"CODE_STAGE"; @@ -207,7 +198,7 @@ mod contract { #[no_mangle] pub extern "C" fn deploy_upgrade() { // This function is intentionally not implemented in `contract_methods` - // because it only make sense in the context of the Near runtime. + // because it only makes sense in the context of the NEAR runtime. let mut io = Runtime; let state = state::get_state(&io).sdk_unwrap(); require_running(&state); @@ -387,7 +378,7 @@ mod contract { /// Create and/or fund an XCC sub-account directly (as opposed to having one be automatically /// created via the XCC precompile in the EVM). The purpose of this method is to enable - /// XCC on engine instances where wrapped NEAR (WNEAR) is not bridged. + /// XCC on engine instances where wrapped NEAR (`wNEAR`) is not bridged. #[no_mangle] pub extern "C" fn fund_xcc_sub_account() { let io = Runtime; @@ -398,51 +389,24 @@ mod contract { .sdk_unwrap(); } - /// Allow receiving NEP141 tokens to the EVM contract. - /// - /// This function returns the amount of tokens to return to the sender. - /// Either all tokens are transferred and tokens are returned - /// in case of an error, or no token is returned if the transaction was successful. + /// Mirror existing ERC-20 token on the main Aurora contract. + /// Notice: It works if the SILO mode is on. #[no_mangle] - pub extern "C" fn ft_on_transfer() { + pub extern "C" fn mirror_erc20_token() { let io = Runtime; - let env = Runtime; let mut handler = Runtime; - contract_methods::connector::ft_on_transfer(io, &env, &mut handler) + contract_methods::connector::mirror_erc20_token(io, &mut handler) .map_err(ContractError::msg) .sdk_unwrap(); } - /// Deploy ERC20 token mapped to a NEP141 + /// Callback used by the `mirror_erc20_token` function. #[no_mangle] - pub extern "C" fn deploy_erc20_token() { + pub extern "C" fn mirror_erc20_token_callback() { let io = Runtime; let env = Runtime; let mut handler = Runtime; - contract_methods::connector::deploy_erc20_token(io, &env, &mut handler) - .map_err(ContractError::msg) - .sdk_unwrap(); - } - - /// Set metadata of ERC-20 contract. - #[no_mangle] - pub extern "C" fn set_erc20_metadata() { - let io = Runtime; - let env = Runtime; - let mut handler = Runtime; - contract_methods::connector::set_erc20_metadata(io, &env, &mut handler) - .map_err(ContractError::msg) - .sdk_unwrap(); - } - - /// Callback invoked by exit to NEAR precompile to handle potential - /// errors in the exit call or to perform the near tokens transfer. - #[no_mangle] - pub extern "C" fn exit_to_near_precompile_callback() { - let io = Runtime; - let env = Runtime; - let mut handler = Runtime; - contract_methods::connector::exit_to_near_precompile_callback(io, &env, &mut handler) + contract_methods::connector::mirror_erc20_token_callback(io, &env, &mut handler) .map_err(ContractError::msg) .sdk_unwrap(); } @@ -489,8 +453,19 @@ mod contract { .sdk_unwrap(); } + /// Attach a full access key. + #[no_mangle] + pub extern "C" fn attach_full_access_key() { + let io = Runtime; + let env = Runtime; + let mut handler = Runtime; + contract_methods::admin::attach_full_access_key(io, &env, &mut handler) + .map_err(ContractError::msg) + .sdk_unwrap(); + } + /// - /// NONMUTATIVE METHODS + /// READ-ONLY METHODS /// #[no_mangle] pub extern "C" fn view() { @@ -512,8 +487,7 @@ mod contract { let chain_id = state::get_state(&io) .map(|state| state.chain_id) .sdk_unwrap(); - let block_hash = - crate::engine::compute_block_hash(chain_id, block_height, account_id.as_bytes()); + let block_hash = engine::compute_block_hash(chain_id, block_height, account_id.as_bytes()); io.return_output(block_hash.as_bytes()); } @@ -608,6 +582,9 @@ mod contract { // TODO: https://github.com/aurora-is-near/aurora-engine/issues/2 } + /// + /// ETH-CONNECTOR + /// #[no_mangle] pub extern "C" fn new_eth_connector() { let io = Runtime; @@ -630,18 +607,9 @@ mod contract { pub extern "C" fn withdraw() { let io = Runtime; let env = Runtime; - let result_bytes = contract_methods::connector::withdraw(io, &env) + contract_methods::connector::withdraw(io, &env) .map_err(ContractError::msg) .sdk_unwrap(); - // We intentionally do not go through the `io` struct here because we must bypass - // the check that prevents output that is accepted by the eth_custodian - #[allow(clippy::as_conversions)] - unsafe { - exports::value_return( - u64::try_from(result_bytes.len()).sdk_expect(errors::ERR_VALUE_CONVERSION), - result_bytes.as_ptr() as u64, - ); - } } #[no_mangle] @@ -649,7 +617,7 @@ mod contract { let io = Runtime; let env = Runtime; let mut handler = Runtime; - let _ = contract_methods::connector::deposit(io, &env, &mut handler) + contract_methods::connector::deposit(io, &env, &mut handler) .map_err(ContractError::msg) .sdk_unwrap(); } @@ -666,58 +634,49 @@ mod contract { #[no_mangle] pub extern "C" fn is_used_proof() { - let mut io = Runtime; - let args: IsUsedProofCallArgs = io.read_input_borsh().sdk_unwrap(); - - let is_used_proof = EthConnectorContract::init_instance(io) - .sdk_unwrap() - .is_used_proof(&args.proof); - let res = is_used_proof.try_to_vec().unwrap(); - io.return_output(&res[..]); + let io = Runtime; + contract_methods::connector::is_used_proof(io) + .map_err(ContractError::msg) + .sdk_unwrap(); } #[no_mangle] pub extern "C" fn ft_total_supply() { let io = Runtime; - EthConnectorContract::init_instance(io) - .sdk_unwrap() - .ft_total_eth_supply_on_near(); + contract_methods::connector::ft_total_eth_supply_on_near(io) + .map_err(ContractError::msg) + .sdk_unwrap(); } #[no_mangle] pub extern "C" fn ft_total_eth_supply_on_near() { let io = Runtime; - EthConnectorContract::init_instance(io) - .sdk_unwrap() - .ft_total_eth_supply_on_near(); + contract_methods::connector::ft_total_eth_supply_on_near(io) + .map_err(ContractError::msg) + .sdk_unwrap(); } #[no_mangle] pub extern "C" fn ft_total_eth_supply_on_aurora() { let io = Runtime; - EthConnectorContract::init_instance(io) - .sdk_unwrap() - .ft_total_eth_supply_on_aurora(); + contract_methods::connector::ft_total_eth_supply_on_aurora(io) + .map_err(ContractError::msg) + .sdk_unwrap(); } #[no_mangle] pub extern "C" fn ft_balance_of() { let io = Runtime; - let args: parameters::BalanceOfCallArgs = serde_json::from_slice(&io.read_input().to_vec()) - .map_err(Into::::into) + contract_methods::connector::ft_balance_of(io) + .map_err(ContractError::msg) .sdk_unwrap(); - EthConnectorContract::init_instance(io) - .sdk_unwrap() - .ft_balance_of(&args); } #[no_mangle] pub extern "C" fn ft_balance_of_eth() { let io = Runtime; - let args: parameters::BalanceOfEthCallArgs = io.read_input().to_value().sdk_unwrap(); - EthConnectorContract::init_instance(io) - .sdk_unwrap() - .ft_balance_of_eth_on_aurora(&args) + contract_methods::connector::ft_balance_of_eth(io) + .map_err(ContractError::msg) .sdk_unwrap(); } @@ -745,7 +704,56 @@ mod contract { let io = Runtime; let env = Runtime; let mut handler = Runtime; - let _ = contract_methods::connector::ft_transfer_call(io, &env, &mut handler) + contract_methods::connector::ft_transfer_call(io, &env, &mut handler) + .map_err(ContractError::msg) + .sdk_unwrap(); + } + + /// Allow receiving NEP141 tokens to the EVM contract. + /// + /// This function returns the amount of tokens to return to the sender. + /// Either all tokens are transferred and tokens are returned + /// in case of an error, or no token is returned if the transaction was successful. + #[no_mangle] + pub extern "C" fn ft_on_transfer() { + let io = Runtime; + let env = Runtime; + let mut handler = Runtime; + contract_methods::connector::ft_on_transfer(io, &env, &mut handler) + .map_err(ContractError::msg) + .sdk_unwrap(); + } + + /// Deploy ERC20 token mapped to a NEP141 + #[no_mangle] + pub extern "C" fn deploy_erc20_token() { + let io = Runtime; + let env = Runtime; + let mut handler = Runtime; + contract_methods::connector::deploy_erc20_token(io, &env, &mut handler) + .map_err(ContractError::msg) + .sdk_unwrap(); + } + + /// Set metadata of ERC-20 contract. + #[no_mangle] + pub extern "C" fn set_erc20_metadata() { + let io = Runtime; + let env = Runtime; + let mut handler = Runtime; + contract_methods::connector::set_erc20_metadata(io, &env, &mut handler) + .map_err(ContractError::msg) + .sdk_unwrap(); + } + + /// Callback invoked by exit to NEAR precompile to handle potential + /// errors in the exit call or to perform the near tokens transfer. + #[no_mangle] + pub extern "C" fn exit_to_near_precompile_callback() { + let io = Runtime; + let env = Runtime; + let mut handler = Runtime; + contract_methods::connector::exit_to_near_precompile_callback(io, &env, &mut handler) .map_err(ContractError::msg) .sdk_unwrap(); } @@ -782,23 +790,34 @@ mod contract { #[no_mangle] pub extern "C" fn storage_balance_of() { let io = Runtime; - let args: parameters::StorageBalanceOfCallArgs = - serde_json::from_slice(&io.read_input().to_vec()) - .map_err(Into::::into) - .sdk_unwrap(); - EthConnectorContract::init_instance(io) - .sdk_unwrap() - .storage_balance_of(&args); + contract_methods::connector::storage_balance_of(io) + .map_err(ContractError::msg) + .sdk_unwrap(); + } + + #[no_mangle] + pub extern "C" fn get_eth_connector_contract_account() { + let io = Runtime; + contract_methods::connector::get_eth_connector_contract_account(io) + .map_err(ContractError::msg) + .sdk_unwrap(); + } + + #[no_mangle] + pub extern "C" fn set_eth_connector_contract_account() { + let io = Runtime; + let env = Runtime; + contract_methods::connector::set_eth_connector_contract_account(io, &env) + .map_err(ContractError::msg) + .sdk_unwrap(); } #[no_mangle] pub extern "C" fn get_paused_flags() { - let mut io = Runtime; - let paused_flags = EthConnectorContract::init_instance(io) - .sdk_unwrap() - .get_paused_flags(); - let data = paused_flags.try_to_vec().expect(ERR_FAILED_PARSE); - io.return_output(&data[..]); + let io = Runtime; + contract_methods::connector::get_paused_flags(io) + .map_err(ContractError::msg) + .sdk_unwrap(); } #[no_mangle] @@ -811,11 +830,12 @@ mod contract { } #[no_mangle] + #[cfg(not(feature = "ext-connector"))] pub extern "C" fn get_accounts_counter() { let io = Runtime; - EthConnectorContract::init_instance(io) - .sdk_unwrap() - .get_accounts_counter(); + contract_methods::connector::get_accounts_counter(io) + .map_err(ContractError::msg) + .sdk_unwrap(); } #[no_mangle] @@ -844,10 +864,10 @@ mod contract { #[no_mangle] pub extern "C" fn ft_metadata() { - let mut io = Runtime; - let metadata: FungibleTokenMetadata = connector::get_metadata(&io).unwrap_or_default(); - let bytes = serde_json::to_vec(&metadata).unwrap_or_default(); - io.return_output(&bytes); + let io = Runtime; + contract_methods::connector::ft_metadata(io) + .map_err(ContractError::msg) + .sdk_unwrap(); } #[cfg(feature = "integration-test")] @@ -856,20 +876,20 @@ mod contract { sdk::log!("Call from verify_log_entry"); let mut io = Runtime; let data = true.try_to_vec().unwrap(); - io.return_output(&data[..]); + io.return_output(&data); } /// Function used to create accounts for tests #[cfg(feature = "integration-test")] #[no_mangle] pub extern "C" fn mint_account() { - use crate::connector::ZERO_ATTACHED_BALANCE; use crate::prelude::{NEP141Wei, U256}; use evm::backend::ApplyBackend; - const GAS_FOR_VERIFY: NearGas = NearGas::new(20_000_000_000_000); - const GAS_FOR_FINISH: NearGas = NearGas::new(50_000_000_000_000); + #[cfg(not(feature = "ext-connector"))] let mut io = Runtime; + #[cfg(feature = "ext-connector")] + let io = Runtime; let args: ([u8; 20], u64, u64) = io.read_input_borsh().sdk_expect(errors::ERR_ARGS); let address = Address::from_array(args.0); let nonce = U256::from(args.1); @@ -889,41 +909,144 @@ mod contract { }; engine.apply(core::iter::once(state_change), core::iter::empty(), false); - // Call "finish_deposit" to mint the corresponding - // nETH NEP-141 tokens as well - let aurora_account_id = io.current_account_id(); - let args = crate::parameters::FinishDepositCallArgs { - new_owner_id: aurora_account_id.clone(), - amount: balance, - proof_key: crate::prelude::String::new(), - relayer_id: aurora_account_id.clone(), - fee: 0.into(), - msg: None, - }; - let verify_call = aurora_engine_types::parameters::PromiseCreateArgs { - target_account_id: aurora_account_id.clone(), - method: crate::prelude::String::from("verify_log_entry"), - args: crate::prelude::Vec::new(), - attached_balance: ZERO_ATTACHED_BALANCE, - attached_gas: GAS_FOR_VERIFY, - }; - let finish_call = aurora_engine_types::parameters::PromiseCreateArgs { - target_account_id: aurora_account_id, - method: crate::prelude::String::from("finish_deposit"), - args: args.try_to_vec().unwrap(), - attached_balance: ZERO_ATTACHED_BALANCE, - attached_gas: GAS_FOR_FINISH, - }; - // Safety: this call is safe because it is only used in integration tests. - unsafe { + #[cfg(not(feature = "ext-connector"))] + { + use crate::contract_methods::connector::ZERO_ATTACHED_BALANCE; + use crate::prelude::NearGas; use aurora_engine_sdk::promise::PromiseHandler; - io.promise_create_with_callback( - &aurora_engine_types::parameters::PromiseWithCallbackArgs { - base: verify_call, - callback: finish_call, - }, - ) + + const GAS_FOR_VERIFY: NearGas = NearGas::new(20_000_000_000_000); + const GAS_FOR_FINISH: NearGas = NearGas::new(50_000_000_000_000); + // Call "finish_deposit" to mint the corresponding + // nETH NEP-141 tokens as well + let aurora_account_id = io.current_account_id(); + let args = crate::parameters::FinishDepositCallArgs { + new_owner_id: aurora_account_id.clone(), + amount: balance, + proof_key: crate::prelude::String::new(), + relayer_id: aurora_account_id.clone(), + fee: 0.into(), + msg: None, + }; + let verify_call = aurora_engine_types::parameters::PromiseCreateArgs { + target_account_id: aurora_account_id.clone(), + method: crate::prelude::String::from("verify_log_entry"), + args: crate::prelude::Vec::new(), + attached_balance: ZERO_ATTACHED_BALANCE, + attached_gas: GAS_FOR_VERIFY, + }; + let finish_call = aurora_engine_types::parameters::PromiseCreateArgs { + target_account_id: aurora_account_id, + method: crate::prelude::String::from("finish_deposit"), + args: args.try_to_vec().unwrap(), + attached_balance: ZERO_ATTACHED_BALANCE, + attached_gas: GAS_FOR_FINISH, + }; + // Safety: this call is safe because it is only used in integration tests. + unsafe { + io.promise_create_with_callback( + &aurora_engine_types::parameters::PromiseWithCallbackArgs { + base: verify_call, + callback: finish_call, + }, + ) + }; + } + } + + /// + /// Silo + /// + #[no_mangle] + pub extern "C" fn get_fixed_gas_cost() { + let mut io = Runtime; + let cost = FixedGasCostArgs { + cost: silo::get_fixed_gas_cost(&io), }; + + io.return_output(&cost.try_to_vec().map_err(|e| e.to_string()).sdk_unwrap()); + } + + #[no_mangle] + pub extern "C" fn set_fixed_gas_cost() { + let mut io = Runtime; + require_running(&state::get_state(&io).sdk_unwrap()); + silo::assert_admin(&io).sdk_unwrap(); + + let args: FixedGasCostArgs = io.read_input_borsh().sdk_unwrap(); + args.cost.sdk_expect("FIXED_GAS_COST_IS_NONE"); // Use `set_silo_params` to disable the silo mode. + silo::get_silo_params(&io).sdk_expect("SILO_MODE_IS_OFF"); // Use `set_silo_params` to enable the silo mode. + silo::set_fixed_gas_cost(&mut io, args.cost); + } + + #[no_mangle] + pub extern "C" fn get_silo_params() { + let mut io = Runtime; + let params = silo::get_silo_params(&io); + + io.return_output(¶ms.try_to_vec().map_err(|e| e.to_string()).sdk_unwrap()); + } + + #[no_mangle] + pub extern "C" fn set_silo_params() { + let mut io = Runtime; + require_running(&state::get_state(&io).sdk_unwrap()); + silo::assert_admin(&io).sdk_unwrap(); + + let args: Option = io.read_input_borsh().sdk_unwrap(); + silo::set_silo_params(&mut io, args); + } + + #[no_mangle] + pub extern "C" fn set_whitelist_status() { + let io = Runtime; + require_running(&state::get_state(&io).sdk_unwrap()); + silo::assert_admin(&io).sdk_unwrap(); + + let args: WhitelistStatusArgs = io.read_input_borsh().sdk_unwrap(); + silo::set_whitelist_status(&io, &args); + } + + #[no_mangle] + pub extern "C" fn get_whitelist_status() { + let mut io = Runtime; + let args: WhitelistKindArgs = io.read_input_borsh().sdk_unwrap(); + let status = silo::get_whitelist_status(&io, &args) + .try_to_vec() + .map_err(|e| e.to_string()) + .sdk_unwrap(); + + io.return_output(&status); + } + + #[no_mangle] + pub extern "C" fn add_entry_to_whitelist() { + let io = Runtime; + require_running(&state::get_state(&io).sdk_unwrap()); + silo::assert_admin(&io).sdk_unwrap(); + + let args: WhitelistArgs = io.read_input_borsh().sdk_unwrap(); + silo::add_entry_to_whitelist(&io, &args); + } + + #[no_mangle] + pub extern "C" fn add_entry_to_whitelist_batch() { + let io = Runtime; + require_running(&state::get_state(&io).sdk_unwrap()); + silo::assert_admin(&io).sdk_unwrap(); + + let args: Vec = io.read_input_borsh().sdk_unwrap(); + silo::add_entry_to_whitelist_batch(&io, args); + } + + #[no_mangle] + pub extern "C" fn remove_entry_from_whitelist() { + let io = Runtime; + require_running(&state::get_state(&io).sdk_unwrap()); + silo::assert_admin(&io).sdk_unwrap(); + + let args: WhitelistArgs = io.read_input_borsh().sdk_unwrap(); + silo::remove_entry_from_whitelist(&io, &args); } // TODO: rust-2023-08-24#[allow(clippy::empty_line_after_doc_comments)] @@ -948,7 +1071,8 @@ mod contract { } } - mod exports { + #[cfg(not(feature = "ext-connector"))] + pub mod exports { extern "C" { pub(crate) fn value_return(value_len: u64, value_ptr: u64); } diff --git a/engine/src/state.rs b/engine/src/state.rs index d066f6d0a..20c8e0e70 100644 --- a/engine/src/state.rs +++ b/engine/src/state.rs @@ -1,8 +1,9 @@ -use crate::parameters::{LegacyNewCallArgs, NewCallArgs, NewCallArgsV2}; +use crate::parameters::{ + LegacyNewCallArgs, NewCallArgs, NewCallArgsV2, NewCallArgsV3, NewCallArgsV4, +}; use aurora_engine_sdk::io::{StorageIntermediate, IO}; use aurora_engine_types::account_id::AccountId; use aurora_engine_types::borsh::{self, BorshDeserialize, BorshSerialize}; -use aurora_engine_types::parameters::engine::NewCallArgsV3; use aurora_engine_types::storage::{bytes_to_key, KeyPrefix}; use aurora_engine_types::{Cow, Vec}; @@ -177,18 +178,31 @@ impl From for EngineState { } } +impl From for EngineState { + fn from(args: NewCallArgsV4) -> Self { + Self { + chain_id: args.chain_id, + owner_id: args.owner_id, + upgrade_delay_blocks: args.upgrade_delay_blocks, + is_paused: false, + key_manager: Some(args.key_manager), + } + } +} + impl From for EngineState { fn from(args: NewCallArgs) -> Self { match args { NewCallArgs::V1(args) => args.into(), NewCallArgs::V2(args) => args.into(), NewCallArgs::V3(args) => args.into(), + NewCallArgs::V4(args) => args.into(), } } } /// Gets the state from storage, if it exists otherwise it will error. -pub fn get_state(io: &I) -> Result { +pub fn get_state(io: &I) -> Result { io.read_storage(&bytes_to_key(KeyPrefix::Config, STATE_KEY)) .map_or_else( || Err(EngineStateError::NotFound), diff --git a/etc/eth-contracts/yarn.lock b/etc/eth-contracts/yarn.lock index 026ddc7f3..60b319d1f 100644 --- a/etc/eth-contracts/yarn.lock +++ b/etc/eth-contracts/yarn.lock @@ -5707,9 +5707,9 @@ get-caller-file@^2.0.1, get-caller-file@^2.0.5: integrity sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg== get-func-name@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/get-func-name/-/get-func-name-2.0.0.tgz#ead774abee72e20409433a066366023dd6887a41" - integrity sha1-6td0q+5y4gQJQzoGY2YCPdaIekE= + version "2.0.2" + resolved "https://registry.yarnpkg.com/get-func-name/-/get-func-name-2.0.2.tgz#0d7cf20cd13fda808669ffa88f4ffc7a3943fc41" + integrity sha512-8vXOvuE167CtIc3OyItco7N/dpRtBbYOsPsXCz7X/PMnlGjYjSGuZJgM1Y7mmew7BKf9BqvLX2tnOVy1BBUsxQ== get-intrinsic@^1.0.2, get-intrinsic@^1.1.1: version "1.1.1"