diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 6d752c6b8..5b1048175 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -8,18 +8,26 @@ jobs: steps: - name: Clone the repository uses: actions/checkout@v2 + - name: Cache Cargo artifacts + uses: actions/cache@v2 + with: + path: | + ~/.cargo/registry + ~/.cargo/git + target + key: ${{ runner.os }}-cargo-${{ hashFiles('**/Cargo.lock') }} - name: Install the toolchain uses: actions-rs/toolchain@v1 with: profile: minimal toolchain: nightly-2021-03-25 override: true - - run: make release - - run: ls -lH release.wasm + - run: make test-build - name: Run cargo test uses: actions-rs/cargo@v1 with: command: test - args: --verbose + args: --locked --verbose env: CARGO_TERM_COLOR: always + CARGO_INCREMENTAL: 0 diff --git a/.gitignore b/.gitignore index 2f1946311..bdbdfad27 100644 --- a/.gitignore +++ b/.gitignore @@ -13,6 +13,7 @@ # Rust artifacts /*.wasm /target/ +etc/state-migration-test/target/ node_modules/ artifacts/ diff --git a/Cargo.lock b/Cargo.lock index b35416275..f15cfc6dd 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -121,6 +121,7 @@ dependencies = [ "blake2 0.9.1 (git+https://github.com/near/near-blake2.git)", "borsh", "bstr", + "byte-slice-cast", "criterion", "ethabi", "evm", diff --git a/Cargo.toml b/Cargo.toml index 334b93eef..29fcf698e 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -54,6 +54,7 @@ wee_alloc = { version = "0.4.5", default-features = false } lunarity-lexer = { git = "https://github.com/ilblackdragon/lunarity", rev = "5201d9a76f7e491082b7f74af7e64049271e387f", default-features = false } ethabi = { git = "https://github.com/darwinia-network/ethabi", branch = "xavier-no-std", default-features = false } hex = { version = "0.4", default-features = false, features = ["alloc"] } +byte-slice-cast = { version = "1.0", default-features = false } rjson = { version = "0.3.1", default-features = false } [dev-dependencies] @@ -76,5 +77,9 @@ git2 = "0.13" default = ["sha2", "std"] std = ["borsh/std", "evm/std", "primitive-types/std", "rlp/std", "sha3/std", "ethabi/std", "lunarity-lexer/std", "bn/std"] testnet = [] -contract = [] +engine = [] +contract = ["engine"] evm_bully = [] +log = [] +exit-precompiles = ["contract"] +integration-test = ["log"] diff --git a/Makefile b/Makefile index d94dcf5a1..9d2d30eed 100644 --- a/Makefile +++ b/Makefile @@ -1,6 +1,6 @@ CARGO = cargo NEAR = near -FEATURES = contract +FEATURES = contract,log ifeq ($(evm-bully),yes) FEATURES := $(FEATURES),evm_bully @@ -15,6 +15,7 @@ release.wasm: target/wasm32-unknown-unknown/release/aurora_engine.wasm target/wasm32-unknown-unknown/release/aurora_engine.wasm: Cargo.toml Cargo.lock $(wildcard src/*.rs) RUSTFLAGS='-C link-arg=-s' $(CARGO) build --target wasm32-unknown-unknown --release --no-default-features --features=$(FEATURES) -Z avoid-dev-deps + ls -l target/wasm32-unknown-unknown/release/aurora_engine.wasm debug: debug.wasm @@ -24,8 +25,10 @@ debug.wasm: target/wasm32-unknown-unknown/debug/aurora_engine.wasm target/wasm32-unknown-unknown/debug/aurora_engine.wasm: Cargo.toml Cargo.lock $(wildcard src/*.rs) $(CARGO) build --target wasm32-unknown-unknown --no-default-features --features=$(FEATURES) -Z avoid-dev-deps -etc/eth-contracts/artifacts/contracts/StateTest.sol/SelfDestructFactory.json: etc/eth-contracts/contracts/StateTest.sol - cd etc/eth-contracts && yarn && yarn compile +test-build: + RUSTFLAGS='-C link-arg=-s' $(CARGO) build --target wasm32-unknown-unknown --release --no-default-features --features=contract,integration-test -Z avoid-dev-deps + ln -sf target/wasm32-unknown-unknown/release/aurora_engine.wasm release.wasm + ls -l target/wasm32-unknown-unknown/release/aurora_engine.wasm .PHONY: all release debug @@ -38,10 +41,10 @@ check-format: $(CARGO) fmt -- --check check-clippy: - $(CARGO) +nightly clippy --no-default-features --features=$(FEATURES) -- -D warnings + $(CARGO) clippy --no-default-features --features=$(FEATURES) -- -D warnings # test depends on release since `tests/test_upgrade.rs` includes `release.wasm` -test: release etc/eth-contracts/artifacts/contracts/StateTest.sol/SelfDestructFactory.json +test: test-build $(CARGO) test format: diff --git a/README.md b/README.md index c4335bc40..08ad9ba3b 100644 --- a/README.md +++ b/README.md @@ -118,6 +118,11 @@ aurora deploy-code 0x600060005560648060106000396000f360e060020a6000350480638ada0 ### Examining EVM contract state +```console +$ aurora encode-address test.near +0xCBdA96B3F2B8eb962f97AE50C3852CA976740e2B +``` + ```sh aurora get-nonce 0xCBdA96B3F2B8eb962f97AE50C3852CA976740e2B aurora get-balance 0xCBdA96B3F2B8eb962f97AE50C3852CA976740e2B @@ -127,6 +132,11 @@ aurora get-storage-at 0xFc481F4037887e10708552c0D7563Ec6858640d6 0 ### Calling an EVM contract read-only +```console +$ aurora encode-address test.near +0xCBdA96B3F2B8eb962f97AE50C3852CA976740e2B +``` + ```sh aurora view --sender 0xCBdA96B3F2B8eb962f97AE50C3852CA976740e2B 0xFc481F4037887e10708552c0D7563Ec6858640d6 0x8ada066e # getCounter() aurora view --sender 0xCBdA96B3F2B8eb962f97AE50C3852CA976740e2B 0xFc481F4037887e10708552c0D7563Ec6858640d6 0xd09de08a # increment() diff --git a/doc/eth-connector.md b/doc/eth-connector.md new file mode 100644 index 000000000..56e8f07bc --- /dev/null +++ b/doc/eth-connector.md @@ -0,0 +1,42 @@ +# ETH connector + +## Build +1. For production set in the Makefile + ``` + FEATURES = contract + ``` + 1.1. For **development and testing** set in the Makefile + ``` + FEATURES = contract,integration-test + ``` +2. Build release: `$ make release` +3. Run tests: `$ cargo test` +4. Deploying process is common for Aurora itself. Please reference [README.md](../README.md) + +## Initialize eth-conenctor +With `near-cli` run: +``` +$ near call new_eth_connector '{"prover_account": "", "eth_custodian_address": ""}' --account-id + +``` + +## ETH connector specific methods +* new_eth_connector (call once) +* deposit (mutable) +* withdraw (mutable, payable) +* finish_deposit_near (private, mutable) +* ft_total_supply (view) +* ft_total_supply_near (view) +* ft_total_supply_eth (view) +* ft_balance_of (view) +* ft_balance_of_eth (view) +* ft_transfer (mutable, payable) +* ft_resolve_transfer (private, mutable) +* ft_transfer_call (mutable, payable) +* ft_on_transfer (private, mutable) +* storage_deposit (mutable) +* storage_withdraw (mutable, payable) +* storage_balance_of (view) + +## Ethereum specific flow +Follow by [this instruction](https://github.com/aurora-is-near/eth-connector/blob/master/README.md). diff --git a/etc/state-migration-test/Cargo.lock b/etc/state-migration-test/Cargo.lock new file mode 100644 index 000000000..41d496c5b --- /dev/null +++ b/etc/state-migration-test/Cargo.lock @@ -0,0 +1,997 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 3 + +[[package]] +name = "ahash" +version = "0.4.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "739f4a8db6605981345c5654f3a85b056ce52f37a39d34da03f25bf2151ea16e" + +[[package]] +name = "anyhow" +version = "1.0.40" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "28b2cd92db5cbd74e8e5028f7e27dd7aa3090e89e4f2a197cc7c8dfb69c7063b" + +[[package]] +name = "arrayref" +version = "0.3.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a4c527152e37cf757a3f78aae5a06fbeefdb07ccc535c980a3208ee3060dd544" + +[[package]] +name = "aurora-bn" +version = "0.1.0" +source = "git+https://github.com/aurora-is-near/aurora-bn.git#8f1743884061981cac84388862e2763b2aa09307" +dependencies = [ + "byteorder", + "getrandom", +] + +[[package]] +name = "aurora-engine" +version = "1.0.0" +dependencies = [ + "aurora-bn", + "blake2", + "borsh", + "byte-slice-cast", + "ethabi", + "evm", + "evm-core", + "hex", + "libsecp256k1", + "lunarity-lexer", + "num", + "primitive-types", + "ripemd160", + "rjson", + "rlp", + "sha2", + "sha3 0.9.1", + "wee_alloc", +] + +[[package]] +name = "aurora-engine-state-migration-test" +version = "1.0.0" +dependencies = [ + "aurora-engine", + "borsh", +] + +[[package]] +name = "autocfg" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cdb031dd78e28731d87d56cc8ffef4a8f36ca26c38fe2de700543e627f8a464a" + +[[package]] +name = "blake2" +version = "0.9.1" +source = "git+https://github.com/near/near-blake2.git#736ff607cc8160af87ffa697c14ebef85050138f" +dependencies = [ + "crypto-mac", + "digest 0.9.0", + "opaque-debug 0.3.0", +] + +[[package]] +name = "block-buffer" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c0940dc441f31689269e10ac70eb1002a3a1d3ad1390e030043662eb7fe4688b" +dependencies = [ + "block-padding 0.1.5", + "byte-tools", + "byteorder", + "generic-array 0.12.4", +] + +[[package]] +name = "block-buffer" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4152116fd6e9dadb291ae18fc1ec3575ed6d84c29642d97890f4b4a3417297e4" +dependencies = [ + "block-padding 0.2.1", + "generic-array 0.14.4", +] + +[[package]] +name = "block-padding" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fa79dedbb091f449f1f39e53edf88d5dbe95f895dae6135a8d7b881fb5af73f5" +dependencies = [ + "byte-tools", +] + +[[package]] +name = "block-padding" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8d696c370c750c948ada61c69a0ee2cbbb9c50b1019ddb86d9317157a99c2cae" + +[[package]] +name = "borsh" +version = "0.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09a7111f797cc721407885a323fb071636aee57f750b1a4ddc27397eba168a74" +dependencies = [ + "borsh-derive", + "hashbrown", +] + +[[package]] +name = "borsh-derive" +version = "0.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "307f3740906bac2c118a8122fe22681232b244f1369273e45f1156b45c43d2dd" +dependencies = [ + "borsh-derive-internal", + "borsh-schema-derive-internal", + "proc-macro-crate", + "proc-macro2 1.0.27", + "syn 1.0.72", +] + +[[package]] +name = "borsh-derive-internal" +version = "0.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d2104c73179359431cc98e016998f2f23bc7a05bc53e79741bcba705f30047bc" +dependencies = [ + "proc-macro2 1.0.27", + "quote 1.0.9", + "syn 1.0.72", +] + +[[package]] +name = "borsh-schema-derive-internal" +version = "0.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ae29eb8418fcd46f723f8691a2ac06857d31179d33d2f2d91eb13967de97c728" +dependencies = [ + "proc-macro2 1.0.27", + "quote 1.0.9", + "syn 1.0.72", +] + +[[package]] +name = "bumpalo" +version = "3.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c59e7af012c713f529e7a3ee57ce9b31ddd858d4b512923602f74608b009631" + +[[package]] +name = "byte-slice-cast" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "65c1bf4a04a88c54f589125563643d773f3254b5c38571395e2b591c693bbc81" + +[[package]] +name = "byte-tools" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3b5ca7a04898ad4bcd41c90c5285445ff5b791899bb1b0abdd2a2aa791211d7" + +[[package]] +name = "byteorder" +version = "1.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "14c189c53d098945499cdfa7ecc63567cf3886b3332b312a5b4585d8d3a6a610" + +[[package]] +name = "bytes" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b700ce4376041dcd0a327fd0097c41095743c4c8af8887265942faf1100bd040" + +[[package]] +name = "cfg-if" +version = "0.1.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4785bdd1c96b2a846b2bd7cc02e86b6b3dbf14e7e53446c4f54c92a361040822" + +[[package]] +name = "cfg-if" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" + +[[package]] +name = "cpufeatures" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed00c67cb5d0a7d64a44f6ad2668db7e7530311dd53ea79bcd4fb022c64911c8" +dependencies = [ + "libc", +] + +[[package]] +name = "crunchy" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7a81dae078cea95a014a339291cec439d2f232ebe854a9d672b796c6afafa9b7" + +[[package]] +name = "crypto-mac" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b584a330336237c1eecd3e94266efb216c56ed91225d634cb2991c5f3fd1aeab" +dependencies = [ + "generic-array 0.14.4", + "subtle", +] + +[[package]] +name = "digest" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f3d0c8c8752312f9713efd397ff63acb9f85585afbf179282e720e7704954dd5" +dependencies = [ + "generic-array 0.12.4", +] + +[[package]] +name = "digest" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3dd60d1080a57a05ab032377049e0591415d2b31afd7028356dbf3cc6dcb066" +dependencies = [ + "generic-array 0.14.4", +] + +[[package]] +name = "ethabi" +version = "13.0.0" +source = "git+https://github.com/darwinia-network/ethabi?branch=xavier-no-std#baacf174d5c2f12122c4ee3fe31506fb52069983" +dependencies = [ + "anyhow", + "ethereum-types", + "hex", + "sha3 0.9.1", + "uint", +] + +[[package]] +name = "ethbloom" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "779864b9c7f7ead1f092972c3257496c6a84b46dba2ce131dd8a282cb2cc5972" +dependencies = [ + "crunchy", + "fixed-hash", + "impl-rlp", + "tiny-keccak", +] + +[[package]] +name = "ethereum" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "567ce064a8232c16e2b2c2173a936b91fbe35c2f2c5278871f5a1a31688b42e9" +dependencies = [ + "ethereum-types", + "funty", + "hash-db", + "hash256-std-hasher", + "rlp", + "rlp-derive", + "sha3 0.9.1", + "triehash", +] + +[[package]] +name = "ethereum-types" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f64b5df66a228d85e4b17e5d6c6aa43b0310898ffe8a85988c4c032357aaabfd" +dependencies = [ + "ethbloom", + "fixed-hash", + "impl-rlp", + "primitive-types", + "uint", +] + +[[package]] +name = "evm" +version = "0.26.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1202e8dfa45bea73ee003c4be2f3549d60cb2e2ac5b0c1db563bd4653213efde" +dependencies = [ + "ethereum", + "evm-core", + "evm-gasometer", + "evm-runtime", + "log", + "primitive-types", + "rlp", + "sha3 0.8.2", +] + +[[package]] +name = "evm-core" +version = "0.26.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0f235e93b84fccc1ebdffad226dc56caf833de2fb2f3395f933d95fbf66b254e" +dependencies = [ + "funty", + "primitive-types", +] + +[[package]] +name = "evm-gasometer" +version = "0.26.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "463412356790c5e34e8a13cd23ba06284d9afa999e82e8c64497aed2ee625375" +dependencies = [ + "evm-core", + "evm-runtime", + "primitive-types", +] + +[[package]] +name = "evm-runtime" +version = "0.26.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0c08f510e5535cee2352adb9b93ff24dc80f8ca1bad445a9aa1292ce144b45a1" +dependencies = [ + "evm-core", + "primitive-types", + "sha3 0.8.2", +] + +[[package]] +name = "fixed-hash" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cfcf0ed7fe52a17a03854ec54a9f76d6d84508d1c0e66bc1793301c73fc8493c" +dependencies = [ + "byteorder", + "rustc-hex", + "static_assertions", +] + +[[package]] +name = "funty" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fed34cd105917e91daa4da6b3728c47b068749d6a62c59811f06ed2ac71d9da7" + +[[package]] +name = "generic-array" +version = "0.12.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ffdf9f34f1447443d37393cc6c2b8313aebddcd96906caf34e54c68d8e57d7bd" +dependencies = [ + "typenum", +] + +[[package]] +name = "generic-array" +version = "0.14.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "501466ecc8a30d1d3b7fc9229b122b2ce8ed6e9d9223f1138d4babb253e51817" +dependencies = [ + "typenum", + "version_check", +] + +[[package]] +name = "getrandom" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7fcd999463524c52659517fe2cea98493cfe485d10565e7b0fb07dbba7ad2753" +dependencies = [ + "cfg-if 1.0.0", + "js-sys", + "libc", + "wasi", + "wasm-bindgen", +] + +[[package]] +name = "hash-db" +version = "0.15.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d23bd4e7b5eda0d0f3a307e8b381fdc8ba9000f26fbe912250c0a4cc3956364a" + +[[package]] +name = "hash256-std-hasher" +version = "0.15.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "92c171d55b98633f4ed3860808f004099b36c1cc29c42cfc53aa8591b21efcf2" +dependencies = [ + "crunchy", +] + +[[package]] +name = "hashbrown" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d7afe4a420e3fe79967a00898cc1f4db7c8a49a9333a29f8a4bd76a253d5cd04" +dependencies = [ + "ahash", +] + +[[package]] +name = "hex" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" + +[[package]] +name = "impl-rlp" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f28220f89297a075ddc7245cd538076ee98b01f2a9c23a53a4f1105d5a322808" +dependencies = [ + "rlp", +] + +[[package]] +name = "js-sys" +version = "0.3.51" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "83bdfbace3a0e81a4253f73b49e960b053e396a11012cbd49b9b74d6a2b67062" +dependencies = [ + "wasm-bindgen", +] + +[[package]] +name = "keccak" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "67c21572b4949434e4fc1e1978b99c5f77064153c59d998bf13ecd96fb5ecba7" + +[[package]] +name = "lazy_static" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" + +[[package]] +name = "libc" +version = "0.2.95" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "789da6d93f1b866ffe175afc5322a4d76c038605a1c3319bb57b06967ca98a36" + +[[package]] +name = "libsecp256k1" +version = "0.3.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fc1e2c808481a63dc6da2074752fdd4336a3c8fcc68b83db6f1fd5224ae7962" +dependencies = [ + "arrayref", + "crunchy", + "digest 0.8.1", + "rand", + "subtle", +] + +[[package]] +name = "log" +version = "0.4.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "51b9bbe6c47d51fc3e1a9b945965946b4c44142ab8792c50835a980d362c2710" +dependencies = [ + "cfg-if 1.0.0", +] + +[[package]] +name = "logos" +version = "0.7.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "60ca690691528b32832c7e8aaae8ae1edcdee4e9ffde55b2d31a4795bc7a12d0" +dependencies = [ + "logos-derive", +] + +[[package]] +name = "logos-derive" +version = "0.7.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "917dccdd529d5681f3d28b26bcfdafd2ed67fe4f26d15b5ac679f67b55279f3d" +dependencies = [ + "proc-macro2 0.4.30", + "quote 0.6.13", + "regex-syntax", + "syn 0.15.44", + "utf8-ranges", +] + +[[package]] +name = "lunarity-lexer" +version = "0.2.1" +source = "git+https://github.com/ilblackdragon/lunarity?rev=5201d9a76f7e491082b7f74af7e64049271e387f#5201d9a76f7e491082b7f74af7e64049271e387f" +dependencies = [ + "logos", +] + +[[package]] +name = "memory_units" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8452105ba047068f40ff7093dd1d9da90898e63dd61736462e9cdda6a90ad3c3" + +[[package]] +name = "num" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "43db66d1170d347f9a065114077f7dccb00c1b9478c89384490a3425279a4606" +dependencies = [ + "num-bigint", + "num-complex", + "num-integer", + "num-iter", + "num-rational", + "num-traits", +] + +[[package]] +name = "num-bigint" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4e0d047c1062aa51e256408c560894e5251f08925980e53cf1aa5bd00eec6512" +dependencies = [ + "autocfg", + "num-integer", + "num-traits", +] + +[[package]] +name = "num-complex" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "26873667bbbb7c5182d4a37c1add32cdf09f841af72da53318fdb81543c15085" +dependencies = [ + "num-traits", +] + +[[package]] +name = "num-integer" +version = "0.1.44" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d2cc698a63b549a70bc047073d2949cce27cd1c7b0a4a862d08a8031bc2801db" +dependencies = [ + "autocfg", + "num-traits", +] + +[[package]] +name = "num-iter" +version = "0.1.42" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b2021c8337a54d21aca0d59a92577a029af9431cb59b909b03252b9c164fad59" +dependencies = [ + "autocfg", + "num-integer", + "num-traits", +] + +[[package]] +name = "num-rational" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d41702bd167c2df5520b384281bc111a4b5efcf7fbc4c9c222c815b07e0a6a6a" +dependencies = [ + "autocfg", + "num-bigint", + "num-integer", + "num-traits", +] + +[[package]] +name = "num-traits" +version = "0.2.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a64b1ec5cda2586e284722486d802acf1f7dbdc623e2bfc57e65ca1cd099290" +dependencies = [ + "autocfg", +] + +[[package]] +name = "opaque-debug" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2839e79665f131bdb5782e51f2c6c9599c133c6098982a54c794358bf432529c" + +[[package]] +name = "opaque-debug" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "624a8340c38c1b80fd549087862da4ba43e08858af025b236e509b6649fc13d5" + +[[package]] +name = "ppv-lite86" +version = "0.2.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac74c624d6b2d21f425f752262f42188365d7b8ff1aff74c82e45136510a4857" + +[[package]] +name = "primitive-types" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2415937401cb030a2a0a4d922483f945fa068f52a7dbb22ce0fe5f2b6f6adace" +dependencies = [ + "fixed-hash", + "impl-rlp", + "uint", +] + +[[package]] +name = "proc-macro-crate" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d6ea3c4595b96363c13943497db34af4460fb474a95c43f4446ad341b8c9785" +dependencies = [ + "toml", +] + +[[package]] +name = "proc-macro2" +version = "0.4.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cf3d2011ab5c909338f7887f4fc896d35932e29146c12c8d01da6b22a80ba759" +dependencies = [ + "unicode-xid 0.1.0", +] + +[[package]] +name = "proc-macro2" +version = "1.0.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f0d8caf72986c1a598726adc988bb5984792ef84f5ee5aa50209145ee8077038" +dependencies = [ + "unicode-xid 0.2.2", +] + +[[package]] +name = "quote" +version = "0.6.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6ce23b6b870e8f94f81fb0a363d65d86675884b34a09043c81e5562f11c1f8e1" +dependencies = [ + "proc-macro2 0.4.30", +] + +[[package]] +name = "quote" +version = "1.0.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c3d0b9745dc2debf507c8422de05d7226cc1f0644216dfdfead988f9b1ab32a7" +dependencies = [ + "proc-macro2 1.0.27", +] + +[[package]] +name = "rand" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6a6b1679d49b24bbfe0c803429aa1874472f50d9b363131f0e89fc356b544d03" +dependencies = [ + "rand_chacha", + "rand_core", + "rand_hc", +] + +[[package]] +name = "rand_chacha" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f4c8ed856279c9737206bf725bf36935d8666ead7aa69b52be55af369d193402" +dependencies = [ + "ppv-lite86", + "rand_core", +] + +[[package]] +name = "rand_core" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "90bde5296fc891b0cef12a6d03ddccc162ce7b2aff54160af9338f8d40df6d19" + +[[package]] +name = "rand_hc" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ca3129af7b92a17112d59ad498c6f81eaf463253766b90396d39ea7a39d6613c" +dependencies = [ + "rand_core", +] + +[[package]] +name = "regex-syntax" +version = "0.6.25" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f497285884f3fcff424ffc933e56d7cbca511def0c9831a7f9b5f6153e3cc89b" + +[[package]] +name = "ripemd160" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2eca4ecc81b7f313189bf73ce724400a07da2a6dac19588b03c8bd76a2dcc251" +dependencies = [ + "block-buffer 0.9.0", + "digest 0.9.0", + "opaque-debug 0.3.0", +] + +[[package]] +name = "rjson" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5510dbde48c4c37bf69123b1f636b6dd5f8dffe1f4e358af03c46a4947dca219" + +[[package]] +name = "rlp" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e54369147e3e7796c9b885c7304db87ca3d09a0a98f72843d532868675bbfba8" +dependencies = [ + "bytes", + "rustc-hex", +] + +[[package]] +name = "rlp-derive" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e33d7b2abe0c340d8797fe2907d3f20d3b5ea5908683618bfe80df7f621f672a" +dependencies = [ + "proc-macro2 1.0.27", + "quote 1.0.9", + "syn 1.0.72", +] + +[[package]] +name = "rustc-hex" +version = "2.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3e75f6a532d0fd9f7f13144f392b6ad56a32696bfcd9c78f797f16bbb6f072d6" + +[[package]] +name = "serde" +version = "1.0.126" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec7505abeacaec74ae4778d9d9328fe5a5d04253220a85c4ee022239fc996d03" + +[[package]] +name = "sha2" +version = "0.9.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b362ae5752fd2137731f9fa25fd4d9058af34666ca1966fb969119cc35719f12" +dependencies = [ + "block-buffer 0.9.0", + "cfg-if 1.0.0", + "cpufeatures", + "digest 0.9.0", + "opaque-debug 0.3.0", +] + +[[package]] +name = "sha3" +version = "0.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dd26bc0e7a2e3a7c959bc494caf58b72ee0c71d67704e9520f736ca7e4853ecf" +dependencies = [ + "block-buffer 0.7.3", + "byte-tools", + "digest 0.8.1", + "keccak", + "opaque-debug 0.2.3", +] + +[[package]] +name = "sha3" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f81199417d4e5de3f04b1e871023acea7389672c4135918f05aa9cbf2f2fa809" +dependencies = [ + "block-buffer 0.9.0", + "digest 0.9.0", + "keccak", + "opaque-debug 0.3.0", +] + +[[package]] +name = "static_assertions" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f" + +[[package]] +name = "subtle" +version = "2.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e81da0851ada1f3e9d4312c704aa4f8806f0f9d69faaf8df2f3464b4a9437c2" + +[[package]] +name = "syn" +version = "0.15.44" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ca4b3b69a77cbe1ffc9e198781b7acb0c7365a883670e8f1c1bc66fba79a5c5" +dependencies = [ + "proc-macro2 0.4.30", + "quote 0.6.13", + "unicode-xid 0.1.0", +] + +[[package]] +name = "syn" +version = "1.0.72" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a1e8cdbefb79a9a5a65e0db8b47b723ee907b7c7f8496c76a1770b5c310bab82" +dependencies = [ + "proc-macro2 1.0.27", + "quote 1.0.9", + "unicode-xid 0.2.2", +] + +[[package]] +name = "tiny-keccak" +version = "2.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2c9d3793400a45f954c52e73d068316d76b6f4e36977e3fcebb13a2721e80237" +dependencies = [ + "crunchy", +] + +[[package]] +name = "toml" +version = "0.5.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a31142970826733df8241ef35dc040ef98c679ab14d7c3e54d827099b3acecaa" +dependencies = [ + "serde", +] + +[[package]] +name = "triehash" +version = "0.8.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a1631b201eb031b563d2e85ca18ec8092508e262a3196ce9bd10a67ec87b9f5c" +dependencies = [ + "hash-db", + "rlp", +] + +[[package]] +name = "typenum" +version = "1.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "879f6906492a7cd215bfa4cf595b600146ccfac0c79bcbd1f3000162af5e8b06" + +[[package]] +name = "uint" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e11fe9a9348741cf134085ad57c249508345fe16411b3d7fb4ff2da2f1d6382e" +dependencies = [ + "byteorder", + "crunchy", + "hex", + "static_assertions", +] + +[[package]] +name = "unicode-xid" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fc72304796d0818e357ead4e000d19c9c174ab23dc11093ac919054d20a6a7fc" + +[[package]] +name = "unicode-xid" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ccb82d61f80a663efe1f787a51b16b5a51e3314d6ac365b08639f52387b33f3" + +[[package]] +name = "utf8-ranges" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b4ae116fef2b7fea257ed6440d3cfcff7f190865f170cdad00bb6465bf18ecba" + +[[package]] +name = "version_check" +version = "0.9.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5fecdca9a5291cc2b8dcf7dc02453fee791a280f3743cb0905f8822ae463b3fe" + +[[package]] +name = "wasi" +version = "0.10.2+wasi-snapshot-preview1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fd6fbd9a79829dd1ad0cc20627bf1ed606756a7f77edff7b66b7064f9cb327c6" + +[[package]] +name = "wasm-bindgen" +version = "0.2.74" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d54ee1d4ed486f78874278e63e4069fc1ab9f6a18ca492076ffb90c5eb2997fd" +dependencies = [ + "cfg-if 1.0.0", + "wasm-bindgen-macro", +] + +[[package]] +name = "wasm-bindgen-backend" +version = "0.2.74" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3b33f6a0694ccfea53d94db8b2ed1c3a8a4c86dd936b13b9f0a15ec4a451b900" +dependencies = [ + "bumpalo", + "lazy_static", + "log", + "proc-macro2 1.0.27", + "quote 1.0.9", + "syn 1.0.72", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-macro" +version = "0.2.74" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "088169ca61430fe1e58b8096c24975251700e7b1f6fd91cc9d59b04fb9b18bd4" +dependencies = [ + "quote 1.0.9", + "wasm-bindgen-macro-support", +] + +[[package]] +name = "wasm-bindgen-macro-support" +version = "0.2.74" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "be2241542ff3d9f241f5e2cb6dd09b37efe786df8851c54957683a49f0987a97" +dependencies = [ + "proc-macro2 1.0.27", + "quote 1.0.9", + "syn 1.0.72", + "wasm-bindgen-backend", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-shared" +version = "0.2.74" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d7cff876b8f18eed75a66cf49b65e7f967cb354a7aa16003fb55dbfd25b44b4f" + +[[package]] +name = "wee_alloc" +version = "0.4.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dbb3b5a6b2bb17cb6ad44a2e68a43e8d2722c997da10e928665c72ec6c0a0b8e" +dependencies = [ + "cfg-if 0.1.10", + "libc", + "memory_units", + "winapi", +] + +[[package]] +name = "winapi" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" +dependencies = [ + "winapi-i686-pc-windows-gnu", + "winapi-x86_64-pc-windows-gnu", +] + +[[package]] +name = "winapi-i686-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" + +[[package]] +name = "winapi-x86_64-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" diff --git a/etc/state-migration-test/Cargo.toml b/etc/state-migration-test/Cargo.toml new file mode 100644 index 000000000..f3a8cf82a --- /dev/null +++ b/etc/state-migration-test/Cargo.toml @@ -0,0 +1,41 @@ +[package] +name = "aurora-engine-state-migration-test" +version = "1.0.0" +authors = ["NEAR "] +edition = "2018" +description = "" +documentation = "" +readme = true +homepage = "https://github.com/aurora-is-near/aurora-engine" +repository = "https://github.com/aurora-is-near/aurora-engine" +license = "CC0-1.0" +publish = false + +[lib] +crate-type = ["cdylib", "rlib"] + +[profile.release] +opt-level = "z" +debug = false +debug-assertions = false +overflow-checks = true +lto = true +panic = "abort" +incremental = false +codegen-units = 1 +rpath = false + +[profile.dev] +opt-level = "z" +debug = false +debug-assertions = true +overflow-checks = true +lto = true +panic = "abort" +incremental = false +codegen-units = 1 +rpath = false + +[dependencies] +borsh = { version = "0.8.2", default-features = false } +aurora-engine = { path = "../../", default-features = false, features = ["engine", "sha2"] } diff --git a/etc/state-migration-test/src/lib.rs b/etc/state-migration-test/src/lib.rs new file mode 100644 index 000000000..89782d496 --- /dev/null +++ b/etc/state-migration-test/src/lib.rs @@ -0,0 +1,42 @@ +#![no_std] + +extern crate alloc; + +use alloc::vec::Vec; +use aurora_engine::engine::{Engine, EngineState}; +use aurora_engine::{sdk, storage}; +use borsh::{BorshDeserialize, BorshSerialize}; + +#[derive(BorshDeserialize, BorshSerialize)] +struct NewFancyState { + old_state: EngineState, + some_other_numbers: [u32; 7], +} + +#[no_mangle] +pub extern "C" fn state_migration() { + let old_state = match Engine::get_state() { + Ok(state) => state, + Err(e) => sdk::panic_utf8(e.as_ref()), + }; + + let new_state = NewFancyState { + old_state, + some_other_numbers: [3, 1, 4, 1, 5, 9, 2], + }; + + sdk::write_storage(&state_key(), &new_state.try_to_vec().expect("ERR_SER")); +} + +#[no_mangle] +pub extern "C" fn some_new_fancy_function() { + let state = sdk::read_storage(&state_key()) + .and_then(|bytes| NewFancyState::try_from_slice(&bytes).ok()) + .unwrap(); + + sdk::return_output(&state.some_other_numbers.try_to_vec().unwrap()); +} + +fn state_key() -> Vec { + storage::bytes_to_key(storage::KeyPrefix::Config, b"STATE") +} diff --git a/src/admin_controlled.rs b/src/admin_controlled.rs new file mode 100644 index 000000000..c96c55ccf --- /dev/null +++ b/src/admin_controlled.rs @@ -0,0 +1,28 @@ +use crate::sdk; + +pub type PausedMask = u8; + +pub trait AdminControlled { + /// Returns true if the current account is owner + fn is_owner(&self) -> bool { + sdk::current_account_id() == sdk::predecessor_account_id() + } + + /// Return the current mask representing all paused events. + fn get_paused(&self) -> PausedMask; + + /// Update mask with all paused events. + /// Implementor is responsible for guaranteeing that this function can only be + /// called by owner of the contract. + fn set_paused(&mut self, paused: PausedMask); + + /// Return if the contract is paused for the current flag and user + fn is_paused(&self, flag: PausedMask) -> bool { + (self.get_paused() & flag) != 0 && !self.is_owner() + } + + /// Asserts the passed paused flag is not set. Panics with "ERR_PAUSED" if the flag is set. + fn assert_not_paused(&self, flag: PausedMask) { + assert!(!self.is_paused(flag), "ERR_PAUSED"); + } +} diff --git a/src/benches/eth_deploy_code.rs b/src/benches/eth_deploy_code.rs index 1c0714ca5..565dd01bc 100644 --- a/src/benches/eth_deploy_code.rs +++ b/src/benches/eth_deploy_code.rs @@ -2,10 +2,11 @@ use criterion::{BatchSize, BenchmarkId, Criterion, Throughput}; use secp256k1::SecretKey; use crate::test_utils::{address_from_secret_key, create_eth_transaction, deploy_evm, SUBMIT}; +use crate::types::Wei; -const INITIAL_BALANCE: u64 = 1000; +const INITIAL_BALANCE: Wei = Wei::new_u64(1000); const INITIAL_NONCE: u64 = 0; -const TRANSFER_AMOUNT: u64 = 0; +const TRANSFER_AMOUNT: Wei = Wei::zero(); pub(crate) fn eth_deploy_code_benchmark(c: &mut Criterion) { let mut runner = deploy_evm(); @@ -13,7 +14,7 @@ pub(crate) fn eth_deploy_code_benchmark(c: &mut Criterion) { let source_account = SecretKey::random(&mut rng); runner.create_address( address_from_secret_key(&source_account), - INITIAL_BALANCE.into(), + INITIAL_BALANCE, INITIAL_NONCE.into(), ); let inputs: Vec<_> = [1, 4, 8, 12, 16] diff --git a/src/benches/eth_erc20.rs b/src/benches/eth_erc20.rs index 4dbca6449..d8f2424a1 100644 --- a/src/benches/eth_erc20.rs +++ b/src/benches/eth_erc20.rs @@ -15,7 +15,7 @@ pub(crate) fn eth_erc20_benchmark(c: &mut Criterion) { let source_account = SecretKey::random(&mut rng); runner.create_address( address_from_secret_key(&source_account), - INITIAL_BALANCE.into(), + crate::types::Wei::new_u64(INITIAL_BALANCE), INITIAL_NONCE.into(), ); let calling_account_id = "some-account.near".to_string(); diff --git a/src/benches/eth_standard_precompiles.rs b/src/benches/eth_standard_precompiles.rs index 8e6449943..fd57ce697 100644 --- a/src/benches/eth_standard_precompiles.rs +++ b/src/benches/eth_standard_precompiles.rs @@ -4,8 +4,9 @@ use secp256k1::SecretKey; use crate::test_utils::standard_precompiles::{PrecompilesConstructor, PrecompilesContract}; use crate::test_utils::{address_from_secret_key, deploy_evm, sign_transaction, SUBMIT}; +use crate::types::Wei; -const INITIAL_BALANCE: u64 = 1000; +const INITIAL_BALANCE: Wei = Wei::new_u64(1000); const INITIAL_NONCE: u64 = 0; pub(crate) fn eth_standard_precompiles_benchmark(c: &mut Criterion) { @@ -14,7 +15,7 @@ pub(crate) fn eth_standard_precompiles_benchmark(c: &mut Criterion) { let source_account = SecretKey::random(&mut rng); runner.create_address( address_from_secret_key(&source_account), - INITIAL_BALANCE.into(), + INITIAL_BALANCE, INITIAL_NONCE.into(), ); let calling_account_id = "some-account.near".to_string(); diff --git a/src/benches/eth_transfer.rs b/src/benches/eth_transfer.rs index 5532d07d5..2a0ec6c22 100644 --- a/src/benches/eth_transfer.rs +++ b/src/benches/eth_transfer.rs @@ -2,10 +2,11 @@ use criterion::{BatchSize, Criterion}; use secp256k1::SecretKey; use crate::test_utils::{address_from_secret_key, create_eth_transaction, deploy_evm, SUBMIT}; +use crate::types::Wei; -const INITIAL_BALANCE: u64 = 1000; +const INITIAL_BALANCE: Wei = Wei::new_u64(1000); const INITIAL_NONCE: u64 = 0; -const TRANSFER_AMOUNT: u64 = 123; +const TRANSFER_AMOUNT: Wei = Wei::new_u64(123); pub(crate) fn eth_transfer_benchmark(c: &mut Criterion) { let mut runner = deploy_evm(); @@ -13,13 +14,13 @@ pub(crate) fn eth_transfer_benchmark(c: &mut Criterion) { let source_account = SecretKey::random(&mut rng); runner.create_address( address_from_secret_key(&source_account), - INITIAL_BALANCE.into(), + INITIAL_BALANCE, INITIAL_NONCE.into(), ); let dest_account = address_from_secret_key(&SecretKey::random(&mut rng)); let transaction = create_eth_transaction( Some(dest_account), - TRANSFER_AMOUNT.into(), + TRANSFER_AMOUNT, vec![], Some(runner.chain_id), &source_account, diff --git a/src/connector.rs b/src/connector.rs new file mode 100644 index 000000000..fd4df0110 --- /dev/null +++ b/src/connector.rs @@ -0,0 +1,669 @@ +use crate::fungible_token::*; +use crate::parameters::*; +use crate::sdk; +use crate::types::*; + +use crate::admin_controlled::{AdminControlled, PausedMask}; +use crate::deposit_event::*; +use crate::engine::Engine; +use crate::json::parse_json; +use crate::prelude::*; +use crate::prover::validate_eth_address; +use crate::storage::{self, EthConnectorStorageId, KeyPrefix}; +#[cfg(feature = "log")] +use alloc::format; +use borsh::{BorshDeserialize, BorshSerialize}; + +pub const NO_DEPOSIT: Balance = 0; +const GAS_FOR_FINISH_DEPOSIT: Gas = 50_000_000_000_000; +// Note: Is 40Tgas always enough? +const GAS_FOR_VERIFY_LOG_ENTRY: Gas = 40_000_000_000_000; + +const UNPAUSE_ALL: PausedMask = 0; +const PAUSE_DEPOSIT: PausedMask = 1 << 0; +const PAUSE_WITHDRAW: PausedMask = 1 << 1; + +#[derive(BorshSerialize, BorshDeserialize)] +pub struct EthConnectorContract { + contract: EthConnector, + ft: FungibleToken, + paused_mask: PausedMask, +} + +/// eth-connector specific data +#[derive(BorshSerialize, BorshDeserialize)] +pub struct EthConnector { + pub prover_account: AccountId, + pub eth_custodian_address: EthAddress, +} + +/// Token message data +#[derive(BorshSerialize, BorshDeserialize)] +#[cfg_attr(not(target_arch = "wasm32"), derive(Debug))] +pub enum TokenMessageData { + Near(AccountId), + Eth { address: AccountId, message: String }, +} + +/// On-transfer message +pub struct OnTransferMessageData { + pub relayer: AccountId, + pub recipient: EthAddress, + pub fee: U256, +} + +impl EthConnectorContract { + pub fn get_instance() -> Self { + Self { + contract: Self::get_contract_data(&EthConnectorStorageId::Contract), + ft: Self::get_contract_data(&EthConnectorStorageId::FungibleToken), + paused_mask: Self::get_contract_data(&EthConnectorStorageId::PausedMask), + } + } + + fn get_contract_key(suffix: &EthConnectorStorageId) -> Vec { + storage::bytes_to_key(KeyPrefix::EthConnector, &[*suffix as u8]) + } + + fn get_contract_data(suffix: &EthConnectorStorageId) -> T { + let data = + sdk::read_storage(&Self::get_contract_key(&suffix)).expect("Failed read storage"); + T::try_from_slice(&data[..]).unwrap() + } + + /// Init eth-connector contract specific data + pub fn init_contract(args: InitCallArgs) { + // Check is it already initialized + assert!( + !sdk::storage_has_key(&Self::get_contract_key(&EthConnectorStorageId::Contract)), + "ERR_CONTRACT_INITIALIZED" + ); + #[cfg(feature = "log")] + sdk::log("[init contract]"); + + let contract_data = Self::set_contract_data(SetContractDataCallArgs { + prover_account: args.prover_account, + eth_custodian_address: args.eth_custodian_address, + }); + + let current_account_id = sdk::current_account_id(); + let owner_id = String::from_utf8(current_account_id).unwrap(); + let mut ft = FungibleToken::new(); + // Register FT account for current contract + ft.internal_register_account(&owner_id); + + let paused_mask = UNPAUSE_ALL; + sdk::save_contract( + &Self::get_contract_key(&EthConnectorStorageId::PausedMask), + &paused_mask, + ); + + Self { + contract: contract_data, + ft, + paused_mask, + } + .save_ft_contract(); + } + + /// Sets the contract data and returns it back + pub fn set_contract_data(args: SetContractDataCallArgs) -> EthConnector { + // Get initial contract arguments + let contract_data = EthConnector { + prover_account: args.prover_account, + eth_custodian_address: validate_eth_address(args.eth_custodian_address), + }; + // Save eth-connector specific data + sdk::save_contract( + &Self::get_contract_key(&EthConnectorStorageId::Contract), + &contract_data, + ); + + contract_data + } + + /// Parse event message data for tokens + fn parse_event_message(&self, message: &str) -> TokenMessageData { + let data: Vec<_> = message.split(':').collect(); + assert!(data.len() < 3); + if data.len() == 1 { + let account_id = data[0]; + assert!( + is_valid_account_id(account_id.as_bytes()), + "ERR_INVALID_ACCOUNT_ID" + ); + TokenMessageData::Near(account_id.into()) + } else { + TokenMessageData::Eth { + address: data[0].into(), + message: data[1].into(), + } + } + } + + /// Get on-transfer data from message + fn parse_on_transfer_message(&self, message: &str) -> OnTransferMessageData { + let data: Vec<_> = message.split(':').collect(); + assert_eq!(data.len(), 2); + + let msg = hex::decode(data[1]).expect(ERR_FAILED_PARSE); + let mut fee: [u8; 32] = Default::default(); + assert_eq!(msg.len(), 52, "ERR_WRONG_MESSAGE_LENGTH"); + fee.copy_from_slice(&msg[..32]); + let mut recipient: EthAddress = Default::default(); + recipient.copy_from_slice(&msg[32..52]); + // Checkk account + let account_id = data[0]; + assert!( + is_valid_account_id(account_id.as_bytes()), + "ERR_INVALID_ACCOUNT_ID" + ); + OnTransferMessageData { + relayer: account_id.into(), + recipient, + fee: U256::from_little_endian(&fee[..]), + } + } + + /// Prepare message for `ft_transfer_call` -> `ft_on_transfer` + fn set_message_for_on_transfer(&self, fee: U256, message: String) -> String { + use byte_slice_cast::AsByteSlice; + + // Relayer == predecessor + let relayer_account_id = String::from_utf8(sdk::predecessor_account_id()).unwrap(); + let mut data = fee.as_byte_slice().to_vec(); + let message = hex::decode(message).expect(ERR_FAILED_PARSE); + data.extend(message); + [relayer_account_id, hex::encode(data)].join(":") + } + + /// Deposit all types of tokens + pub fn deposit(&self) { + self.assert_not_paused(PAUSE_DEPOSIT); + + use crate::prover::Proof; + #[cfg(feature = "log")] + sdk::log("[Deposit tokens]"); + + // Get incoming deposit arguments + let raw_proof = sdk::read_input(); + let proof: Proof = Proof::try_from_slice(&raw_proof).expect(ERR_FAILED_PARSE); + // Fetch event data from Proof + let event = DepositedEvent::from_log_entry_data(&proof.log_entry_data); + + #[cfg(feature = "log")] + sdk::log(&format!( + "Deposit started: from {} to recipient {:?} with amount: {:?} and fee {:?}", + hex::encode(event.sender), + event.recipient, + event.amount.as_u128(), + event.fee.as_u128() + )); + + #[cfg(feature = "log")] + sdk::log(&format!( + "Event's address {}, custodian address {}", + hex::encode(&event.eth_custodian_address), + hex::encode(&self.contract.eth_custodian_address), + )); + + assert_eq!( + event.eth_custodian_address, self.contract.eth_custodian_address, + "ERR_WRONG_EVENT_ADDRESS", + ); + assert!(event.amount > event.fee, "ERR_NOT_ENOUGH_BALANCE_FOR_FEE"); + + // Verify proof data with cross-contract call to prover account + #[cfg(feature = "log")] + sdk::log(&format!( + "Deposit verify_log_entry for prover: {}", + self.contract.prover_account, + )); + + // Do not skip bridge call. This is only used for development and diagnostics. + let skip_bridge_call = false.try_to_vec().unwrap(); + let mut proof_to_verify = raw_proof; + proof_to_verify.extend(skip_bridge_call); + let promise0 = sdk::promise_create( + self.contract.prover_account.as_bytes(), + b"verify_log_entry", + &proof_to_verify, + NO_DEPOSIT, + GAS_FOR_VERIFY_LOG_ENTRY, + ); + let predecessor_account_id = String::from_utf8(sdk::predecessor_account_id()).unwrap(); + + // Finalize deposit + let promise1 = match self.parse_event_message(&event.recipient) { + // Deposit to NEAR accounts + TokenMessageData::Near(account_id) => { + let data = FinishDepositCallArgs { + new_owner_id: account_id, + amount: event.amount.as_u128(), + proof_key: proof.get_key(), + relayer_id: predecessor_account_id, + fee: event.fee.as_u128(), + msg: None, + } + .try_to_vec() + .unwrap(); + + sdk::promise_then( + promise0, + &sdk::current_account_id(), + b"finish_deposit_near", + &data[..], + NO_DEPOSIT, + GAS_FOR_FINISH_DEPOSIT, + ) + } + // Deposit to Eth accounts + // fee is being minted in the `ft_on_transfer` callback method + TokenMessageData::Eth { address, message } => { + // Transfer to self and then transfer ETH in `ft_on_transfer` + // address - is NEAR account + let transfer_data = TransferCallCallArgs { + receiver_id: address, + amount: event.amount.as_u128(), + memo: None, + msg: self.set_message_for_on_transfer(event.fee, message), + } + .try_to_vec() + .unwrap(); + let current_account_id = String::from_utf8(sdk::current_account_id()).unwrap(); + // Send to self - current account id + let data = FinishDepositCallArgs { + new_owner_id: current_account_id, + amount: event.amount.as_u128(), + proof_key: proof.get_key(), + relayer_id: predecessor_account_id, + fee: event.fee.as_u128(), + msg: Some(transfer_data), + } + .try_to_vec() + .unwrap(); + + sdk::promise_then( + promise0, + &sdk::current_account_id(), + b"finish_deposit_near", + &data[..], + NO_DEPOSIT, + GAS_FOR_FINISH_DEPOSIT, + ) + } + }; + + sdk::promise_return(promise1); + } + + /// Finish deposit NEAR (private method) + /// NOTE: we should `record_proof` only after `mint` operation. The reason + /// is that in this case we only calculate the amount to be credited but + /// do not save it, however, if an error occurs during the calculation, + /// this will happen before `record_proof`. After that contract will save. + pub fn finish_deposit_near(&mut self) { + sdk::assert_private_call(); + let data: FinishDepositCallArgs = + FinishDepositCallArgs::try_from_slice(&sdk::read_input()).unwrap(); + #[cfg(feature = "log")] + sdk::log(&format!("Finish deposit NEAR amount: {}", data.amount)); + assert_eq!(sdk::promise_results_count(), 1); + + // Check promise results + let data0: Vec = match sdk::promise_result(0) { + PromiseResult::Successful(x) => x, + _ => sdk::panic_utf8(b"ERR_PROMISE_INDEX"), + }; + #[cfg(feature = "log")] + sdk::log("Check verification_success"); + let verification_success = bool::try_from_slice(&data0).unwrap(); + assert!(verification_success, "ERR_VERIFY_PROOF"); + + // Mint tokens to recipient minus fee + if let Some(msg) = data.msg { + // Mint - calculate new balances + self.mint_near(data.new_owner_id, data.amount); + // Store proof only after `mint` calculations + self.record_proof(&data.proof_key); + // Save new contract data + self.save_ft_contract(); + let transfer_call_args = TransferCallCallArgs::try_from_slice(&msg).unwrap(); + self.ft_transfer_call(transfer_call_args); + } else { + // Mint - calculate new balances + self.mint_near(data.new_owner_id.clone(), data.amount - data.fee); + self.mint_near(data.relayer_id, data.fee); + // Store proof only after `mint` calculations + self.record_proof(&data.proof_key); + // Save new contract data + self.save_ft_contract(); + } + } + + /// Internal logic for explicitly setting an eth balance (needed by ApplyBackend for Engine) + pub(crate) fn internal_set_eth_balance(&mut self, address: &Address, amount: &U256) { + // Call to `as_u128` here should be fine because u128::MAX is a value greater than + // all the Wei in existence, so a u128 should always be able to represent + // the balance of a single account. + self.ft + .internal_set_eth_balance(address.0, amount.as_u128()); + self.save_ft_contract(); + } + + /// Internal ETH withdraw ETH logic + pub(crate) fn internal_remove_eth(&mut self, address: &Address, amount: &U256) { + self.burn_eth(address.0, amount.as_u128()); + self.save_ft_contract(); + } + + /// Record used proof as hash key + fn record_proof(&mut self, key: &str) { + #[cfg(feature = "log")] + sdk::log(&format!("Record proof: {}", key)); + + assert!(!self.check_used_event(key), "ERR_PROOF_EXIST"); + self.save_used_event(key); + } + + /// Mint NEAR tokens + fn mint_near(&mut self, owner_id: AccountId, amount: Balance) { + #[cfg(feature = "log")] + sdk::log(&format!("Mint NEAR {} tokens for: {}", amount, owner_id)); + + if self.ft.accounts_get(&owner_id).is_none() { + self.ft.accounts_insert(&owner_id, 0); + } + self.ft.internal_deposit(&owner_id, amount); + } + + /// Mint ETH tokens + fn mint_eth(&mut self, owner_id: EthAddress, amount: Balance) { + #[cfg(feature = "log")] + sdk::log(&format!( + "Mint ETH {} tokens for: {}", + amount, + hex::encode(owner_id) + )); + self.ft.internal_deposit_eth(owner_id, amount); + } + + /// Burn NEAR tokens + fn burn_near(&mut self, owner_id: AccountId, amount: Balance) { + #[cfg(feature = "log")] + sdk::log(&format!("Burn NEAR {} tokens for: {}", amount, owner_id)); + self.ft.internal_withdraw(&owner_id, amount); + } + + /// Burn ETH tokens + fn burn_eth(&mut self, address: EthAddress, amount: Balance) { + #[cfg(feature = "log")] + sdk::log(&format!( + "Burn ETH {} tokens for: {}", + amount, + hex::encode(address) + )); + self.ft.internal_withdraw_eth(address, amount); + } + + /// Withdraw from NEAR accounts + /// NOTE: it should be without any log data + pub fn withdraw_near(&mut self) { + self.assert_not_paused(PAUSE_WITHDRAW); + + sdk::assert_one_yocto(); + let args = WithdrawCallArgs::try_from_slice(&sdk::read_input()).expect(ERR_FAILED_PARSE); + let res = WithdrawResult { + recipient_id: args.recipient_address, + amount: args.amount, + eth_custodian_address: self.contract.eth_custodian_address, + } + .try_to_vec() + .unwrap(); + // Burn tokens to recipient + let predecessor_account_id = String::from_utf8(sdk::predecessor_account_id()).unwrap(); + self.ft + .internal_withdraw(&predecessor_account_id, args.amount); + // Save new contract data + self.save_ft_contract(); + sdk::return_output(&res[..]); + } + + /// Return total supply of NEAR + ETH + pub fn ft_total_supply(&self) { + let total_supply = self.ft.ft_total_supply(); + sdk::return_output(&total_supply.to_string().as_bytes()); + #[cfg(feature = "log")] + sdk::log(&format!("Total supply: {}", total_supply)); + } + + /// Return total supply of NEAR + pub fn ft_total_supply_near(&self) { + let total_supply = self.ft.ft_total_supply_near(); + sdk::return_output(&total_supply.to_string().as_bytes()); + #[cfg(feature = "log")] + sdk::log(&format!("Total supply NEAR: {}", total_supply)); + } + + /// Return total supply of ETH + pub fn ft_total_supply_eth(&self) { + let total_supply = self.ft.ft_total_supply_eth(); + sdk::return_output(&total_supply.to_string().as_bytes()); + #[cfg(feature = "log")] + sdk::log(&format!("Total supply ETH: {}", total_supply)); + } + + /// Return balance of NEAR + pub fn ft_balance_of(&self) { + let args = BalanceOfCallArgs::from( + parse_json(&sdk::read_input()).expect_utf8(ERR_FAILED_PARSE.as_bytes()), + ); + let balance = self.ft.ft_balance_of(&args.account_id); + sdk::return_output(&balance.to_string().as_bytes()); + #[cfg(feature = "log")] + sdk::log(&format!( + "Balance of NEAR [{}]: {}", + args.account_id, balance + )); + } + + /// Return balance of ETH + pub fn ft_balance_of_eth(&self) { + let args = + BalanceOfEthCallArgs::try_from_slice(&sdk::read_input()).expect(ERR_FAILED_PARSE); + let balance = self.ft.internal_unwrap_balance_of_eth(args.address); + #[cfg(feature = "log")] + sdk::log(&format!( + "Balance of ETH [{}]: {}", + hex::encode(args.address), + balance + )); + sdk::return_output(&balance.to_string().as_bytes()); + } + + /// Transfer between NEAR accounts + pub fn ft_transfer(&mut self) { + sdk::assert_one_yocto(); + let args = TransferCallArgs::from( + parse_json(&sdk::read_input()).expect_utf8(ERR_FAILED_PARSE.as_bytes()), + ); + self.ft + .ft_transfer(&args.receiver_id, args.amount, &args.memo); + self.save_ft_contract(); + #[cfg(feature = "log")] + sdk::log(&format!( + "Transfer amount {} to {} success with memo: {:?}", + args.amount, args.receiver_id, args.memo + )); + } + + /// FT resolve transfer logic + pub fn ft_resolve_transfer(&mut self) { + sdk::assert_private_call(); + // Check if previous promise succeeded + assert_eq!(sdk::promise_results_count(), 1); + + let args = ResolveTransferCallArgs::try_from_slice(&sdk::read_input()).unwrap(); + let amount = self + .ft + .ft_resolve_transfer(&args.sender_id, &args.receiver_id, args.amount); + #[cfg(feature = "log")] + sdk::log(&format!( + "Resolve transfer from {} to {} success", + args.sender_id, args.receiver_id + )); + // `ft_resolve_transfer` can change `total_supply` so we should save the contract + self.save_ft_contract(); + sdk::return_output(&amount.to_string().as_bytes()); + } + + /// FT transfer call from sender account (invoker account) to receiver + /// We starting early checking for message data to avoid `ft_on_transfer` call panics + /// But we don't check relayer exists. If relayer doesn't exist we simply not mint/burn the amount of the fee + pub fn ft_transfer_call(&mut self, args: TransferCallCallArgs) { + #[cfg(feature = "log")] + sdk::log(&format!( + "Transfer call to {} amount {}", + args.receiver_id, args.amount, + )); + // Verify message data before `ft_on_transfer` call to avoid verification panics + let message_data = self.parse_on_transfer_message(&args.msg); + // Check is transfer amount > fee + assert!( + args.amount > message_data.fee.as_u128(), + "ERR_NOT_ENOUGH_BALANCE_FOR_FEE" + ); + + // Additional check overflow before process `ft_on_transfer` + // But don't check overflow for relayer + // Note: It can't overflow because the total supply doesn't change during transfer. + let amount_for_check = self + .ft + .internal_unwrap_balance_of_eth(message_data.recipient); + assert!(amount_for_check.checked_add(args.amount).is_some()); + assert!(self.ft.total_supply_eth.checked_add(args.amount).is_some()); + assert!(self.ft.total_supply.checked_add(args.amount).is_some()); + + self.ft + .ft_transfer_call(&args.receiver_id, args.amount, &args.memo, args.msg); + } + + /// FT storage deposit logic + pub fn storage_deposit(&mut self) { + let args = StorageDepositCallArgs::from( + parse_json(&sdk::read_input()).expect_utf8(ERR_FAILED_PARSE.as_bytes()), + ); + + let res = self + .ft + .storage_deposit(args.account_id.as_ref(), args.registration_only); + self.save_ft_contract(); + sdk::return_output(&res.to_json_bytes()); + } + + /// FT storage withdraw + pub fn storage_withdraw(&mut self) { + sdk::assert_one_yocto(); + let args = StorageWithdrawCallArgs::from( + parse_json(&sdk::read_input()).expect_utf8(ERR_FAILED_PARSE.as_bytes()), + ); + let res = self.ft.storage_withdraw(args.amount); + self.save_ft_contract(); + sdk::return_output(&res.to_json_bytes()); + } + + /// Get balance of storage + pub fn storage_balance_of(&self) { + let args = StorageBalanceOfCallArgs::from( + parse_json(&sdk::read_input()).expect_utf8(ERR_FAILED_PARSE.as_bytes()), + ); + sdk::return_output(&self.ft.storage_balance_of(&args.account_id).to_json_bytes()); + } + + /// ft_on_transfer callback function + #[allow(clippy::unnecessary_unwrap)] + pub fn ft_on_transfer(&mut self, engine: &Engine) { + #[cfg(feature = "log")] + sdk::log("Call ft_on_transfer"); + let args = FtOnTransfer::try_from_slice(&sdk::read_input()).expect(ERR_FAILED_PARSE); + let predecessor_account_id = String::from_utf8(sdk::predecessor_account_id()).unwrap(); + let current_account_id = String::from_utf8(sdk::current_account_id()).unwrap(); + // Parse message with specific rules + let message_data = self.parse_on_transfer_message(&args.msg); + + // Special case when predecessor_account_id is current_account_id + if current_account_id == predecessor_account_id { + let fee = message_data.fee.as_u128(); + self.burn_near(current_account_id, args.amount); + // Mint fee to relayer + let relayer = engine.get_relayer(&message_data.relayer.as_bytes()); + if fee > 0 && relayer.is_some() { + self.mint_eth(message_data.recipient, args.amount - fee); + let evm_relayer_address: EthAddress = relayer.unwrap().0; + self.mint_eth(evm_relayer_address, fee); + } else { + self.mint_eth(message_data.recipient, args.amount); + } + } else { + // Implement new scheme for ERC20 + todo!(); + } + self.save_ft_contract(); + sdk::return_output(0.to_string().as_bytes()); + } + + /// Get accounts counter for statistics. + /// It represents total unique accounts (all-time, including accounts which now have zero balance). + pub fn get_accounts_counter(&self) { + sdk::return_output(&self.ft.get_accounts_counter().to_le_bytes()); + } + + /// Save eth-connector contract data + fn save_ft_contract(&mut self) { + sdk::save_contract( + &Self::get_contract_key(&EthConnectorStorageId::FungibleToken), + &self.ft, + ); + } + + /// Generate key for used events from Prood + fn used_event_key(&self, key: &str) -> Vec { + let mut v = Self::get_contract_key(&EthConnectorStorageId::UsedEvent).to_vec(); + v.extend_from_slice(key.as_bytes()); + v + } + + /// Save already used event proof as hash key + fn save_used_event(&self, key: &str) { + sdk::save_contract(&self.used_event_key(key), &0u8); + } + + /// Check is event of proof already used + fn check_used_event(&self, key: &str) -> bool { + sdk::storage_has_key(&self.used_event_key(key)) + } + + /// Get Eth connector paused flags + pub fn get_paused_flags(&self) -> PausedMask { + self.get_paused() + } + + /// Set Eth connector paused flags + pub fn set_paused_flags(&mut self, args: PauseEthConnectorCallArgs) { + self.set_paused(args.paused_mask); + } +} + +impl AdminControlled for EthConnectorContract { + fn get_paused(&self) -> PausedMask { + self.paused_mask + } + + fn set_paused(&mut self, paused_mask: PausedMask) { + self.paused_mask = paused_mask; + sdk::save_contract( + &Self::get_contract_key(&EthConnectorStorageId::PausedMask), + &self.paused_mask, + ); + } +} diff --git a/src/deposit_event.rs b/src/deposit_event.rs new file mode 100644 index 000000000..ac92dad63 --- /dev/null +++ b/src/deposit_event.rs @@ -0,0 +1,65 @@ +use crate::prover::*; +use crate::types::*; +use alloc::{ + string::{String, ToString}, + vec, +}; +use ethabi::{EventParam, ParamType}; +use primitive_types::U256; + +const DEPOSITED_EVENT: &str = "Deposited"; + +/// Data that was emitted by Deposited event. +#[derive(Debug, PartialEq)] +pub struct DepositedEvent { + pub eth_custodian_address: EthAddress, + pub sender: EthAddress, + pub recipient: String, + pub amount: U256, + pub fee: U256, +} + +impl DepositedEvent { + #[allow(dead_code)] + fn event_params() -> EventParams { + vec![ + EventParam { + name: "sender".to_string(), + kind: ParamType::Address, + indexed: true, + }, + EventParam { + name: "recipient".to_string(), + kind: ParamType::String, + indexed: false, + }, + EventParam { + name: "amount".to_string(), + kind: ParamType::Uint(256), + indexed: false, + }, + EventParam { + name: "fee".to_string(), + kind: ParamType::Uint(256), + indexed: false, + }, + ] + } + + /// Parse raw log Etherium proof entry data. + pub fn from_log_entry_data(data: &[u8]) -> Self { + let event = EthEvent::fetch_log_entry_data(DEPOSITED_EVENT, Self::event_params(), data); + let sender = event.log.params[0].value.clone().into_address().unwrap().0; + + let recipient = event.log.params[1].value.clone().to_string(); + let amount = event.log.params[2].value.clone().into_uint().unwrap(); + let fee = event.log.params[3].value.clone().into_uint().unwrap(); + Self { + eth_custodian_address: event.eth_custodian_address, + sender, + recipient, + amount, + fee, + } + } +} diff --git a/src/engine.rs b/src/engine.rs index a1190b97d..a059e591b 100644 --- a/src/engine.rs +++ b/src/engine.rs @@ -4,13 +4,15 @@ use evm::executor::{MemoryStackState, StackExecutor, StackSubstateMetadata}; use evm::ExitFatal; use evm::{Config, CreateScheme, ExitError, ExitReason}; +use crate::connector::EthConnectorContract; use crate::map::LookupMap; use crate::parameters::{FunctionCallArgs, NewCallArgs, SubmitResult, ViewCallArgs}; + use crate::precompiles; use crate::prelude::{Address, TryInto, Vec, H256, U256}; use crate::sdk; use crate::storage::{address_to_key, bytes_to_key, storage_to_key, KeyPrefix, KeyPrefixU8}; -use crate::types::{u256_to_arr, AccountId}; +use crate::types::{u256_to_arr, AccountId, Wei}; /// Errors with the EVM engine. #[derive(Debug, Clone, Eq, PartialEq)] @@ -92,6 +94,21 @@ impl ExitIntoResult for ExitReason { } } +#[derive(Debug)] +pub enum EngineStateError { + NotFound, + DeserializationFailed, +} + +impl AsRef<[u8]> for EngineStateError { + fn as_ref(&self) -> &[u8] { + match self { + Self::NotFound => b"ERR_STATE_NOT_FOUND", + Self::DeserializationFailed => b"ERR_STATE_CORRUPTED", + } + } +} + /// Engine internal state, mostly configuration. /// Should not contain anything large or enumerable. #[derive(BorshSerialize, BorshDeserialize, Default)] @@ -134,8 +151,8 @@ const CONFIG: &Config = &Config::istanbul(); const STATE_KEY: &[u8; 5] = b"STATE"; impl Engine { - pub fn new(origin: Address) -> Self { - Self::new_with_state(Engine::get_state(), origin) + pub fn new(origin: Address) -> Result { + Engine::get_state().map(|state| Self::new_with_state(state, origin)) } pub fn new_with_state(state: EngineState, origin: Address) -> Self { @@ -151,10 +168,11 @@ impl Engine { } /// Fails if state is not found. - pub fn get_state() -> EngineState { + pub fn get_state() -> Result { match sdk::read_storage(&bytes_to_key(KeyPrefix::Config, STATE_KEY)) { - None => Default::default(), - Some(bytes) => EngineState::try_from_slice(&bytes).expect("ERR_DESER"), + None => Err(EngineStateError::NotFound), + Some(bytes) => EngineState::try_from_slice(&bytes) + .map_err(|_| EngineStateError::DeserializationFailed), } } @@ -171,8 +189,7 @@ impl Engine { } pub fn get_code_size(address: &Address) -> usize { - // TODO: Seems this can be optimized to only read the register length. - Engine::get_code(&address).len() + sdk::read_storage_len(&address_to_key(KeyPrefix::Code, address)).unwrap_or(0) } pub fn set_nonce(address: &Address, nonce: &U256) { @@ -205,33 +222,37 @@ impl Engine { .unwrap_or_else(U256::zero) } - pub fn set_balance(address: &Address, balance: &U256) { + pub fn set_balance(address: &Address, balance: &Wei) { sdk::write_storage( &address_to_key(KeyPrefix::Balance, address), - &u256_to_arr(balance), + &balance.to_bytes(), ); } pub fn remove_balance(address: &Address) { + let balance = Self::get_balance(address); + // Apply changes for eth-conenctor + EthConnectorContract::get_instance().internal_remove_eth(address, &balance.raw()); sdk::remove_storage(&address_to_key(KeyPrefix::Balance, address)) } - pub fn get_balance(address: &Address) -> U256 { - sdk::read_storage(&address_to_key(KeyPrefix::Balance, address)) + pub fn get_balance(address: &Address) -> Wei { + let raw = sdk::read_storage(&address_to_key(KeyPrefix::Balance, address)) .map(|value| U256::from_big_endian(&value)) - .unwrap_or_else(U256::zero) + .unwrap_or_else(U256::zero); + Wei::new(raw) } - pub fn remove_storage(address: &Address, key: &H256) { - sdk::remove_storage(&storage_to_key(address, key)); + pub fn remove_storage(address: &Address, key: &H256, generation: u32) { + sdk::remove_storage(&storage_to_key(address, key, generation).as_ref()); } - pub fn set_storage(address: &Address, key: &H256, value: &H256) { - sdk::write_storage(&storage_to_key(address, key), &value.0); + pub fn set_storage(address: &Address, key: &H256, value: &H256, generation: u32) { + sdk::write_storage(&storage_to_key(address, key, generation).as_ref(), &value.0); } - pub fn get_storage(address: &Address, key: &H256) -> H256 { - sdk::read_storage(&storage_to_key(address, key)) + pub fn get_storage(address: &Address, key: &H256, generation: u32) -> H256 { + sdk::read_storage(storage_to_key(address, key, generation).as_ref()) .map(|value| H256::from_slice(&value)) .unwrap_or_else(H256::default) } @@ -240,11 +261,29 @@ impl Engine { let balance = Self::get_balance(address); let nonce = Self::get_nonce(address); let code_len = Self::get_code_size(address); - balance == U256::zero() && nonce == U256::zero() && code_len == 0 + balance.is_zero() && nonce.is_zero() && code_len == 0 + } + + /// Increments storage generation for a given address. + pub fn set_generation(address: &Address, generation: u32) { + sdk::write_storage( + &address_to_key(KeyPrefix::Generation, address), + &generation.to_be_bytes(), + ); + } + + pub fn get_generation(address: &Address) -> u32 { + sdk::read_storage(&address_to_key(KeyPrefix::Generation, address)) + .map(|value| { + let mut bytes = [0u8; 4]; + bytes[0..4].copy_from_slice(&value[0..4]); + u32::from_be_bytes(bytes) + }) + .unwrap_or(0) } /// Removes all storage for the given address. - pub fn remove_all_storage(_address: &Address) { + pub fn remove_all_storage(address: &Address, generation: u32) { // FIXME: there is presently no way to prefix delete trie state. // NOTE: There is not going to be a method on runtime for this. // You may need to store all keys in a list if you want to do this in a contract. @@ -253,42 +292,42 @@ impl Engine { // Either way you may have to store the nonce per storage address root. When the account // has to be deleted the storage nonce needs to be increased, and the old nonce keys // can be deleted over time. That's how TurboGeth does storage. + Self::set_generation(address, generation + 1); } /// Removes an account. - pub fn remove_account(address: &Address) { + pub fn remove_account(address: &Address, generation: u32) { Self::remove_nonce(address); Self::remove_balance(address); Self::remove_code(address); - Self::remove_all_storage(address); + Self::remove_all_storage(address, generation); } /// Removes an account if it is empty. - pub fn remove_account_if_empty(address: &Address) { + pub fn remove_account_if_empty(address: &Address, generation: u32) { if Self::is_account_empty(address) { - Self::remove_account(address); + Self::remove_account(address, generation); } } pub fn deploy_code_with_input(&mut self, input: Vec) -> EngineResult { let origin = self.origin(); - let value = U256::zero(); + let value = Wei::zero(); self.deploy_code(origin, value, input) } pub fn deploy_code( &mut self, origin: Address, - value: U256, + value: Wei, input: Vec, ) -> EngineResult { let mut executor = self.make_executor(); let address = executor.create_address(CreateScheme::Legacy { caller: origin }); let (status, result) = ( - executor.transact_create(origin, value, input, u64::MAX), + executor.transact_create(origin, value.raw(), input, u64::MAX), address, ); - let is_succeed = status.is_succeed(); status.into_result()?; let used_gas = executor.used_gas(); @@ -306,7 +345,7 @@ impl Engine { pub fn call_with_args(&mut self, args: FunctionCallArgs) -> EngineResult { let origin = self.origin(); let contract = Address(args.contract); - let value = U256::zero(); + let value = Wei::zero(); self.call(origin, contract, value, args.input) } @@ -314,11 +353,12 @@ impl Engine { &mut self, origin: Address, contract: Address, - value: U256, + value: Wei, input: Vec, ) -> EngineResult { let mut executor = self.make_executor(); - let (status, result) = executor.transact_call(origin, contract, value, input, u64::MAX); + let (status, result) = + executor.transact_call(origin, contract, value.raw(), input, u64::MAX); let used_gas = executor.used_gas(); let (values, logs) = executor.into_state().deconstruct(); @@ -347,33 +387,23 @@ impl Engine { Self::set_nonce(address, &new_nonce); } - #[cfg(feature = "testnet")] - /// Credits the address with 10 coins from the faucet. - pub fn credit(&self, address: &Address) -> EngineResult<()> { - let balance = Self::get_balance(address); - // Saturating adds are intentional - let new_balance = balance.saturating_add(U256::from(10_000_000_000_000_000_000)); - - Self::set_balance(address, &new_balance); - Ok(()) - } - pub fn view_with_args(&self, args: ViewCallArgs) -> EngineResult> { let origin = Address::from_slice(&args.sender); let contract = Address::from_slice(&args.address); let value = U256::from_big_endian(&args.amount); - self.view(origin, contract, value, args.input) + self.view(origin, contract, Wei::new(value), args.input) } pub fn view( &self, origin: Address, contract: Address, - value: U256, + value: Wei, input: Vec, ) -> EngineResult> { let mut executor = self.make_executor(); - let (status, result) = executor.transact_call(origin, contract, value, input, u64::MAX); + let (status, result) = + executor.transact_call(origin, contract, value.raw(), input, u64::MAX); status.into_result()?; Ok(result) } @@ -492,7 +522,7 @@ impl evm::backend::Backend for Engine { fn basic(&self, address: Address) -> Basic { Basic { nonce: Engine::get_nonce(&address), - balance: Engine::get_balance(&address), + balance: Engine::get_balance(&address).raw(), } } @@ -503,7 +533,8 @@ impl evm::backend::Backend for Engine { /// Get storage value of address at index. fn storage(&self, address: Address, index: H256) -> H256 { - Engine::get_storage(&address, &index) + let generation = Self::get_generation(&address); + Engine::get_storage(&address, &index, generation) } /// Get original storage value of address at index, if available. @@ -530,29 +561,38 @@ impl ApplyBackend for Engine { storage, reset_storage, } => { + let generation = Self::get_generation(&address); Engine::set_nonce(&address, &basic.nonce); - Engine::set_balance(&address, &basic.balance); + + // Apply changes for eth-connector + EthConnectorContract::get_instance() + .internal_set_eth_balance(&address, &basic.balance); + Engine::set_balance(&address, &Wei::new(basic.balance)); + if let Some(code) = code { Engine::set_code(&address, &code) } if reset_storage { - Engine::remove_all_storage(&address) + Engine::remove_all_storage(&address, generation) } for (index, value) in storage { if value == H256::default() { - Engine::remove_storage(&address, &index) + Engine::remove_storage(&address, &index, generation) } else { - Engine::set_storage(&address, &index, &value) + Engine::set_storage(&address, &index, &value, generation) } } if delete_empty { - Engine::remove_account_if_empty(&address) + Engine::remove_account_if_empty(&address, generation) } } - Apply::Delete { address } => Engine::remove_account(&address), + Apply::Delete { address } => { + let generation = Self::get_generation(&address); + Engine::remove_account(&address, generation); + } } } } diff --git a/src/fungible_token.rs b/src/fungible_token.rs new file mode 100644 index 000000000..361345cad --- /dev/null +++ b/src/fungible_token.rs @@ -0,0 +1,484 @@ +#[cfg(feature = "engine")] +use crate::parameters::*; +#[cfg(feature = "engine")] +use crate::prelude::{self, Ordering, String, ToString, Vec, U256}; +use crate::types::*; +#[cfg(feature = "engine")] +use crate::{connector, engine, sdk, storage}; +#[cfg(feature = "log")] +use alloc::format; +use borsh::{BorshDeserialize, BorshSerialize}; + +#[cfg(feature = "engine")] +const GAS_FOR_RESOLVE_TRANSFER: Gas = 5_000_000_000_000; +#[cfg(feature = "engine")] +const GAS_FOR_FT_ON_TRANSFER: Gas = 10_000_000_000_000; + +#[derive(Debug, Default, BorshDeserialize, BorshSerialize)] +pub struct FungibleToken { + /// Total supply of the all token. + pub total_supply: Balance, + + /// Total supply of the all ETH token. + pub total_supply_eth: Balance, + + /// The storage size in bytes for one account. + pub account_storage_usage: StorageUsage, +} + +#[cfg(feature = "engine")] +impl FungibleToken { + pub fn new() -> Self { + Self::default() + } + + /// Balance of NEAR tokens + pub fn internal_unwrap_balance_of(&self, account_id: &str) -> Balance { + match self.accounts_get(account_id) { + Some(balance) => u128::try_from_slice(&balance[..]).unwrap(), + None => sdk::panic_utf8(b"ERR_ACCOUNT_NOT_EXIST"), + } + } + + /// Balance of ETH tokens + pub fn internal_unwrap_balance_of_eth(&self, address: EthAddress) -> Balance { + engine::Engine::get_balance(&prelude::Address(address)) + .raw() + .as_u128() + } + + /// Internal deposit NEAR - NEP-141 + pub fn internal_deposit(&mut self, account_id: &str, amount: Balance) { + let balance = self.internal_unwrap_balance_of(account_id); + if let Some(new_balance) = balance.checked_add(amount) { + self.accounts_insert(account_id, new_balance); + self.total_supply = self + .total_supply + .checked_add(amount) + .expect("ERR_TOTAL_SUPPLY_OVERFLOW"); + } else { + sdk::panic_utf8(b"ERR_BALANCE_OVERFLOW"); + } + } + + /// Internal deposit ETH (nETH) + pub fn internal_deposit_eth(&mut self, address: EthAddress, amount: Balance) { + let balance = self.internal_unwrap_balance_of_eth(address); + if let Some(new_balance) = balance.checked_add(amount) { + engine::Engine::set_balance( + &prelude::Address(address), + &Wei::new(U256::from(new_balance)), + ); + self.total_supply_eth = self + .total_supply_eth + .checked_add(amount) + .expect("ERR_TOTAL_SUPPLY_OVERFLOW"); + self.total_supply = self + .total_supply + .checked_add(amount) + .expect("ERR_TOTAL_SUPPLY_OVERFLOW"); + } else { + sdk::panic_utf8(b"ERR_BALANCE_OVERFLOW"); + } + } + + /// Needed by engine to update balances after a transaction (see ApplyBackend for Engine) + pub(crate) fn internal_set_eth_balance(&mut self, address: EthAddress, new_balance: Balance) { + let current_balance = self.internal_unwrap_balance_of_eth(address); + match current_balance.cmp(&new_balance) { + Ordering::Less => { + // current_balance is smaller, so we need to deposit + let diff = new_balance - current_balance; + self.internal_deposit_eth(address, diff); + } + Ordering::Greater => { + // current_balance is larger, so we need to withdraw + let diff = current_balance - new_balance; + self.internal_withdraw_eth(address, diff); + } + // if the balances are equal then we do not need to do anything + Ordering::Equal => (), + } + } + + /// Withdraw NEAR tokens + pub fn internal_withdraw(&mut self, account_id: &str, amount: Balance) { + let balance = self.internal_unwrap_balance_of(account_id); + if let Some(new_balance) = balance.checked_sub(amount) { + self.accounts_insert(account_id, new_balance); + self.total_supply = self + .total_supply + .checked_sub(amount) + .expect("ERR_TOTAL_SUPPLY_OVERFLOW"); + } else { + sdk::panic_utf8(b"ERR_NOT_ENOUGH_BALANCE"); + } + } + + /// Withdraw ETH tokens + pub fn internal_withdraw_eth(&mut self, address: EthAddress, amount: Balance) { + let balance = self.internal_unwrap_balance_of_eth(address); + if let Some(new_balance) = balance.checked_sub(amount) { + engine::Engine::set_balance( + &prelude::Address(address), + &Wei::new(U256::from(new_balance)), + ); + self.total_supply_eth = self + .total_supply_eth + .checked_sub(amount) + .expect("ERR_TOTAL_SUPPLY_OVERFLOW"); + self.total_supply = self + .total_supply + .checked_sub(amount) + .expect("ERR_TOTAL_SUPPLY_OVERFLOW"); + } else { + sdk::panic_utf8(b"ERR_NOT_ENOUGH_BALANCE"); + } + } + + /// Transfer NEAR tokens + pub fn internal_transfer( + &mut self, + sender_id: &str, + receiver_id: &str, + amount: Balance, + #[allow(unused_variables)] memo: &Option, + ) { + assert_ne!( + sender_id, receiver_id, + "Sender and receiver should be different" + ); + assert!(amount > 0, "The amount should be a positive number"); + self.internal_withdraw(sender_id, amount); + self.internal_deposit(receiver_id, amount); + #[cfg(feature = "log")] + sdk::log(&format!( + "Transfer {} from {} to {}", + amount, sender_id, receiver_id + )); + #[cfg(feature = "log")] + if let Some(memo) = memo { + sdk::log(&format!("Memo: {}", memo)); + } + } + + pub fn internal_register_account(&mut self, account_id: &str) { + self.accounts_insert(account_id, 0) + } + + pub fn ft_transfer(&mut self, receiver_id: &str, amount: Balance, memo: &Option) { + sdk::assert_one_yocto(); + let predecessor_account_id = sdk::predecessor_account_id(); + let sender_id = str_from_slice(&predecessor_account_id); + self.internal_transfer(sender_id, receiver_id, amount, memo); + } + + pub fn ft_total_supply(&self) -> u128 { + self.total_supply + } + + pub fn ft_total_supply_near(&self) -> u128 { + self.total_supply - self.total_supply_eth + } + + pub fn ft_total_supply_eth(&self) -> u128 { + self.total_supply_eth + } + + pub fn ft_balance_of(&self, account_id: &str) -> u128 { + if let Some(data) = self.accounts_get(account_id) { + u128::try_from_slice(&data[..]).unwrap() + } else { + 0 + } + } + + pub fn ft_transfer_call( + &mut self, + receiver_id: &str, + amount: Balance, + memo: &Option, + msg: String, + ) { + let predecessor_account_id = sdk::predecessor_account_id(); + let sender_id = str_from_slice(&predecessor_account_id); + // Special case for Aurora transfer itself - we shouldn't transfer + if sender_id != receiver_id { + self.internal_transfer(sender_id, receiver_id, amount, memo); + } + // Note: This seems to be breaking the invariant that sender_id != receiver_id. You need to + // make sure the ft implementation doesn't break after this change. + + let data1 = FtOnTransfer { + amount, + msg, + receiver_id: receiver_id.to_string(), + } + .try_to_vec() + .unwrap(); + let account_id = String::from_utf8(sdk::current_account_id()).unwrap(); + let data2 = FtResolveTransfer { + receiver_id: receiver_id.to_string(), + amount, + current_account_id: account_id, + } + .try_to_vec() + .unwrap(); + // Initiating receiver's call and the callback + let promise0 = sdk::promise_create( + receiver_id.as_bytes(), + b"ft_on_transfer", + &data1[..], + connector::NO_DEPOSIT, + GAS_FOR_FT_ON_TRANSFER, + ); + let promise1 = sdk::promise_then( + promise0, + &sdk::current_account_id(), + b"ft_resolve_transfer", + &data2[..], + connector::NO_DEPOSIT, + GAS_FOR_RESOLVE_TRANSFER, + ); + sdk::promise_return(promise1); + } + + pub fn internal_ft_resolve_transfer( + &mut self, + sender_id: &str, + receiver_id: &str, + amount: Balance, + ) -> (u128, u128) { + // Get the unused amount from the `ft_on_transfer` call result. + let unused_amount = match sdk::promise_result(0) { + PromiseResult::NotReady => unreachable!(), + PromiseResult::Successful(value) => { + if let Ok(unused_amount) = String::from_utf8(value) { + let unused_amount = if let Ok(v) = unused_amount.parse::() { + v + } else { + amount + }; + if amount > unused_amount { + unused_amount + } else { + amount + } + } else { + amount + } + } + PromiseResult::Failed => amount, + }; + + if unused_amount > 0 { + let receiver_balance = if let Some(receiver_balance) = self.accounts_get(receiver_id) { + u128::try_from_slice(&receiver_balance[..]).unwrap() + } else { + self.accounts_insert(receiver_id, 0); + 0 + }; + if receiver_balance > 0 { + let refund_amount = if receiver_balance > unused_amount { + unused_amount + } else { + receiver_balance + }; + self.accounts_insert(receiver_id, receiver_balance - refund_amount); + #[cfg(feature = "log")] + sdk::log(&format!( + "Decrease receiver {} balance to: {}", + receiver_id, + receiver_balance - refund_amount + )); + + return if let Some(sender_balance) = self.accounts_get(sender_id) { + let sender_balance = u128::try_from_slice(&sender_balance[..]).unwrap(); + self.accounts_insert(sender_id, sender_balance + refund_amount); + #[cfg(feature = "log")] + sdk::log(&format!( + "Refund amount {} from {} to {}", + refund_amount, receiver_id, sender_id + )); + (amount - refund_amount, 0) + } else { + // Sender's account was deleted, so we need to burn tokens. + self.total_supply -= refund_amount; + #[cfg(feature = "log")] + sdk::log("The account of the sender was deleted"); + (amount, refund_amount) + }; + } + } + (amount, 0) + } + + pub fn ft_resolve_transfer( + &mut self, + sender_id: &str, + receiver_id: &str, + amount: u128, + ) -> u128 { + self.internal_ft_resolve_transfer(sender_id, receiver_id, amount) + .0 + } + + pub fn internal_storage_unregister( + &mut self, + force: Option, + ) -> Option<(AccountId, Balance)> { + sdk::assert_one_yocto(); + let account_id_key = sdk::predecessor_account_id(); + let account_id = str_from_slice(&account_id_key); + let force = force.unwrap_or(false); + if let Some(balance) = self.accounts_get(account_id) { + let balance = u128::try_from_slice(&balance[..]).unwrap(); + if balance == 0 || force { + self.accounts_remove(account_id); + self.total_supply -= balance; + let amount = self.storage_balance_bounds().min + 1; + let promise0 = sdk::promise_batch_create(&account_id_key); + sdk::promise_batch_action_transfer(promise0, amount); + Some((account_id.to_string(), balance)) + } else { + sdk::panic_utf8(b"ERR_FAILED_UNREGISTER_ACCOUNT_POSITIVE_BALANCE") + } + } else { + #[cfg(feature = "log")] + sdk::log(&format!("The account {} is not registered", &account_id)); + None + } + } + + pub fn storage_balance_bounds(&self) -> StorageBalanceBounds { + let required_storage_balance = + Balance::from(self.account_storage_usage) * sdk::storage_byte_cost(); + StorageBalanceBounds { + min: required_storage_balance, + max: Some(required_storage_balance), + } + } + + pub fn internal_storage_balance_of(&self, account_id: &str) -> Option { + if self.accounts_contains_key(account_id) { + Some(StorageBalance { + total: self.storage_balance_bounds().min, + available: 0, + }) + } else { + None + } + } + + pub fn storage_balance_of(&self, account_id: &str) -> StorageBalance { + self.internal_storage_balance_of(account_id) + .unwrap_or_default() + } + + // `registration_only` doesn't affect the implementation for vanilla fungible token. + #[allow(unused_variables)] + pub fn storage_deposit( + &mut self, + account_id: Option<&AccountId>, + registration_only: Option, + ) -> StorageBalance { + let amount: Balance = sdk::attached_deposit(); + let predecessor_account_id = String::from_utf8(sdk::predecessor_account_id()).unwrap(); + let account_id = account_id.unwrap_or(&predecessor_account_id); + if self.accounts_contains_key(account_id) { + #[cfg(feature = "log")] + sdk::log("The account is already registered, refunding the deposit"); + if amount > 0 { + let promise0 = sdk::promise_batch_create(&sdk::predecessor_account_id()); + sdk::promise_batch_action_transfer(promise0, amount); + } + } else { + let min_balance = self.storage_balance_bounds().min; + if amount < min_balance { + #[cfg(feature = "log")] + sdk::panic_utf8(b"ERR_ATTACHED_DEPOSIT_NOT_ENOUGH"); + } + + self.internal_register_account(account_id); + let refund = amount - min_balance; + if refund > 0 { + let promise0 = sdk::promise_batch_create(&sdk::predecessor_account_id()); + sdk::promise_batch_action_transfer(promise0, refund); + } + } + self.internal_storage_balance_of(account_id).unwrap() + } + + #[allow(dead_code)] + pub fn storage_unregister(&mut self, force: Option) -> bool { + self.internal_storage_unregister(force).is_some() + } + + pub fn storage_withdraw(&mut self, amount: Option) -> StorageBalance { + let predecessor_account_id_bytes = sdk::predecessor_account_id(); + let predecessor_account_id = str_from_slice(&predecessor_account_id_bytes); + if let Some(storage_balance) = self.internal_storage_balance_of(predecessor_account_id) { + match amount { + Some(amount) if amount > 0 => { + sdk::panic_utf8(b"ERR_WRONG_AMOUNT"); + } + _ => storage_balance, + } + } else { + sdk::panic_utf8(b"ERR_ACCOUNT_NOT_REGISTERED"); + } + } + + /// Insert account. + /// Calculate total unique accounts + pub fn accounts_insert(&self, account_id: &str, amount: Balance) { + if !self.accounts_contains_key(account_id) { + let key = Self::get_statistic_key(); + let accounts_counter = sdk::read_u64(&key) + .unwrap_or(Ok(0)) + .unwrap_or(0) + .checked_add(1) + .expect("ERR_ACCOUNTS_COUNTER_OVERFLOW"); + sdk::write_storage(&key, &accounts_counter.to_le_bytes()); + } + sdk::save_contract(&Self::account_to_key(account_id), &amount); + } + + /// Get accounts counter for statistics + /// It represents total unique accounts. + pub fn get_accounts_counter(&self) -> u64 { + sdk::read_u64(&Self::get_statistic_key()) + .unwrap_or(Ok(0)) + .unwrap_or(0) + } + + fn accounts_contains_key(&self, account_id: &str) -> bool { + sdk::storage_has_key(&Self::account_to_key(account_id)) + } + + fn accounts_remove(&self, account_id: &str) { + sdk::remove_storage(&Self::account_to_key(account_id)) + } + + pub fn accounts_get(&self, account_id: &str) -> Option> { + sdk::read_storage(&Self::account_to_key(account_id)) + } + + /// Fungible token key + fn account_to_key(account_id: &str) -> Vec { + let mut key = storage::bytes_to_key( + storage::KeyPrefix::EthConnector, + &[storage::EthConnectorStorageId::FungibleToken as u8], + ); + key.extend_from_slice(account_id.as_bytes()); + key + } + + /// Key for store contract statistics data + fn get_statistic_key() -> Vec { + storage::bytes_to_key( + storage::KeyPrefix::EthConnector, + &[storage::EthConnectorStorageId::StatisticsAuroraAccountsCounter as u8], + ) + } +} diff --git a/src/json.rs b/src/json.rs index de4c611a7..03e08c40a 100644 --- a/src/json.rs +++ b/src/json.rs @@ -1,13 +1,11 @@ use super::prelude::*; use crate::sdk; +use crate::types::ERR_FAILED_PARSE; use alloc::collections::BTreeMap; use core::convert::From; use rjson::{Array, Null, Object, Value}; -#[allow(dead_code)] -pub const FAILED_PARSE: &[u8; 22] = b"\0ERR_FAILED_PARSE_JSON"; - pub enum JsonValue { Null, Number(f64), @@ -69,7 +67,7 @@ impl JsonValue { pub fn parse_u8(v: &JsonValue) -> u8 { match v { JsonValue::Number(n) => *n as u8, - _ => sdk::panic_utf8(FAILED_PARSE), + _ => sdk::panic_utf8(ERR_FAILED_PARSE.as_bytes()), } } diff --git a/src/lib.rs b/src/lib.rs index 6ef419227..f43aa7a3c 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,13 +1,14 @@ +#![feature(array_methods)] #![cfg_attr(not(feature = "std"), no_std)] -#![cfg_attr(not(feature = "std"), feature(core_intrinsics))] #![cfg_attr(not(feature = "std"), feature(alloc_error_handler))] +#![cfg_attr(feature = "log", feature(panic_info_message))] #[cfg(not(feature = "std"))] extern crate alloc; #[cfg(not(feature = "std"))] extern crate core; -#[cfg(feature = "contract")] +#[cfg(feature = "engine")] mod map; pub mod meta_parsing; pub mod parameters; @@ -16,15 +17,25 @@ pub mod storage; pub mod transaction; pub mod types; -#[cfg(feature = "contract")] -mod engine; -#[cfg(feature = "contract")] +#[cfg(feature = "engine")] +mod admin_controlled; +#[cfg(feature = "engine")] +mod connector; +#[cfg(feature = "engine")] +mod deposit_event; +#[cfg(feature = "engine")] +pub mod engine; +#[cfg(any(feature = "engine", test))] +mod fungible_token; +#[cfg(feature = "engine")] mod json; -#[cfg(feature = "contract")] +#[cfg(feature = "engine")] mod log_entry; mod precompiles; -#[cfg(feature = "contract")] -mod sdk; +#[cfg(feature = "engine")] +mod prover; +#[cfg(feature = "engine")] +pub mod sdk; #[cfg(test)] mod benches; @@ -33,39 +44,60 @@ mod test_utils; #[cfg(test)] mod tests; +#[cfg(target_arch = "wasm32")] +#[global_allocator] +static ALLOC: wee_alloc::WeeAlloc = wee_alloc::WeeAlloc::INIT; + +#[cfg(target_arch = "wasm32")] +#[panic_handler] +#[cfg_attr(not(feature = "log"), allow(unused_variables))] +#[no_mangle] +pub unsafe fn on_panic(info: &::core::panic::PanicInfo) -> ! { + #[cfg(feature = "log")] + { + use alloc::{format, string::ToString}; + if let Some(msg) = info.message() { + let msg = if let Some(log) = info.location() { + format!("{} [{}]", msg, log) + } else { + msg.to_string() + }; + sdk::panic_utf8(msg.as_bytes()); + } else if let Some(log) = info.location() { + sdk::panic_utf8(log.to_string().as_bytes()); + } + } + + ::core::arch::wasm32::unreachable(); +} + +#[cfg(target_arch = "wasm32")] +#[alloc_error_handler] +#[no_mangle] +pub unsafe fn on_alloc_error(_: core::alloc::Layout) -> ! { + ::core::arch::wasm32::unreachable(); +} + #[cfg(feature = "contract")] mod contract { use borsh::{BorshDeserialize, BorshSerialize}; + use crate::connector::EthConnectorContract; use crate::engine::{Engine, EngineResult, EngineState}; #[cfg(feature = "evm_bully")] use crate::parameters::{BeginBlockArgs, BeginChainArgs}; - use crate::parameters::{FunctionCallArgs, GetStorageAtArgs, NewCallArgs, ViewCallArgs}; - use crate::prelude::{Address, TryInto, H256, U256}; + use crate::parameters::{ + ExpectUtf8, FunctionCallArgs, GetStorageAtArgs, InitCallArgs, NewCallArgs, + PauseEthConnectorCallArgs, SetContractDataCallArgs, TransferCallCallArgs, ViewCallArgs, + }; + use crate::prelude::{Address, H256, U256}; use crate::sdk; use crate::storage::{bytes_to_key, KeyPrefix}; - use crate::types::{near_account_to_evm_address, u256_to_arr}; - - #[global_allocator] - static ALLOC: wee_alloc::WeeAlloc = wee_alloc::WeeAlloc::INIT; + use crate::types::{near_account_to_evm_address, u256_to_arr, ERR_FAILED_PARSE}; const CODE_KEY: &[u8; 4] = b"CODE"; const CODE_STAGE_KEY: &[u8; 10] = b"CODE_STAGE"; - #[cfg(target_arch = "wasm32")] - #[panic_handler] - #[no_mangle] - pub unsafe fn on_panic(_info: &::core::panic::PanicInfo) -> ! { - ::core::intrinsics::abort(); - } - - #[cfg(target_arch = "wasm32")] - #[alloc_error_handler] - #[no_mangle] - pub unsafe fn on_alloc_error(_: core::alloc::Layout) -> ! { - ::core::intrinsics::abort(); - } - /// /// ADMINISTRATIVE METHODS /// @@ -74,11 +106,11 @@ mod contract { /// Should be called on deployment. #[no_mangle] pub extern "C" fn new() { - let state = Engine::get_state(); - if !state.owner_id.is_empty() { + if let Ok(state) = Engine::get_state() { require_owner_only(&state); } - let args = NewCallArgs::try_from_slice(&sdk::read_input()).sdk_expect("ERR_ARG_PARSE"); + + let args: NewCallArgs = sdk::read_input_borsh().sdk_unwrap(); Engine::set_state(args.into()); } @@ -95,35 +127,36 @@ mod contract { /// Get owner account id for this contract. #[no_mangle] pub extern "C" fn get_owner() { - let state = Engine::get_state(); + let state = Engine::get_state().sdk_unwrap(); sdk::return_output(state.owner_id.as_bytes()); } /// Get bridge prover id for this contract. #[no_mangle] pub extern "C" fn get_bridge_prover() { - let state = Engine::get_state(); + let state = Engine::get_state().sdk_unwrap(); sdk::return_output(state.bridge_prover_id.as_bytes()); } /// Get chain id for this contract. #[no_mangle] pub extern "C" fn get_chain_id() { - sdk::return_output(&Engine::get_state().chain_id) + sdk::return_output(&Engine::get_state().sdk_unwrap().chain_id) } #[no_mangle] pub extern "C" fn get_upgrade_index() { - let state = Engine::get_state(); + let state = Engine::get_state().sdk_unwrap(); let index = sdk::read_u64(&bytes_to_key(KeyPrefix::Config, CODE_STAGE_KEY)) - .sdk_expect("ERR_NO_UPGRADE"); + .sdk_expect("ERR_NO_UPGRADE") + .sdk_unwrap(); sdk::return_output(&(index + state.upgrade_delay_blocks).to_le_bytes()) } /// Stage new code for deployment. #[no_mangle] pub extern "C" fn stage_upgrade() { - let state = Engine::get_state(); + let state = Engine::get_state().sdk_unwrap(); require_owner_only(&state); sdk::read_input_and_store(&bytes_to_key(KeyPrefix::Config, CODE_KEY)); sdk::write_storage( @@ -135,14 +168,25 @@ mod contract { /// Deploy staged upgrade. #[no_mangle] pub extern "C" fn deploy_upgrade() { - let state = Engine::get_state(); - let index = sdk::read_u64(&bytes_to_key(KeyPrefix::Config, CODE_STAGE_KEY)).sdk_unwrap(); + let state = Engine::get_state().sdk_unwrap(); + let index = sdk::read_u64(&bytes_to_key(KeyPrefix::Config, CODE_STAGE_KEY)) + .sdk_expect("ERR_NO_UPGRADE") + .sdk_unwrap(); if sdk::block_index() <= index + state.upgrade_delay_blocks { sdk::panic_utf8(b"ERR_NOT_ALLOWED:TOO_EARLY"); } sdk::self_deploy(&bytes_to_key(KeyPrefix::Config, CODE_KEY)); } + /// Called as part of the upgrade process (see `sdk::self_deploy`). This function is meant + /// to make any necessary changes to the state such that it aligns with the newly deployed + /// code. + #[no_mangle] + pub extern "C" fn state_migration() { + // This function is purposely left empty because we do not have any state migration + // to do. + } + /// /// MUTATIVE METHODS /// @@ -151,7 +195,7 @@ mod contract { #[no_mangle] pub extern "C" fn deploy_code() { let input = sdk::read_input(); - let mut engine = Engine::new(predecessor_address()); + let mut engine = Engine::new(predecessor_address()).sdk_unwrap(); Engine::deploy_code_with_input(&mut engine, input) .map(|res| res.try_to_vec().sdk_expect("ERR_SERIALIZE")) .sdk_process(); @@ -161,10 +205,8 @@ mod contract { /// Call method on the EVM contract. #[no_mangle] pub extern "C" fn call() { - // TODO: Borsh input pattern is so common here. It worth writing sdk::read_input_borsh(). - let input = sdk::read_input(); - let args = FunctionCallArgs::try_from_slice(&input).sdk_expect("ERR_ARG_PARSE"); - let mut engine = Engine::new(predecessor_address()); + let args: FunctionCallArgs = sdk::read_input_borsh().sdk_unwrap(); + let mut engine = Engine::new(predecessor_address()).sdk_unwrap(); Engine::call_with_args(&mut engine, args) .map(|res| res.try_to_vec().sdk_expect("ERR_SERIALIZE")) .sdk_process(); @@ -182,7 +224,7 @@ mod contract { let signed_transaction = EthSignedTransaction::decode(&Rlp::new(&input)).sdk_expect("ERR_INVALID_TX"); - let state = Engine::get_state(); + let state = Engine::get_state().sdk_unwrap(); // Validate the chain ID, if provided inside the signature: if let Some(chain_id) = signed_transaction.chain_id() { @@ -218,7 +260,7 @@ mod contract { #[no_mangle] pub extern "C" fn meta_call() { let input = sdk::read_input(); - let state = Engine::get_state(); + let state = Engine::get_state().sdk_unwrap(); let domain_separator = crate::meta_parsing::near_erc712_domain(U256::from(state.chain_id)); let meta_call_args = crate::meta_parsing::parse_meta_call( &domain_separator, @@ -241,85 +283,56 @@ mod contract { .sdk_process(); } - #[cfg(feature = "testnet")] - #[no_mangle] - pub extern "C" fn make_it_rain() { - let input = sdk::read_input(); - let dest_address = Address::from_slice(&input); - let source_address = predecessor_address(); - let engine = Engine::new(source_address); - - engine.increment_nonce(&source_address); - - let result = engine.credit(&dest_address); - result.map(|_f| Vec::new()).sdk_process(); - } - #[no_mangle] pub extern "C" fn register_relayer() { - let relayer_address = sdk::read_input(); - // NOTE: Why not `sdk::read_input_arr20();`? - assert_eq!(relayer_address.len(), 20); + let relayer_address = sdk::read_input_arr20().sdk_unwrap(); - let mut engine = Engine::new(predecessor_address()); + let mut engine = Engine::new(predecessor_address()).sdk_unwrap(); engine.register_relayer( sdk::predecessor_account_id().as_slice(), - Address(relayer_address.as_slice().try_into().unwrap()), + Address(relayer_address), ); } - /// Allow receiving NEP141 tokens to the EVM contract - #[no_mangle] - pub extern "C" fn ft_on_transfer() { - #[allow(clippy::if_same_then_else)] - if sdk::predecessor_account_id() == sdk::current_account_id() { - // TODO(#59) ETH transfer - todo!(); - } else { - // TODO(#51) ERC20 transfer - todo!(); - } - } - /// /// NONMUTATIVE METHODS /// #[no_mangle] pub extern "C" fn view() { - let input = sdk::read_input(); - let args = ViewCallArgs::try_from_slice(&input).sdk_expect("ERR_ARG_PARSE"); - let engine = Engine::new(Address::from_slice(&args.sender)); + let args: ViewCallArgs = sdk::read_input_borsh().sdk_unwrap(); + let engine = Engine::new(Address::from_slice(&args.sender)).sdk_unwrap(); let result = Engine::view_with_args(&engine, args); result.sdk_process() } #[no_mangle] pub extern "C" fn get_code() { - let address = sdk::read_input_arr20(); + let address = sdk::read_input_arr20().sdk_unwrap(); let code = Engine::get_code(&Address(address)); sdk::return_output(&code) } #[no_mangle] pub extern "C" fn get_balance() { - let address = sdk::read_input_arr20(); + let address = sdk::read_input_arr20().sdk_unwrap(); let balance = Engine::get_balance(&Address(address)); - sdk::return_output(&u256_to_arr(&balance)) + sdk::return_output(&balance.to_bytes()) } #[no_mangle] pub extern "C" fn get_nonce() { - let address = sdk::read_input_arr20(); + let address = sdk::read_input_arr20().sdk_unwrap(); let nonce = Engine::get_nonce(&Address(address)); sdk::return_output(&u256_to_arr(&nonce)) } #[no_mangle] pub extern "C" fn get_storage_at() { - let input = sdk::read_input(); - let args = GetStorageAtArgs::try_from_slice(&input).sdk_expect("ERR_ARG_PARSE"); - let value = Engine::get_storage(&Address(args.address), &H256(args.key)); + let args: GetStorageAtArgs = sdk::read_input_borsh().sdk_unwrap(); + let address = Address(args.address); + let generation = Engine::get_generation(&address); + let value = Engine::get_storage(&Address(args.address), &H256(args.key), generation); sdk::return_output(&value.0) } @@ -330,17 +343,16 @@ mod contract { #[cfg(feature = "evm_bully")] #[no_mangle] pub extern "C" fn begin_chain() { - let mut state = Engine::get_state(); + let mut state = Engine::get_state().sdk_unwrap(); require_owner_only(&state); - let input = sdk::read_input(); - let args = BeginChainArgs::try_from_slice(&input).sdk_expect("ERR_ARG_PARSE"); + let args: BeginChainArgs = sdk::read_input_borsh().sdk_unwrap(); state.chain_id = args.chain_id; Engine::set_state(state); // set genesis block balances for account_balance in args.genesis_alloc { Engine::set_balance( &Address(account_balance.address), - &U256::from(account_balance.balance), + &crate::types::Wei::new(U256::from(account_balance.balance)), ) } // return new chain ID @@ -350,13 +362,147 @@ mod contract { #[cfg(feature = "evm_bully")] #[no_mangle] pub extern "C" fn begin_block() { - let state = Engine::get_state(); + let state = Engine::get_state().sdk_unwrap(); require_owner_only(&state); - let input = sdk::read_input(); - let _args = BeginBlockArgs::try_from_slice(&input).sdk_expect("ERR_ARG_PARSE"); + let _args: BeginBlockArgs = sdk::read_input_borsh().sdk_unwrap(); // TODO: https://github.com/aurora-is-near/aurora-engine/issues/2 } + #[no_mangle] + pub extern "C" fn new_eth_connector() { + // Only the owner can initialize the EthConnector + sdk::assert_private_call(); + + let args = InitCallArgs::try_from_slice(&sdk::read_input()).expect(ERR_FAILED_PARSE); + + EthConnectorContract::init_contract(args); + } + + #[no_mangle] + pub extern "C" fn set_eth_connector_contract_data() { + // Only the owner can set the EthConnector contract data + sdk::assert_private_call(); + + let args = + SetContractDataCallArgs::try_from_slice(&sdk::read_input()).expect(ERR_FAILED_PARSE); + + EthConnectorContract::set_contract_data(args); + } + + #[no_mangle] + pub extern "C" fn withdraw() { + EthConnectorContract::get_instance().withdraw_near() + } + + #[no_mangle] + pub extern "C" fn deposit() { + EthConnectorContract::get_instance().deposit() + } + + #[no_mangle] + pub extern "C" fn finish_deposit_near() { + EthConnectorContract::get_instance().finish_deposit_near(); + } + + #[no_mangle] + pub extern "C" fn ft_total_supply() { + EthConnectorContract::get_instance().ft_total_supply(); + } + + #[no_mangle] + pub extern "C" fn ft_total_supply_near() { + EthConnectorContract::get_instance().ft_total_supply_near(); + } + + #[no_mangle] + pub extern "C" fn ft_total_supply_eth() { + EthConnectorContract::get_instance().ft_total_supply_eth(); + } + + #[no_mangle] + pub extern "C" fn ft_balance_of() { + EthConnectorContract::get_instance().ft_balance_of(); + } + + #[no_mangle] + pub extern "C" fn ft_balance_of_eth() { + EthConnectorContract::get_instance().ft_balance_of_eth(); + } + + #[no_mangle] + pub extern "C" fn ft_transfer() { + EthConnectorContract::get_instance().ft_transfer(); + } + + #[no_mangle] + pub extern "C" fn ft_resolve_transfer() { + EthConnectorContract::get_instance().ft_resolve_transfer(); + } + + #[no_mangle] + pub extern "C" fn ft_transfer_call() { + use crate::json::parse_json; + + // Check is payable + sdk::assert_one_yocto(); + + let args = TransferCallCallArgs::from( + parse_json(&sdk::read_input()).expect_utf8(ERR_FAILED_PARSE.as_bytes()), + ); + EthConnectorContract::get_instance().ft_transfer_call(args); + } + + #[no_mangle] + pub extern "C" fn storage_deposit() { + EthConnectorContract::get_instance().storage_deposit() + } + + #[no_mangle] + pub extern "C" fn storage_withdraw() { + EthConnectorContract::get_instance().storage_withdraw() + } + + #[no_mangle] + pub extern "C" fn storage_balance_of() { + EthConnectorContract::get_instance().storage_balance_of() + } + + #[no_mangle] + pub extern "C" fn ft_on_transfer() { + let engine = Engine::new(predecessor_address()).sdk_unwrap(); + EthConnectorContract::get_instance().ft_on_transfer(&engine) + } + + #[no_mangle] + pub extern "C" fn get_paused_flags() { + let paused_flags = EthConnectorContract::get_instance().get_paused_flags(); + let data = paused_flags.try_to_vec().expect(ERR_FAILED_PARSE); + sdk::return_output(&data[..]); + } + + #[no_mangle] + pub extern "C" fn set_paused_flags() { + sdk::assert_private_call(); + + let args = + PauseEthConnectorCallArgs::try_from_slice(&sdk::read_input()).expect(ERR_FAILED_PARSE); + EthConnectorContract::get_instance().set_paused_flags(args); + } + + #[no_mangle] + pub extern "C" fn get_accounts_counter() { + EthConnectorContract::get_instance().get_accounts_counter() + } + + #[cfg(feature = "integration-test")] + #[no_mangle] + pub extern "C" fn verify_log_entry() { + #[cfg(feature = "log")] + sdk::log("Call from verify_log_entry"); + let data = true.try_to_vec().unwrap(); + sdk::return_output(&data[..]); + } + /// /// Utility methods. /// diff --git a/src/meta_parsing.rs b/src/meta_parsing.rs index 219f2302c..6b5dfcbfa 100644 --- a/src/meta_parsing.rs +++ b/src/meta_parsing.rs @@ -5,7 +5,7 @@ use rlp::{Decodable, DecoderError, Rlp}; use crate::parameters::MetaCallArgs; use crate::prelude::{vec, Address, Box, HashMap, String, ToOwned, ToString, Vec, H256, U256}; -use crate::types::{keccak, u256_to_arr, InternalMetaCallArgs, RawU256}; +use crate::types::{keccak, u256_to_arr, InternalMetaCallArgs, RawU256, Wei}; /// Internal errors to propagate up and format in the single place. pub enum ParsingError { @@ -13,6 +13,7 @@ pub enum ParsingError { InvalidMetaTransactionMethodName, InvalidMetaTransactionFunctionArg, InvalidEcRecoverSignature, + ArgsLengthMismatch, } pub type ParsingResult = core::result::Result; @@ -493,10 +494,10 @@ pub fn prepare_meta_call_args( bytes.extend_from_slice(&keccak(types.as_bytes()).as_bytes()); bytes.extend_from_slice(&keccak(account_id).as_bytes()); bytes.extend_from_slice(&u256_to_arr(&input.nonce)); - bytes.extend_from_slice(&u256_to_arr(&input.fee_amount)); + bytes.extend_from_slice(&input.fee_amount.to_bytes()); bytes.extend_from_slice(&encode_address(input.fee_address)); bytes.extend_from_slice(&encode_address(input.contract_address)); - bytes.extend_from_slice(&u256_to_arr(&input.value)); + bytes.extend_from_slice(&input.value.to_bytes()); let methods = MethodAndTypes::parse(&method_def)?; let method_sig = method_signature(&methods); @@ -505,9 +506,11 @@ pub fn prepare_meta_call_args( let mut arg_bytes = Vec::new(); arg_bytes.extend_from_slice(&keccak(arguments.as_bytes()).as_bytes()); let args_decoded: Vec = rlp_decode(&input.input)?; + if methods.method.args.len() != args_decoded.len() { + return Err(ParsingError::ArgsLengthMismatch); + } for (i, arg) in args_decoded.iter().enumerate() { arg_bytes.extend_from_slice(&eip_712_hash_argument( - // TODO: Check that method.args.len() == args_decoded.len(). Otherwise it may panic here. &methods.method.args[i].t, arg, &methods.types, @@ -544,10 +547,10 @@ pub fn parse_meta_call( let meta_tx = MetaCallArgs::try_from_slice(&args).map_err(|_| ParsingError::ArgumentParseError)?; let nonce = U256::from(meta_tx.nonce); - let fee_amount = U256::from(meta_tx.fee_amount); + let fee_amount = Wei::new(U256::from(meta_tx.fee_amount)); let fee_address = Address::from(meta_tx.fee_address); let contract_address = Address::from(meta_tx.contract_address); - let value = U256::from(meta_tx.value); + let value = Wei::new(U256::from(meta_tx.value)); let mut result = InternalMetaCallArgs { sender: Address::zero(), diff --git a/src/parameters.rs b/src/parameters.rs index 3207eb9fc..7b1831427 100644 --- a/src/parameters.rs +++ b/src/parameters.rs @@ -1,7 +1,21 @@ use borsh::{BorshDeserialize, BorshSerialize}; +#[cfg(feature = "engine")] +use crate::admin_controlled::PausedMask; +#[cfg(feature = "engine")] +use crate::json; +#[cfg(feature = "engine")] +use crate::prelude::ToString; use crate::prelude::{String, Vec}; +#[cfg(feature = "engine")] +use crate::prover::Proof; +#[cfg(feature = "engine")] +use crate::sdk; +#[cfg(feature = "engine")] +use crate::types::Balance; use crate::types::{AccountId, RawAddress, RawH256, RawU256}; +#[cfg(feature = "engine")] +use crate::types::{EthAddress, ERR_FAILED_PARSE}; use evm::backend::Log; /// Borsh-encoded parameters for the `new` function. @@ -121,6 +135,297 @@ pub struct BeginBlockArgs { pub gaslimit: RawU256, } +/// Eth-connector deposit arguments +#[cfg(feature = "engine")] +#[derive(BorshSerialize, BorshDeserialize)] +pub struct DepositCallArgs { + /// Proof data + pub proof: Proof, + /// Optional relayer address + pub relayer_eth_account: Option, +} + +/// withdraw result for eth-connector +#[cfg(feature = "engine")] +#[derive(BorshSerialize)] +pub struct WithdrawResult { + pub amount: Balance, + pub recipient_id: RawAddress, + pub eth_custodian_address: RawAddress, +} + +/// ft_on_transfer eth-connector call args +#[cfg(feature = "engine")] +#[derive(BorshSerialize, BorshDeserialize)] +pub struct FtOnTransfer { + pub amount: Balance, + pub msg: String, + pub receiver_id: AccountId, +} + +/// ft_resolve_transfer eth-connector call args +#[cfg(feature = "engine")] +#[derive(BorshSerialize)] +pub struct FtResolveTransfer { + pub receiver_id: AccountId, + pub amount: Balance, + pub current_account_id: AccountId, +} + +/// Fungible token storage balance +#[cfg(feature = "engine")] +#[derive(Default)] +pub struct StorageBalance { + pub total: Balance, + pub available: Balance, +} + +#[cfg(feature = "engine")] +impl StorageBalance { + pub fn to_json_bytes(&self) -> Vec { + use alloc::format; + format!( + "{{\"total\": \"{}\", \"available\": \"{}\",}}", + self.total.to_string(), + self.available.to_string() + ) + .as_bytes() + .to_vec() + } +} + +/// resolve_transfer eth-connector call args +#[cfg(feature = "engine")] +#[derive(BorshSerialize, BorshDeserialize)] +pub struct ResolveTransferCallArgs { + pub sender_id: AccountId, + pub amount: Balance, + pub receiver_id: AccountId, +} + +/// Finish deposit NEAR eth-connector call args +#[cfg(feature = "engine")] +#[derive(BorshSerialize, BorshDeserialize)] +pub struct FinishDepositCallArgs { + pub new_owner_id: AccountId, + pub amount: Balance, + pub proof_key: String, + pub relayer_id: AccountId, + pub fee: Balance, + pub msg: Option>, +} + +/// Deposit ETH args +#[cfg(feature = "engine")] +#[derive(Default, BorshDeserialize, BorshSerialize, Clone)] +pub struct DepositEthCallArgs { + pub proof: Proof, + pub relayer_eth_account: EthAddress, +} + +/// Finish deposit NEAR eth-connector call args +#[cfg(feature = "engine")] +#[derive(BorshSerialize, BorshDeserialize)] +pub struct FinishDepositEthCallArgs { + pub new_owner_id: EthAddress, + pub amount: Balance, + pub fee: Balance, + pub relayer_eth_account: AccountId, + pub proof: Proof, +} + +/// Eth-connector initial args +#[derive(BorshSerialize, BorshDeserialize)] +pub struct InitCallArgs { + pub prover_account: AccountId, + pub eth_custodian_address: AccountId, +} + +/// Eth-connector Set contract data call args +pub type SetContractDataCallArgs = InitCallArgs; + +/// transfer eth-connector call args +#[cfg(feature = "engine")] +#[derive(BorshSerialize, BorshDeserialize)] +pub struct TransferCallCallArgs { + pub receiver_id: AccountId, + pub amount: Balance, + pub memo: Option, + pub msg: String, +} + +#[cfg(feature = "engine")] +impl From for TransferCallCallArgs { + fn from(v: json::JsonValue) -> Self { + Self { + receiver_id: v + .string("receiver_id") + .expect_utf8(ERR_FAILED_PARSE.as_bytes()), + amount: v.u128("amount").expect_utf8(ERR_FAILED_PARSE.as_bytes()), + memo: v.string("memo").ok(), + msg: v.string("msg").expect_utf8(ERR_FAILED_PARSE.as_bytes()), + } + } +} + +/// storage_balance_of eth-connector call args +#[cfg(feature = "engine")] +#[derive(BorshSerialize, BorshDeserialize)] +pub struct StorageBalanceOfCallArgs { + pub account_id: AccountId, +} + +#[cfg(feature = "engine")] +impl From for StorageBalanceOfCallArgs { + fn from(v: json::JsonValue) -> Self { + Self { + account_id: v + .string("account_id") + .expect_utf8(ERR_FAILED_PARSE.as_bytes()), + } + } +} + +/// storage_deposit eth-connector call args +#[cfg(feature = "engine")] +#[derive(BorshSerialize, BorshDeserialize)] +pub struct StorageDepositCallArgs { + pub account_id: Option, + pub registration_only: Option, +} + +#[cfg(feature = "engine")] +impl From for StorageDepositCallArgs { + fn from(v: json::JsonValue) -> Self { + Self { + account_id: v.string("account_id").ok(), + registration_only: v.bool("registration_only").ok(), + } + } +} + +/// storage_withdraw eth-connector call args +#[cfg(feature = "engine")] +#[derive(BorshSerialize, BorshDeserialize)] +pub struct StorageWithdrawCallArgs { + pub amount: Option, +} + +#[cfg(feature = "engine")] +impl From for StorageWithdrawCallArgs { + fn from(v: json::JsonValue) -> Self { + Self { + amount: v.u128("amount").ok(), + } + } +} + +/// transfer args for json invocation +#[cfg(feature = "engine")] +#[derive(BorshSerialize, BorshDeserialize)] +pub struct TransferCallArgs { + pub receiver_id: AccountId, + pub amount: Balance, + pub memo: Option, +} + +#[cfg(feature = "engine")] +impl From for TransferCallArgs { + fn from(v: json::JsonValue) -> Self { + Self { + receiver_id: v + .string("receiver_id") + .expect_utf8(ERR_FAILED_PARSE.as_bytes()), + amount: v.u128("amount").expect_utf8(ERR_FAILED_PARSE.as_bytes()), + memo: v.string("memo").ok(), + } + } +} + +/// withdraw NEAR eth-connector call args +#[cfg(feature = "engine")] +#[derive(BorshSerialize, BorshDeserialize)] +pub struct WithdrawCallArgs { + pub recipient_address: EthAddress, + pub amount: Balance, +} + +/// balance_of args for json invocation +#[cfg(feature = "engine")] +#[derive(BorshSerialize, BorshDeserialize)] +pub struct BalanceOfCallArgs { + pub account_id: AccountId, +} + +#[cfg(feature = "engine")] +#[derive(BorshSerialize, BorshDeserialize)] +pub struct BalanceOfEthCallArgs { + pub address: EthAddress, +} + +#[cfg(feature = "engine")] +impl From for BalanceOfCallArgs { + fn from(v: json::JsonValue) -> Self { + Self { + account_id: v + .string("account_id") + .expect_utf8(ERR_FAILED_PARSE.as_bytes()), + } + } +} + +#[cfg(feature = "engine")] +#[derive(BorshSerialize, BorshDeserialize)] +pub struct RegisterRelayerCallArgs { + pub address: EthAddress, +} + +#[cfg(feature = "engine")] +#[derive(BorshSerialize, BorshDeserialize)] +pub struct PauseEthConnectorCallArgs { + pub paused_mask: PausedMask, +} + +#[cfg(feature = "engine")] +pub trait ExpectUtf8 { + fn expect_utf8(self, message: &[u8]) -> T; +} + +#[cfg(feature = "engine")] +impl ExpectUtf8 for Option { + fn expect_utf8(self, message: &[u8]) -> T { + match self { + Some(t) => t, + None => sdk::panic_utf8(message), + } + } +} + +#[cfg(feature = "engine")] +impl ExpectUtf8 for core::result::Result { + fn expect_utf8(self, message: &[u8]) -> T { + match self { + Ok(t) => t, + Err(_) => sdk::panic_utf8(message), + } + } +} + +#[cfg(feature = "engine")] +impl From for ResolveTransferCallArgs { + fn from(v: json::JsonValue) -> Self { + Self { + sender_id: v + .string("sender_id") + .expect_utf8(ERR_FAILED_PARSE.as_bytes()), + receiver_id: v + .string("receiver_id") + .expect_utf8(ERR_FAILED_PARSE.as_bytes()), + amount: v.u128("amount").expect_utf8(ERR_FAILED_PARSE.as_bytes()), + } + } +} + #[cfg(test)] mod tests { use super::*; diff --git a/src/precompiles/blake2.rs b/src/precompiles/blake2.rs index e0e8e3d89..d7186a552 100644 --- a/src/precompiles/blake2.rs +++ b/src/precompiles/blake2.rs @@ -15,6 +15,10 @@ mod consts { pub(super) struct Blake2F; +impl Blake2F { + pub(super) const ADDRESS: [u8; 20] = super::make_address(0, 9); +} + impl Precompile for Blake2F { fn required_gas(input: &[u8]) -> Result { let (int_bytes, _) = input.split_at(mem::size_of::()); diff --git a/src/precompiles/bn128.rs b/src/precompiles/bn128.rs index 75dc53596..7b1a15200 100644 --- a/src/precompiles/bn128.rs +++ b/src/precompiles/bn128.rs @@ -41,6 +41,15 @@ mod consts { pub(super) const PAIR_ELEMENT_LEN: usize = 192; } +/// bn128 precompile addresses +pub(super) mod addresses { + use crate::precompiles; + + pub const ADD: [u8; 20] = precompiles::make_address(0, 6); + pub const MUL: [u8; 20] = precompiles::make_address(0, 7); + pub const PAIR: [u8; 20] = precompiles::make_address(0, 8); +} + /// Reads the `x` and `y` points from an input at a given position. fn read_point(input: &[u8], pos: usize) -> Result { use bn::{AffineG1, Fq, Group, G1}; diff --git a/src/precompiles/hash.rs b/src/precompiles/hash.rs index b9467ee7f..e8eb3bb5c 100644 --- a/src/precompiles/hash.rs +++ b/src/precompiles/hash.rs @@ -20,6 +20,10 @@ mod consts { /// SHA256 precompile. pub struct SHA256; +impl SHA256 { + pub(super) const ADDRESS: [u8; 20] = super::make_address(0, 2); +} + impl Precompile for SHA256 { fn required_gas(input: &[u8]) -> Result { Ok( @@ -67,6 +71,10 @@ impl Precompile for SHA256 { /// RIPEMD160 precompile. pub struct RIPEMD160; +impl RIPEMD160 { + pub(super) const ADDRESS: [u8; 20] = super::make_address(0, 3); +} + impl Precompile for RIPEMD160 { fn required_gas(input: &[u8]) -> Result { Ok( diff --git a/src/precompiles/identity.rs b/src/precompiles/identity.rs index bf7db352d..de41884e4 100644 --- a/src/precompiles/identity.rs +++ b/src/precompiles/identity.rs @@ -17,6 +17,10 @@ mod consts { pub struct Identity; +impl Identity { + pub(super) const ADDRESS: [u8; 20] = super::make_address(0, 4); +} + impl Precompile for Identity { fn required_gas(input: &[u8]) -> Result { Ok( diff --git a/src/precompiles/mod.rs b/src/precompiles/mod.rs index 2713a6936..48f0b7073 100644 --- a/src/precompiles/mod.rs +++ b/src/precompiles/mod.rs @@ -3,6 +3,8 @@ mod bn128; mod hash; mod identity; mod modexp; +#[cfg(feature = "exit-precompiles")] +mod native; mod secp256k1; use crate::precompiles::blake2::Blake2F; @@ -10,6 +12,8 @@ use crate::precompiles::bn128::{BN128Add, BN128Mul, BN128Pair}; use crate::precompiles::hash::{RIPEMD160, SHA256}; use crate::precompiles::identity::Identity; use crate::precompiles::modexp::ModExp; +#[cfg(feature = "exit-precompiles")] +use crate::precompiles::native::{ExitToEthereum, ExitToNear}; pub(crate) use crate::precompiles::secp256k1::ecrecover; use crate::precompiles::secp256k1::ECRecover; use crate::prelude::{Address, Vec}; @@ -74,11 +78,14 @@ pub fn homestead_precompiles( None => return Some(PrecompileResult::Err(ExitError::OutOfGas)), }; - match address.to_low_u64_be() { - 1 => Some(ECRecover::run(input, target_gas, context)), - 2 => Some(SHA256::run(input, target_gas, context)), - 3 => Some(RIPEMD160::run(input, target_gas, context)), - // 4 => Some(identity::identity(input, target_gas)), + match address.0 { + ECRecover::ADDRESS => Some(ECRecover::run(input, target_gas, context)), + SHA256::ADDRESS => Some(SHA256::run(input, target_gas, context)), + RIPEMD160::ADDRESS => Some(RIPEMD160::run(input, target_gas, context)), + #[cfg(feature = "exit-precompiles")] + ExitToNear::ADDRESS => Some(ExitToNear::run(input, target_gas, context)), + #[cfg(feature = "exit-precompiles")] + ExitToEthereum::ADDRESS => Some(ExitToEthereum::run(input, target_gas, context)), _ => None, } } @@ -96,15 +103,19 @@ pub fn byzantium_precompiles( None => return Some(PrecompileResult::Err(ExitError::OutOfGas)), }; - match address.to_low_u64_be() { - 1 => Some(ECRecover::run(input, target_gas, context)), - 2 => Some(SHA256::run(input, target_gas, context)), - 3 => Some(RIPEMD160::run(input, target_gas, context)), - 4 => Some(Identity::run(input, target_gas, context)), - 5 => Some(ModExp::::run(input, target_gas, context)), - 6 => Some(BN128Add::::run(input, target_gas, context)), - 7 => Some(BN128Mul::::run(input, target_gas, context)), - 8 => Some(BN128Pair::::run(input, target_gas, context)), + match address.0 { + ECRecover::ADDRESS => Some(ECRecover::run(input, target_gas, context)), + SHA256::ADDRESS => Some(SHA256::run(input, target_gas, context)), + RIPEMD160::ADDRESS => Some(RIPEMD160::run(input, target_gas, context)), + Identity::ADDRESS => Some(Identity::run(input, target_gas, context)), + modexp::ADDRESS => Some(ModExp::::run(input, target_gas, context)), + bn128::addresses::ADD => Some(BN128Add::::run(input, target_gas, context)), + bn128::addresses::MUL => Some(BN128Mul::::run(input, target_gas, context)), + bn128::addresses::PAIR => Some(BN128Pair::::run(input, target_gas, context)), + #[cfg(feature = "exit-precompiles")] + ExitToNear::ADDRESS => Some(ExitToNear::run(input, target_gas, context)), + #[cfg(feature = "exit-precompiles")] + ExitToEthereum::ADDRESS => Some(ExitToEthereum::run(input, target_gas, context)), _ => None, } } @@ -122,17 +133,20 @@ pub fn istanbul_precompiles( None => return Some(PrecompileResult::Err(ExitError::OutOfGas)), }; - match address.to_low_u64_be() { - 1 => Some(ECRecover::run(input, target_gas, context)), - 2 => Some(SHA256::run(input, target_gas, context)), - 3 => Some(RIPEMD160::run(input, target_gas, context)), - 4 => Some(Identity::run(input, target_gas, context)), - 5 => Some(ModExp::::run(input, target_gas, context)), - 6 => Some(BN128Add::::run(input, target_gas, context)), - 7 => Some(BN128Mul::::run(input, target_gas, context)), - 8 => Some(BN128Pair::::run(input, target_gas, context)), - 9 => Some(Blake2F::run(input, target_gas, context)), - // Not supported. + match address.0 { + ECRecover::ADDRESS => Some(ECRecover::run(input, target_gas, context)), + SHA256::ADDRESS => Some(SHA256::run(input, target_gas, context)), + RIPEMD160::ADDRESS => Some(RIPEMD160::run(input, target_gas, context)), + Identity::ADDRESS => Some(Identity::run(input, target_gas, context)), + modexp::ADDRESS => Some(ModExp::::run(input, target_gas, context)), + bn128::addresses::ADD => Some(BN128Add::::run(input, target_gas, context)), + bn128::addresses::MUL => Some(BN128Mul::::run(input, target_gas, context)), + bn128::addresses::PAIR => Some(BN128Pair::::run(input, target_gas, context)), + Blake2F::ADDRESS => Some(Blake2F::run(input, target_gas, context)), + #[cfg(feature = "exit-precompiles")] + ExitToNear::ADDRESS => Some(ExitToNear::run(input, target_gas, context)), + #[cfg(feature = "exit-precompiles")] + ExitToEthereum::ADDRESS => Some(ExitToEthereum::run(input, target_gas, context)), _ => None, } } @@ -150,17 +164,99 @@ pub fn berlin_precompiles( None => return Some(PrecompileResult::Err(ExitError::OutOfGas)), }; - match address.to_low_u64_be() { - 1 => Some(ECRecover::run(input, target_gas, context)), - 2 => Some(SHA256::run(input, target_gas, context)), - 3 => Some(RIPEMD160::run(input, target_gas, context)), - 4 => Some(Identity::run(input, target_gas, context)), - 5 => Some(ModExp::::run(input, target_gas, context)), // TODO gas changes - 6 => Some(BN128Add::::run(input, target_gas, context)), - 7 => Some(BN128Mul::::run(input, target_gas, context)), - 8 => Some(BN128Pair::::run(input, target_gas, context)), - 9 => Some(Blake2F::run(input, target_gas, context)), - // Not supported. + match address.0 { + ECRecover::ADDRESS => Some(ECRecover::run(input, target_gas, context)), + SHA256::ADDRESS => Some(SHA256::run(input, target_gas, context)), + RIPEMD160::ADDRESS => Some(RIPEMD160::run(input, target_gas, context)), + Identity::ADDRESS => Some(Identity::run(input, target_gas, context)), + modexp::ADDRESS => Some(ModExp::::run(input, target_gas, context)), // TODO gas changes + bn128::addresses::ADD => Some(BN128Add::::run(input, target_gas, context)), + bn128::addresses::MUL => Some(BN128Mul::::run(input, target_gas, context)), + bn128::addresses::PAIR => Some(BN128Pair::::run(input, target_gas, context)), + Blake2F::ADDRESS => Some(Blake2F::run(input, target_gas, context)), + #[cfg(feature = "exit-precompiles")] + ExitToNear::ADDRESS => Some(ExitToNear::run(input, target_gas, context)), + #[cfg(feature = "exit-precompiles")] + ExitToEthereum::ADDRESS => Some(ExitToEthereum::run(input, target_gas, context)), _ => None, } } + +/// const fn for making an address by concatenating the bytes from two given numbers, +/// Note that 32 + 128 = 160 = 20 bytes (the length of an address). This function is used +/// as a convenience for specifying the addresses of the various precompiles. +const fn make_address(x: u32, y: u128) -> [u8; 20] { + let x_bytes = x.to_be_bytes(); + let y_bytes = y.to_be_bytes(); + [ + x_bytes[0], + x_bytes[1], + x_bytes[2], + x_bytes[3], + y_bytes[0], + y_bytes[1], + y_bytes[2], + y_bytes[3], + y_bytes[4], + y_bytes[5], + y_bytes[6], + y_bytes[7], + y_bytes[8], + y_bytes[9], + y_bytes[10], + y_bytes[11], + y_bytes[12], + y_bytes[13], + y_bytes[14], + y_bytes[15], + ] +} + +#[cfg(test)] +mod tests { + use rand::Rng; + + #[test] + fn test_precompile_addresses() { + assert_eq!(super::secp256k1::ECRecover::ADDRESS, u8_to_address(1)); + assert_eq!(super::hash::SHA256::ADDRESS, u8_to_address(2)); + assert_eq!(super::hash::RIPEMD160::ADDRESS, u8_to_address(3)); + assert_eq!(super::identity::Identity::ADDRESS, u8_to_address(4)); + assert_eq!(super::modexp::ADDRESS, u8_to_address(5)); + assert_eq!(super::bn128::addresses::ADD, u8_to_address(6)); + assert_eq!(super::bn128::addresses::MUL, u8_to_address(7)); + assert_eq!(super::bn128::addresses::PAIR, u8_to_address(8)); + assert_eq!(super::blake2::Blake2F::ADDRESS, u8_to_address(9)); + } + + #[test] + fn test_make_address() { + for i in 0..u8::MAX { + assert_eq!(super::make_address(0, i as u128), u8_to_address(i)); + } + + let mut rng = rand::thread_rng(); + for _ in 0..u8::MAX { + let address: [u8; 20] = rng.gen(); + let (x, y) = split_address(address); + assert_eq!(address, super::make_address(x, y)) + } + } + + fn u8_to_address(x: u8) -> [u8; 20] { + let mut bytes = [0u8; 20]; + bytes[19] = x; + bytes + } + + // Inverse function of `super::make_address`. + fn split_address(a: [u8; 20]) -> (u32, u128) { + let mut x_bytes = [0u8; 4]; + let mut y_bytes = [0u8; 16]; + + x_bytes.copy_from_slice(&a[0..4]); + y_bytes.copy_from_slice(&a[4..20]); + + (u32::from_be_bytes(x_bytes), u128::from_be_bytes(y_bytes)) + } +} diff --git a/src/precompiles/modexp.rs b/src/precompiles/modexp.rs index 584e03419..102caf0ae 100644 --- a/src/precompiles/modexp.rs +++ b/src/precompiles/modexp.rs @@ -3,6 +3,8 @@ use crate::prelude::{PhantomData, Vec, U256}; use evm::{Context, ExitError, ExitSucceed}; use num::BigUint; +pub(super) const ADDRESS: [u8; 20] = super::make_address(0, 5); + pub(super) struct ModExp(PhantomData); impl ModExp { diff --git a/src/precompiles/native.rs b/src/precompiles/native.rs new file mode 100644 index 000000000..3163e4698 --- /dev/null +++ b/src/precompiles/native.rs @@ -0,0 +1,252 @@ +use evm::{Context, ExitError, ExitSucceed}; + +use super::{Precompile, PrecompileResult}; +use crate::prelude::Vec; +#[cfg(feature = "exit-precompiles")] +use crate::{ + prelude::{is_valid_account_id, Cow, String, U256}, + types::AccountId, +}; + +#[cfg(feature = "exit-precompiles")] +mod costs { + use crate::types::Gas; + + // TODO(#51): Determine the correct amount of gas + pub(super) const EXIT_TO_NEAR_GAS: Gas = 0; + + // TODO(#51): Determine the correct amount of gas + pub(super) const EXIT_TO_ETHEREUM_GAS: Gas = 0; + + // TODO(#51): Determine the correct amount of gas + pub(super) const FT_TRANSFER_GAS: Gas = 100_000_000_000_000; + + // TODO(#51): Determine the correct amount of gas + pub(super) const WITHDRAWAL_GAS: Gas = 100_000_000_000_000; +} + +/// Get the current nep141 token associated with the current erc20 token. +/// This will fail is none is associated. +#[cfg(feature = "exit-precompiles")] +fn get_nep141_from_erc20(_erc20_token: &[u8]) -> Vec { + // TODO(#51): Already implemented + Vec::new() +} + +pub struct ExitToNear; //TransferEthToNear + +impl ExitToNear { + /// Exit to NEAR precompile address + /// + /// Address: `0xe9217bc70b7ed1f598ddd3199e80b093fa71124f` + /// This address is computed as: `&keccak("exitToNear")[12..]` + pub(super) const ADDRESS: [u8; 20] = + super::make_address(0xe9217bc7, 0x0b7ed1f598ddd3199e80b093fa71124f); +} + +impl Precompile for ExitToNear { + fn required_gas(_input: &[u8]) -> Result { + Ok(costs::EXIT_TO_NEAR_GAS) + } + + #[cfg(not(feature = "exit-precompiles"))] + fn run(input: &[u8], target_gas: u64, _context: &Context) -> PrecompileResult { + if Self::required_gas(input)? > target_gas { + return Err(ExitError::OutOfGas); + } + + Ok((ExitSucceed::Returned, Vec::new(), 0)) + } + + #[cfg(feature = "exit-precompiles")] + fn run(input: &[u8], target_gas: u64, context: &Context) -> PrecompileResult { + if Self::required_gas(input)? > target_gas { + return Err(ExitError::OutOfGas); + } + + let (nep141_address, args) = if context.apparent_value != U256::from(0) { + // ETH transfer + // + // Input slice format: + // recipient_account_id (bytes) - the NEAR recipient account which will receive NEP-141 ETH tokens + + if is_valid_account_id(input) { + ( + crate::sdk::current_account_id(), + crate::prelude::format!( + r#"{{"receiver_id": "{}", "amount": "{}"}}"#, + String::from_utf8(input.to_vec()).unwrap(), + context.apparent_value.as_u128() + ), + ) + } else { + return Err(ExitError::Other(Cow::from( + "ERR_INVALID_RECEIVER_ACCOUNT_ID", + ))); + } + } else { + // ERC20 transfer + // + // This precompile branch is expected to be called from the ERC20 burn function\ + // + // Input slice format: + // amount (U256 le bytes) - the amount that was burned + // recipient_account_id (bytes) - the NEAR recipient account which will receive NEP-141 tokens + + let nep141_address = get_nep141_from_erc20(context.caller.as_bytes()); + + let mut input_mut = input; + let amount = U256::from_big_endian(&input_mut[..32]).as_u128(); + input_mut = &input_mut[32..]; + + // TODO: You have to charge caller's account balance for this transfer. + + if is_valid_account_id(input_mut) { + let receiver_account_id: AccountId = String::from_utf8(input_mut.to_vec()).unwrap(); + ( + nep141_address, + crate::prelude::format!( + r#"{{"receiver_id": "{}", "amount": "{}"}}"#, + receiver_account_id, + amount + ), + ) + } else { + return Err(ExitError::Other(Cow::from( + "ERR_INVALID_RECEIVER_ACCOUNT_ID", + ))); + } + }; + + let promise0 = crate::sdk::promise_create( + &nep141_address, + b"ft_transfer", + args.as_bytes(), + 1, + costs::FT_TRANSFER_GAS, + ); + + crate::sdk::promise_return(promise0); + + Ok((ExitSucceed::Returned, Vec::new(), 0)) + } +} + +pub struct ExitToEthereum; + +impl ExitToEthereum { + /// Exit to Ethereum precompile address + /// + /// Address: `0xb0bd02f6a392af548bdf1cfaee5dfa0eefcc8eab` + /// This address is computed as: `&keccak("exitToEthereum")[12..]` + pub(super) const ADDRESS: [u8; 20] = + super::make_address(0xb0bd02f6, 0xa392af548bdf1cfaee5dfa0eefcc8eab); +} + +impl Precompile for ExitToEthereum { + fn required_gas(_input: &[u8]) -> Result { + Ok(costs::EXIT_TO_ETHEREUM_GAS) + } + + #[cfg(not(feature = "exit-precompiles"))] + fn run(input: &[u8], target_gas: u64, _context: &Context) -> PrecompileResult { + if Self::required_gas(input)? > target_gas { + return Err(ExitError::OutOfGas); + } + + Ok((ExitSucceed::Returned, Vec::new(), 0)) + } + + #[cfg(feature = "exit-precompiles")] + fn run(input: &[u8], target_gas: u64, context: &Context) -> PrecompileResult { + if Self::required_gas(input)? > target_gas { + return Err(ExitError::OutOfGas); + } + + let (nep141_address, serialized_args) = if context.apparent_value != U256::from(0) { + // ETH transfer + // + // Input slice format: + // eth_recipient (20 bytes) - the address of recipient which will receive ETH on Ethereum + + let eth_recipient: String = hex::encode(input); + + if eth_recipient.len() == 20 { + ( + crate::sdk::current_account_id(), + crate::prelude::format!( + r#"{{"amount": "{}", "recipient": "{}"}}"#, + context.apparent_value.as_u128(), + eth_recipient + ), + ) + } else { + return Err(ExitError::Other(Cow::from("ERR_INVALID_RECIPIENT_ADDRESS"))); + } + } else { + // ERC-20 transfer + // + // This precompile branch is expected to be called from the ERC20 withdraw function + // (or burn function with some flag provided that this is expected to be withdrawn) + // + // Input slice format: + // amount (U256 le bytes) - the amount that was burned + // eth_recipient (20 bytes) - the address of recipient which will receive ETH on Ethereum + + let nep141_address = get_nep141_from_erc20(context.caller.as_bytes()); + + let mut input_mut = input; + + let amount = U256::from_big_endian(&input_mut[..32]).as_u128(); + input_mut = &input_mut[32..]; + + // TODO: Charge the caller's account balance? + + if input_mut.len() == 20 { + // Parse ethereum address in hex + let eth_recipient: String = hex::encode(input_mut.to_vec()); + + ( + nep141_address, + crate::prelude::format!( + r#"{{"amount": "{}", "recipient": "{}"}}"#, + amount, + eth_recipient + ), + ) + } else { + return Err(ExitError::Other(Cow::from("ERR_INVALID_RECIPIENT_ADDRESS"))); + } + }; + + let promise0 = crate::sdk::promise_create( + &nep141_address, + b"withdraw", + serialized_args.as_bytes(), + 1, + costs::WITHDRAWAL_GAS, + ); + + crate::sdk::promise_return(promise0); + + Ok((ExitSucceed::Returned, Vec::new(), 0)) + } +} + +#[cfg(test)] +mod tests { + use super::{ExitToEthereum, ExitToNear}; + use crate::types::near_account_to_evm_address; + + #[test] + fn test_precompile_id() { + assert_eq!( + ExitToEthereum::ADDRESS, + near_account_to_evm_address("exitToEthereum".as_bytes()).0 + ); + assert_eq!( + ExitToNear::ADDRESS, + near_account_to_evm_address("exitToNear".as_bytes()).0 + ); + } +} diff --git a/src/precompiles/secp256k1.rs b/src/precompiles/secp256k1.rs index 5a88f8c62..6cc2fc6f4 100644 --- a/src/precompiles/secp256k1.rs +++ b/src/precompiles/secp256k1.rs @@ -41,6 +41,10 @@ pub(crate) fn ecrecover(hash: H256, signature: &[u8]) -> Result Result { Ok(costs::ECRECOVER_BASE) diff --git a/src/prelude.rs b/src/prelude.rs index a81da0d5d..1abd19a5c 100644 --- a/src/prelude.rs +++ b/src/prelude.rs @@ -4,18 +4,38 @@ pub use alloc::{ borrow::{Cow, Cow::*}, boxed::Box, collections::BTreeMap as HashMap, - fmt, + fmt, format, string::String, string::ToString, vec, vec::Vec, }; #[cfg(not(feature = "std"))] -pub use core::{convert::TryInto, marker::PhantomData, mem}; +pub use core::{ + cmp::Ordering, + convert::TryInto, + marker::PhantomData, + mem, + ops::{Add, Sub}, +}; #[cfg(feature = "std")] pub use std::{ - borrow::Cow::Borrowed, borrow::ToOwned, boxed::Box, collections::HashMap, convert::TryInto, - error::Error, fmt, marker::PhantomData, mem, string::String, string::ToString, vec, vec::Vec, + borrow::Cow, + borrow::Cow::Borrowed, + borrow::ToOwned, + boxed::Box, + cmp::Ordering, + collections::HashMap, + convert::TryInto, + error::Error, + fmt, format, + marker::PhantomData, + mem, + ops::{Add, Sub}, + string::String, + string::ToString, + vec, + vec::Vec, }; pub use primitive_types::{H160, H256, U256}; @@ -27,3 +47,41 @@ pub type Address = H160; pub fn Address(input: [u8; 20]) -> Address { H160(input) } + +/// The minimum length of a valid account ID. +const MIN_ACCOUNT_ID_LEN: u64 = 2; +/// The maximum length of a valid account ID. +const MAX_ACCOUNT_ID_LEN: u64 = 64; + +/// Returns `true` if the given account ID is valid and `false` otherwise. +/// +/// Taken from near-sdk-rs: +/// (https://github.com/near/near-sdk-rs/blob/42f62384c3acd024829501ee86e480917da03896/near-sdk/src/environment/env.rs#L816-L843) +pub fn is_valid_account_id(account_id: &[u8]) -> bool { + if (account_id.len() as u64) < MIN_ACCOUNT_ID_LEN + || (account_id.len() as u64) > MAX_ACCOUNT_ID_LEN + { + return false; + } + + // NOTE: We don't want to use Regex here, because it requires extra time to compile it. + // The valid account ID regex is /^(([a-z\d]+[-_])*[a-z\d]+\.)*([a-z\d]+[-_])*[a-z\d]+$/ + // Instead the implementation is based on the previous character checks. + + // We can safely assume that last char was a separator. + let mut last_char_is_separator = true; + + for c in account_id { + let current_char_is_separator = match *c { + b'a'..=b'z' | b'0'..=b'9' => false, + b'-' | b'_' | b'.' => true, + _ => return false, + }; + if current_char_is_separator && last_char_is_separator { + return false; + } + last_char_is_separator = current_char_is_separator; + } + // The account can't end as separator. + !last_char_is_separator +} diff --git a/src/prover.rs b/src/prover.rs new file mode 100644 index 000000000..864a66417 --- /dev/null +++ b/src/prover.rs @@ -0,0 +1,265 @@ +use super::prelude::*; +use super::sdk; +use crate::engine::Engine; +use crate::log_entry::LogEntry; +use crate::precompiles::ecrecover; +use crate::types::{AccountId, EthAddress}; +use alloc::{format, vec::Vec}; +use borsh::{BorshDeserialize, BorshSerialize}; +use ethabi::{Bytes, Event, EventParam, Hash, Log, RawLog, Token}; + +/// Validate Etherium address from string and return EthAddress +#[allow(dead_code)] +pub fn validate_eth_address(address: String) -> EthAddress { + let data = hex::decode(address).expect("ETH_ADDRESS_FAILED"); + assert_eq!(data.len(), 20, "ETH_WRONG_ADDRESS_LENGTH"); + let mut result = [0u8; 20]; + result.copy_from_slice(&data); + result +} + +/// Encodes vector of tokens using non-standard Packed mode into ABI.encodePacked() compliant vector of bytes. +pub fn encode_packed(tokens: &[Token]) -> Bytes { + tokens.iter().flat_map(encode_token_packed).collect() +} + +fn encode_token_packed(token: &Token) -> Vec { + match *token { + Token::Address(ref address) => { + let mut padded = [0u8; 32]; + padded[12..].copy_from_slice(address.as_ref()); + padded[..].to_vec() + } + Token::Bytes(ref bytes) => bytes.to_vec(), + Token::String(ref s) => s.as_bytes().to_vec(), + Token::FixedBytes(ref bytes) => bytes.to_vec(), + Token::Int(int) => { + let data: [u8; 32] = int.into(); + data[..].to_vec() + } + Token::Uint(uint) => { + let data: [u8; 32] = uint.into(); + data[..].to_vec() + } + Token::Bool(b) => { + vec![b.into()] + } + Token::Array(_) | Token::FixedArray(_) | Token::Tuple(_) => { + panic!("These token types are not supported in packed mode"); + } + } +} + +#[derive(Default, BorshDeserialize, BorshSerialize, Clone)] +pub struct Proof { + pub log_index: u64, + pub log_entry_data: Vec, + pub receipt_index: u64, + pub receipt_data: Vec, + pub header_data: Vec, + pub proof: Vec>, +} + +#[allow(dead_code)] +impl Proof { + pub fn get_key(&self) -> String { + let mut data = self.log_index.try_to_vec().unwrap(); + data.extend(self.receipt_index.try_to_vec().unwrap()); + data.extend(self.header_data.clone()); + sdk::sha256(&data[..]) + .0 + .iter() + .map(|n| n.to_string()) + .collect() + } +} + +pub type EventParams = Vec; + +/// Ethereum event +pub struct EthEvent { + pub eth_custodian_address: EthAddress, + pub log: Log, +} + +#[allow(dead_code)] +impl EthEvent { + /// Get Ethereum event from `log_entry_data` + pub fn fetch_log_entry_data(name: &str, params: EventParams, data: &[u8]) -> Self { + let event = Event { + name: name.to_string(), + inputs: params, + anonymous: false, + }; + let log_entry: LogEntry = rlp::decode(data).expect("INVALID_RLP"); + let eth_custodian_address = log_entry.address.0; + let topics = log_entry.topics.iter().map(|h| Hash::from(h.0)).collect(); + + let raw_log = RawLog { + topics, + data: log_entry.data, + }; + let log = event.parse_log(raw_log).expect("Failed to parse event log"); + + Self { + eth_custodian_address, + log, + } + } +} + +const EIP_712_MSG_PREFIX: &[u8] = &[0x19, 0x01]; +const EIP_712_DOMAIN_TYPEHASH: &str = + "EIP712Domain(string name,string version,uint256 chainId,address verifyingContract)"; +const AURORA_DOMAIN_NAME: &str = "Aurora-Engine domain"; +const AURORA_DOMAIN_VERSION: &str = "1.0"; +const WITHDRAW_FROM_EVM_TYPEHASH: &str = + "WithdrawFromEVMRequest(address ethRecipient,uint256 amount,address verifyingContract)"; +const TRANSFER_FROM_EVM_TO_NEAR_TYPEHASH: &str = + "TransferFromEVMtoNearRequest(string nearRecipient,uint256 amount,uint256 fee)"; + +enum EIP712Recipient { + Eth(EthAddress), + Near(AccountId), +} + +/// Encode EIP712 withdraw message data +fn encode_eip712( + eth_recipient: EIP712Recipient, + amount: U256, + custodian_address: EthAddress, + type_hash: &str, +) -> H256 { + let chain_id = U256::from(Engine::get_state().unwrap().chain_id); + + let domain_separator_encoded = encode_packed(&[ + Token::FixedBytes( + sdk::keccak(&encode_packed(&[Token::Bytes( + EIP_712_DOMAIN_TYPEHASH.as_bytes().to_vec(), + )])) + .as_bytes() + .to_vec(), + ), + Token::FixedBytes(encode_packed(&[ + // Domain + Token::Bytes( + sdk::keccak(AURORA_DOMAIN_NAME.as_bytes()) + .as_bytes() + .to_vec(), + ), + // Version + Token::Bytes( + sdk::keccak(AURORA_DOMAIN_VERSION.as_bytes()) + .as_bytes() + .to_vec(), + ), + // ChainID + Token::Uint(chain_id), + // Custodian address + Token::Address(H160::from(custodian_address)), + ])), + ]); + sdk::log(&format!( + "Domain_separator encoded: {}", + hex::encode(domain_separator_encoded.clone()) + )); + + let domain_separator = sdk::keccak(&domain_separator_encoded); + sdk::log(&format!( + "Domain_separator hash: {}", + hex::encode(domain_separator) + )); + + let token_address = match eth_recipient { + EIP712Recipient::Eth(eth_recipient) => Token::Address(H160::from(eth_recipient)), + EIP712Recipient::Near(account_id) => Token::String(account_id), + }; + let withdraw_from_evm_struct_encoded = encode_packed(&[ + Token::FixedBytes( + sdk::keccak(&encode_packed(&[Token::Bytes( + type_hash.as_bytes().to_vec(), + )])) + .as_bytes() + .to_vec(), + ), + Token::FixedBytes(encode_packed(&[ + token_address, + Token::Uint(amount), + Token::Address(H160::from(custodian_address)), + ])), + ]); + sdk::log(&format!( + "WithdrawFromEVM struct encoded: {}", + hex::encode(withdraw_from_evm_struct_encoded.clone()), + )); + + let withdraw_from_evm_struct_hash = sdk::keccak(&withdraw_from_evm_struct_encoded); + sdk::log(&format!( + "WithdrawFromEVM struct hash: {}", + hex::encode(withdraw_from_evm_struct_hash) + )); + + let digest_encoded = encode_packed(&[ + Token::Bytes(EIP_712_MSG_PREFIX.to_vec()), + Token::FixedBytes(domain_separator.as_bytes().to_vec()), + Token::FixedBytes(withdraw_from_evm_struct_hash.as_bytes().to_vec()), + ]); + sdk::log(&format!( + "digest_encoded: {}", + hex::encode(digest_encoded.clone()) + )); + + let digest = sdk::keccak(&digest_encoded); + sdk::log(&format!("digest: {}", hex::encode(digest))); + digest +} + +#[allow(dead_code)] +pub fn verify_withdraw_eip712( + sender: EthAddress, + eth_recipient: EthAddress, + custodian_address: EthAddress, + amount: U256, + eip712_signature: Vec, +) -> bool { + let res = encode_eip712( + EIP712Recipient::Eth(eth_recipient), + amount, + custodian_address, + WITHDRAW_FROM_EVM_TYPEHASH, + ); + let withdraw_msg_signer = ecrecover(res, &eip712_signature[..]).unwrap(); + sdk::log(&format!("sender: {}", hex::encode(sender))); + sdk::log(&format!("ecrecover: {}", hex::encode(withdraw_msg_signer))); + sdk::log(&format!( + "ecrecover: {}", + H160::from(sender) == withdraw_msg_signer + )); + + H160::from(sender) == withdraw_msg_signer +} + +#[allow(dead_code)] +pub fn verify_transfer_eip712( + sender: EthAddress, + near_recipient: AccountId, + custodian_address: EthAddress, + amount: U256, + eip712_signature: Vec, +) -> bool { + let res = encode_eip712( + EIP712Recipient::Near(near_recipient), + amount, + custodian_address, + TRANSFER_FROM_EVM_TO_NEAR_TYPEHASH, + ); + let withdraw_msg_signer = ecrecover(res, &eip712_signature[..]).unwrap(); + sdk::log(&format!("sender: {}", hex::encode(sender))); + sdk::log(&format!("ecrecover: {}", hex::encode(withdraw_msg_signer))); + sdk::log(&format!( + "ecrecover: {}", + H160::from(sender) == withdraw_msg_signer + )); + + H160::from(sender) == withdraw_msg_signer +} diff --git a/src/sdk.rs b/src/sdk.rs index 467f7482f..1d21008e6 100644 --- a/src/sdk.rs +++ b/src/sdk.rs @@ -1,9 +1,13 @@ -use crate::prelude::{vec, String, Vec, H256}; +use crate::prelude::{vec, Vec, H256}; +use crate::types::PromiseResult; use crate::types::STORAGE_PRICE_PER_BYTE; use borsh::{BorshDeserialize, BorshSerialize}; -mod exports { +const READ_STORAGE_REGISTER_ID: u64 = 0; +const INPUT_REGISTER_ID: u64 = 0; +const GAS_FOR_STATE_MIGRATION: u64 = 100_000_000_000_000; +mod exports { #[allow(unused)] extern "C" { // ############# @@ -15,8 +19,8 @@ mod exports { // # Context API # // ############### pub(crate) fn current_account_id(register_id: u64); - fn signer_account_id(register_id: u64); - fn signer_account_pk(register_id: u64); + pub(crate) fn signer_account_id(register_id: u64); + pub(crate) fn signer_account_pk(register_id: u64); pub(crate) fn predecessor_account_id(register_id: u64); pub(crate) fn input(register_id: u64); // TODO #1903 fn block_height() -> u64; @@ -82,7 +86,7 @@ mod exports { code_len: u64, code_ptr: u64, ); - fn promise_batch_action_function_call( + pub(crate) fn promise_batch_action_function_call( promise_index: u64, method_name_len: u64, method_name_ptr: u64, @@ -156,24 +160,30 @@ mod exports { } } -#[allow(dead_code)] pub fn read_input() -> Vec { unsafe { - exports::input(0); - let bytes: Vec = vec![0; exports::register_len(0) as usize]; - exports::read_register(0, bytes.as_ptr() as *const u64 as u64); + exports::input(INPUT_REGISTER_ID); + let bytes: Vec = vec![0; exports::register_len(INPUT_REGISTER_ID) as usize]; + exports::read_register(INPUT_REGISTER_ID, bytes.as_ptr() as *const u64 as u64); bytes } } -#[allow(dead_code)] -pub fn read_input_arr20() -> [u8; 20] { +pub(crate) fn read_input_borsh() -> Result { + let bytes = read_input(); + T::try_from_slice(&bytes).map_err(|_| ArgParseErr) +} + +pub(crate) fn read_input_arr20() -> Result<[u8; 20], IncorrectInputLength> { unsafe { - exports::input(0); - let bytes = [0u8; 20]; - // TODO: Is it fine to not check the length of the input register here? - exports::read_register(0, bytes.as_ptr() as *const u64 as u64); - bytes + exports::input(INPUT_REGISTER_ID); + if exports::register_len(INPUT_REGISTER_ID) == 20 { + let bytes = [0u8; 20]; + exports::read_register(INPUT_REGISTER_ID, bytes.as_ptr() as *const u64 as u64); + Ok(bytes) + } else { + Err(IncorrectInputLength) + } } } @@ -186,7 +196,6 @@ pub fn read_input_and_store(key: &[u8]) { } } -#[allow(dead_code)] pub fn return_output(value: &[u8]) { unsafe { exports::value_return(value.len() as u64, value.as_ptr() as u64); @@ -195,11 +204,25 @@ pub fn return_output(value: &[u8]) { #[allow(dead_code)] pub fn read_storage(key: &[u8]) -> Option> { + read_storage_len(key).map(|value_size| unsafe { + let bytes = vec![0u8; value_size]; + exports::read_register( + READ_STORAGE_REGISTER_ID, + bytes.as_ptr() as *const u64 as u64, + ); + bytes + }) +} + +pub fn read_storage_len(key: &[u8]) -> Option { unsafe { - if exports::storage_read(key.len() as u64, key.as_ptr() as u64, 0) == 1 { - let bytes: Vec = vec![0u8; exports::register_len(0) as usize]; - exports::read_register(0, bytes.as_ptr() as *const u64 as u64); - Some(bytes) + if exports::storage_read( + key.len() as u64, + key.as_ptr() as u64, + READ_STORAGE_REGISTER_ID, + ) == 1 + { + Some(exports::register_len(READ_STORAGE_REGISTER_ID) as usize) } else { None } @@ -207,20 +230,18 @@ pub fn read_storage(key: &[u8]) -> Option> { } /// Read u64 from storage at given key. -pub fn read_u64(key: &[u8]) -> Option { - unsafe { - if exports::storage_read(key.len() as u64, key.as_ptr() as u64, 0) == 1 { +pub(crate) fn read_u64(key: &[u8]) -> Option> { + read_storage_len(key).map(|value_size| unsafe { + if value_size == 8 { let result = [0u8; 8]; - // TODO: Are you sure the register length is correct? - exports::read_register(0, result.as_ptr() as _); - Some(u64::from_le_bytes(result)) + exports::read_register(READ_STORAGE_REGISTER_ID, result.as_ptr() as _); + Ok(u64::from_le_bytes(result)) } else { - None + Err(InvalidU64) } - } + }) } -#[allow(dead_code)] pub fn write_storage(key: &[u8], value: &[u8]) { unsafe { exports::storage_write( @@ -233,19 +254,16 @@ pub fn write_storage(key: &[u8], value: &[u8]) { } } -#[allow(dead_code)] pub fn remove_storage(key: &[u8]) { unsafe { exports::storage_remove(key.len() as u64, key.as_ptr() as u64, 0); } } -#[allow(dead_code)] pub fn block_timestamp() -> u64 { unsafe { exports::block_timestamp() } } -#[allow(dead_code)] pub fn block_index() -> u64 { unsafe { exports::block_index() } } @@ -255,7 +273,6 @@ pub fn panic() { unsafe { exports::panic() } } -#[allow(dead_code)] pub fn panic_utf8(bytes: &[u8]) -> ! { unsafe { exports::panic_utf8(bytes.len() as u64, bytes.as_ptr() as u64); @@ -270,7 +287,6 @@ pub fn log_utf8(bytes: &[u8]) { } } -#[allow(dead_code)] pub fn predecessor_account_id() -> Vec { unsafe { exports::predecessor_account_id(1); @@ -281,7 +297,6 @@ pub fn predecessor_account_id() -> Vec { } /// Calls environment sha256 on given input. -#[allow(dead_code)] pub fn sha256(input: &[u8]) -> H256 { unsafe { exports::sha256(input.len() as u64, input.as_ptr() as u64, 1); @@ -292,7 +307,6 @@ pub fn sha256(input: &[u8]) -> H256 { } /// Calls environment keccak256 on given input. -#[allow(dead_code)] pub fn keccak(input: &[u8]) -> H256 { unsafe { exports::keccak256(input.len() as u64, input.as_ptr() as u64, 1); @@ -302,14 +316,6 @@ pub fn keccak(input: &[u8]) -> H256 { } } -/// Calls environment panic with data encoded in hex as panic message. -#[allow(dead_code)] -pub fn panic_hex(data: &[u8]) -> ! { - let message = crate::types::bytes_to_hex(data).into_bytes(); - unsafe { exports::panic_utf8(message.len() as _, message.as_ptr() as _) } - unreachable!() -} - /// Returns account id of the current account. pub fn current_account_id() -> Vec { unsafe { @@ -329,21 +335,19 @@ pub fn self_deploy(code_key: &[u8]) { let promise_id = exports::promise_batch_create(u64::MAX as _, 0); // Remove code from storage and store it in register 1. exports::storage_remove(code_key.len() as _, code_key.as_ptr() as _, 1); - exports::promise_batch_action_deploy_contract(promise_id, u64::MAX as _, 1); - // TODO: Call upgrade on the same promise to make sure the state has migrated successfully. - // Otherwise, you may have to handle non-latest state in every other method call, which might be inefficient. + exports::promise_batch_action_deploy_contract(promise_id, u64::MAX, 1); + promise_batch_action_function_call( + promise_id, + b"state_migration", + &[], + 0, + GAS_FOR_STATE_MIGRATION, + ) } } -#[allow(dead_code)] -pub fn save_contract(key: &str, data: &T) { - write_storage(key.as_bytes(), &data.try_to_vec().unwrap()[..]); -} - -#[allow(dead_code)] -pub fn get_contract_data(key: &str) -> T { - let data = read_storage(key.as_bytes()).expect("Failed read storage"); - T::try_from_slice(&data[..]).unwrap() +pub fn save_contract(key: &[u8], data: &T) { + write_storage(key, &data.try_to_vec().unwrap()[..]); } #[allow(dead_code)] @@ -351,25 +355,18 @@ pub fn log(data: &str) { log_utf8(data.as_bytes()) } -#[allow(dead_code)] -pub fn storage_usage() -> u64 { - unsafe { exports::storage_usage() } -} - -#[allow(dead_code)] +#[allow(unused)] pub fn prepaid_gas() -> u64 { unsafe { exports::prepaid_gas() } } -#[allow(dead_code)] pub fn promise_create( - account_id: String, + account_id: &[u8], method_name: &[u8], arguments: &[u8], amount: u128, gas: u64, ) -> u64 { - let account_id = account_id.as_bytes(); unsafe { exports::promise_create( account_id.len() as _, @@ -384,16 +381,14 @@ pub fn promise_create( } } -#[allow(dead_code)] pub fn promise_then( promise_idx: u64, - account_id: String, + account_id: &[u8], method_name: &[u8], arguments: &[u8], amount: u128, gas: u64, ) -> u64 { - let account_id = account_id.as_bytes(); unsafe { exports::promise_then( promise_idx, @@ -409,19 +404,17 @@ pub fn promise_then( } } -#[allow(dead_code)] pub fn promise_return(promise_idx: u64) { unsafe { exports::promise_return(promise_idx); } } -#[allow(dead_code)] pub fn promise_results_count() -> u64 { unsafe { exports::promise_results_count() } } -/*pub fn promise_result(result_idx: u64) -> PromiseResult { +pub fn promise_result(result_idx: u64) -> PromiseResult { unsafe { match exports::promise_result(result_idx, 0) { 0 => PromiseResult::NotReady, @@ -431,56 +424,87 @@ pub fn promise_results_count() -> u64 { PromiseResult::Successful(bytes) } 2 => PromiseResult::Failed, - _ => panic!("{}", RETURN_CODE_ERR), + _ => panic_utf8(b"ERR_PROMISE_RETURN_CODE"), } } -}*/ +} -#[allow(dead_code)] pub fn assert_private_call() { assert_eq!( predecessor_account_id(), current_account_id(), - "Function is private" + "ERR_PRIVATE_CALL" ); } pub fn attached_deposit() -> u128 { - use core::intrinsics::size_of; unsafe { - let data = [0u8; size_of::()]; + let data = [0u8; core::mem::size_of::()]; exports::attached_deposit(data.as_ptr() as u64); u128::from_le_bytes(data) } } -#[allow(dead_code)] pub fn assert_one_yocto() { - assert_eq!( - attached_deposit(), - 1, - "Requires attached deposit of exactly 1 yoctoNEAR" - ) + assert_eq!(attached_deposit(), 1, "ERR_1YOCTO_ATTACH") } -#[allow(dead_code)] pub fn promise_batch_action_transfer(promise_index: u64, amount: u128) { unsafe { exports::promise_batch_action_transfer(promise_index, &amount as *const u128 as _); } } -#[allow(dead_code)] pub fn storage_byte_cost() -> u128 { STORAGE_PRICE_PER_BYTE } -#[allow(dead_code)] -pub fn promise_batch_create(account_id: String) -> u64 { +pub fn promise_batch_create(account_id: &[u8]) -> u64 { unsafe { exports::promise_batch_create(account_id.len() as _, account_id.as_ptr() as _) } } +pub fn promise_batch_action_function_call( + promise_idx: u64, + method_name: &[u8], + arguments: &[u8], + amount: u128, + gas: u64, +) { + unsafe { + exports::promise_batch_action_function_call( + promise_idx, + method_name.len() as _, + method_name.as_ptr() as _, + arguments.len() as _, + arguments.as_ptr() as _, + &amount as *const u128 as _, + gas, + ) + } +} + #[allow(dead_code)] pub fn storage_has_key(key: &[u8]) -> bool { - unsafe { exports::storage_has_key(key.len() as u64, key.as_ptr() as u64) == 1 } + unsafe { exports::storage_has_key(key.len() as _, key.as_ptr() as _) == 1 } +} + +pub(crate) struct IncorrectInputLength; +impl AsRef<[u8]> for IncorrectInputLength { + fn as_ref(&self) -> &[u8] { + b"ERR_INCORRECT_INPUT_LENGTH" + } +} + +pub(crate) struct ArgParseErr; +impl AsRef<[u8]> for ArgParseErr { + fn as_ref(&self) -> &[u8] { + b"ERR_ARG_PARSE" + } +} + +pub(crate) struct InvalidU64; +impl AsRef<[u8]> for InvalidU64 { + fn as_ref(&self) -> &[u8] { + b"ERR_NOT_U64" + } } diff --git a/src/storage.rs b/src/storage.rs index a02a48477..80fb9b6ba 100644 --- a/src/storage.rs +++ b/src/storage.rs @@ -16,7 +16,18 @@ pub enum KeyPrefix { Code = 0x3, Storage = 0x4, RelayerEvmAddressMap = 0x5, - // EthConnector = 0x6, + EthConnector = 0x6, + Generation = 0x7, +} + +/// Enum used to differentiate different storage keys used by eth-connector +#[derive(Clone, Copy, BorshSerialize, BorshDeserialize)] +pub enum EthConnectorStorageId { + Contract = 0x0, + FungibleToken = 0x1, + UsedEvent = 0x2, + PausedMask = 0x3, + StatisticsAuroraAccountsCounter = 0x4, } /// We can't use const generic over Enum, but we can do it over integral type @@ -32,7 +43,8 @@ impl From for KeyPrefix { 0x3 => Self::Code, 0x4 => Self::Storage, 0x5 => Self::RelayerEvmAddressMap, - // 0x6 used + 0x6 => Self::EthConnector, + 0x7 => Self::Generation, _ => unreachable!(), } } @@ -52,8 +64,31 @@ pub fn address_to_key(prefix: KeyPrefix, address: &Address) -> [u8; 22] { result } +pub enum StorageKeyKind { + Normal([u8; 54]), + Generation([u8; 58]), +} + +impl AsRef<[u8]> for StorageKeyKind { + fn as_ref(&self) -> &[u8] { + use StorageKeyKind::*; + match self { + Normal(v) => v.as_slice(), + Generation(v) => v.as_slice(), + } + } +} + +pub fn storage_to_key(address: &Address, key: &H256, generation: u32) -> StorageKeyKind { + if generation == 0 { + StorageKeyKind::Normal(normal_storage_key(address, key)) + } else { + StorageKeyKind::Generation(generation_storage_key(address, key, generation)) + } +} + #[allow(dead_code)] -pub fn storage_to_key(address: &Address, key: &H256) -> [u8; 54] { +fn normal_storage_key(address: &Address, key: &H256) -> [u8; 54] { let mut result = [0u8; 54]; result[0] = VersionPrefix::V1 as u8; result[1] = KeyPrefix::Storage as u8; @@ -63,13 +98,13 @@ pub fn storage_to_key(address: &Address, key: &H256) -> [u8; 54] { } #[allow(dead_code)] -pub fn storage_to_key_nonced(address: &Address, storage_nonce: u32, key: &H256) -> [u8; 58] { +fn generation_storage_key(address: &Address, key: &H256, generation: u32) -> [u8; 58] { let mut result = [0u8; 58]; result[0] = VersionPrefix::V1 as u8; result[1] = KeyPrefix::Storage as u8; result[2..22].copy_from_slice(&address.0); - result[22..26].copy_from_slice(&storage_nonce.to_le_bytes()); - result[26..58].copy_from_slice(&key.0); + result[22..54].copy_from_slice(&generation.to_le_bytes()); + result[54..58].copy_from_slice(&key.0); result } diff --git a/src/test_utils/mod.rs b/src/test_utils/mod.rs index 5381dd268..ecdb97653 100644 --- a/src/test_utils/mod.rs +++ b/src/test_utils/mod.rs @@ -12,7 +12,8 @@ use primitive_types::U256; use rlp::RlpStream; use secp256k1::{self, Message, PublicKey, SecretKey}; -use crate::parameters::{NewCallArgs, SubmitResult}; +use crate::fungible_token::FungibleToken; +use crate::parameters::{InitCallArgs, NewCallArgs, SubmitResult}; use crate::prelude::Address; use crate::storage; use crate::test_utils::solidity::{ContractConstructor, DeployedContract}; @@ -146,17 +147,32 @@ impl AuroraRunner { (maybe_outcome, maybe_error) } - pub fn create_address(&mut self, address: Address, init_balance: U256, init_nonce: U256) { + pub fn create_address(&mut self, address: Address, init_balance: types::Wei, init_nonce: U256) { let trie = &mut self.ext.fake_trie; let balance_key = storage::address_to_key(storage::KeyPrefix::Balance, &address); - let balance_value = types::u256_to_arr(&init_balance); + let balance_value = init_balance.to_bytes(); let nonce_key = storage::address_to_key(storage::KeyPrefix::Nonce, &address); let nonce_value = types::u256_to_arr(&init_nonce); + let ft_key = storage::bytes_to_key( + storage::KeyPrefix::EthConnector, + &[storage::EthConnectorStorageId::FungibleToken as u8], + ); + let ft_value = { + let mut current_ft: FungibleToken = trie + .get(&ft_key) + .map(|bytes| FungibleToken::try_from_slice(&bytes).unwrap()) + .unwrap_or_default(); + current_ft.total_supply += init_balance.raw().as_u128(); + current_ft.total_supply_eth += init_balance.raw().as_u128(); + current_ft + }; + trie.insert(balance_key.to_vec(), balance_value.to_vec()); trie.insert(nonce_key.to_vec(), nonce_value.to_vec()); + trie.insert(ft_key, ft_value.try_to_vec().unwrap()); } pub fn submit_transaction( @@ -202,8 +218,8 @@ impl AuroraRunner { } } - pub fn get_balance(&self, address: Address) -> U256 { - self.getter_method_call("get_balance", address) + pub fn get_balance(&self, address: Address) -> types::Wei { + types::Wei::new(self.getter_method_call("get_balance", address)) } pub fn get_nonce(&self, address: Address) -> U256 { @@ -277,7 +293,7 @@ pub(crate) fn deploy_evm() -> AuroraRunner { let args = NewCallArgs { chain_id: types::u256_to_arr(&U256::from(runner.chain_id)), owner_id: runner.aurora_account_id.clone(), - bridge_prover_id: "prover.near".to_string(), + bridge_prover_id: "bridge_prover.near".to_string(), upgrade_delay_blocks: 1, }; @@ -289,12 +305,24 @@ pub(crate) fn deploy_evm() -> AuroraRunner { assert!(maybe_error.is_none()); + let args = InitCallArgs { + prover_account: "prover.near".to_string(), + eth_custodian_address: "d045f7e19B2488924B97F9c145b5E51D0D895A65".to_string(), + }; + let (_, maybe_error) = runner.call( + "new_eth_connector", + runner.aurora_account_id.clone(), + args.try_to_vec().unwrap(), + ); + + assert!(maybe_error.is_none()); + runner } pub(crate) fn create_eth_transaction( to: Option
, - value: U256, + value: types::Wei, data: Vec, chain_id: Option, secret_key: &SecretKey, @@ -354,7 +382,7 @@ pub(crate) fn parse_eth_gas(output: &VMOutcome) -> u64 { pub(crate) fn validate_address_balance_and_nonce( runner: &AuroraRunner, address: Address, - expected_balance: U256, + expected_balance: types::Wei, expected_nonce: U256, ) { assert_eq!(runner.get_balance(address), expected_balance, "balance"); diff --git a/src/tests/erc20.rs b/src/tests/erc20.rs index 4790bb3a8..54769bde8 100644 --- a/src/tests/erc20.rs +++ b/src/tests/erc20.rs @@ -3,6 +3,7 @@ use crate::test_utils::{ self, erc20::{ERC20Constructor, ERC20}, }; +use crate::types::Wei; use bstr::ByteSlice; use secp256k1::SecretKey; @@ -211,7 +212,11 @@ fn initialize_erc20() -> (test_utils::AuroraRunner, SecretKey, Address, ERC20) { let mut rng = rand::thread_rng(); let source_account = SecretKey::random(&mut rng); let source_address = test_utils::address_from_secret_key(&source_account); - runner.create_address(source_address, INITIAL_BALANCE.into(), INITIAL_NONCE.into()); + runner.create_address( + source_address, + Wei::new_u64(INITIAL_BALANCE), + INITIAL_NONCE.into(), + ); let dest_address = test_utils::address_from_secret_key(&SecretKey::random(&mut rng)); let constructor = ERC20Constructor::load(); diff --git a/src/tests/sanity.rs b/src/tests/sanity.rs index 067119a5b..b89a81887 100644 --- a/src/tests/sanity.rs +++ b/src/tests/sanity.rs @@ -1,11 +1,12 @@ use crate::prelude::Address; use crate::test_utils; use crate::transaction::EthTransaction; +use crate::types::Wei; use secp256k1::SecretKey; -const INITIAL_BALANCE: u64 = 1000; +const INITIAL_BALANCE: Wei = Wei::new_u64(1000); const INITIAL_NONCE: u64 = 0; -const TRANSFER_AMOUNT: u64 = 123; +const TRANSFER_AMOUNT: Wei = Wei::new_u64(123); /// Tests we can transfer Eth from one account to another and that the balances are correctly /// updated. @@ -28,10 +29,10 @@ fn test_eth_transfer_success() { test_utils::validate_address_balance_and_nonce( &runner, source_address, - INITIAL_BALANCE.into(), + INITIAL_BALANCE, INITIAL_NONCE.into(), ); - test_utils::validate_address_balance_and_nonce(&runner, dest_address, 0.into(), 0.into()); + test_utils::validate_address_balance_and_nonce(&runner, dest_address, Wei::zero(), 0.into()); // perform transfer let (_, maybe_err) = runner.call(test_utils::SUBMIT, calling_account_id, input); @@ -41,13 +42,13 @@ fn test_eth_transfer_success() { test_utils::validate_address_balance_and_nonce( &runner, source_address, - (INITIAL_BALANCE - TRANSFER_AMOUNT).into(), + INITIAL_BALANCE - TRANSFER_AMOUNT, (INITIAL_NONCE + 1).into(), ); test_utils::validate_address_balance_and_nonce( &runner, dest_address, - TRANSFER_AMOUNT.into(), + TRANSFER_AMOUNT, 0.into(), ); } @@ -59,7 +60,7 @@ fn test_eth_transfer_insufficient_balance() { let source_address = test_utils::address_from_secret_key(&source_account); let transaction = test_utils::create_eth_transaction( Some(dest_address), - (2 * INITIAL_BALANCE).into(), // trying to transfer more than we have + INITIAL_BALANCE + INITIAL_BALANCE, // trying to transfer more than we have vec![], Some(runner.chain_id), &source_account, @@ -71,10 +72,10 @@ fn test_eth_transfer_insufficient_balance() { test_utils::validate_address_balance_and_nonce( &runner, source_address, - INITIAL_BALANCE.into(), + INITIAL_BALANCE, INITIAL_NONCE.into(), ); - test_utils::validate_address_balance_and_nonce(&runner, dest_address, 0.into(), 0.into()); + test_utils::validate_address_balance_and_nonce(&runner, dest_address, Wei::zero(), 0.into()); // attempt transfer let (_, maybe_err) = runner.call(test_utils::SUBMIT, calling_account_id, input); @@ -85,11 +86,11 @@ fn test_eth_transfer_insufficient_balance() { test_utils::validate_address_balance_and_nonce( &runner, source_address, - INITIAL_BALANCE.into(), + INITIAL_BALANCE, // the nonce is still incremented even though the transfer failed (INITIAL_NONCE + 1).into(), ); - test_utils::validate_address_balance_and_nonce(&runner, dest_address, 0.into(), 0.into()); + test_utils::validate_address_balance_and_nonce(&runner, dest_address, Wei::zero(), 0.into()); } /// Tests the case where the nonce on the transaction does not match the address @@ -114,10 +115,10 @@ fn test_eth_transfer_incorrect_nonce() { test_utils::validate_address_balance_and_nonce( &runner, source_address, - INITIAL_BALANCE.into(), + INITIAL_BALANCE, INITIAL_NONCE.into(), ); - test_utils::validate_address_balance_and_nonce(&runner, dest_address, 0.into(), 0.into()); + test_utils::validate_address_balance_and_nonce(&runner, dest_address, Wei::zero(), 0.into()); // attempt transfer let (_, maybe_err) = runner.call(test_utils::SUBMIT, calling_account_id, input); @@ -128,10 +129,10 @@ fn test_eth_transfer_incorrect_nonce() { test_utils::validate_address_balance_and_nonce( &runner, source_address, - INITIAL_BALANCE.into(), + INITIAL_BALANCE, INITIAL_NONCE.into(), ); - test_utils::validate_address_balance_and_nonce(&runner, dest_address, 0.into(), 0.into()); + test_utils::validate_address_balance_and_nonce(&runner, dest_address, Wei::zero(), 0.into()); } fn initialize_transfer() -> (test_utils::AuroraRunner, SecretKey, Address) { @@ -140,7 +141,7 @@ fn initialize_transfer() -> (test_utils::AuroraRunner, SecretKey, Address) { let mut rng = rand::thread_rng(); let source_account = SecretKey::random(&mut rng); let source_address = test_utils::address_from_secret_key(&source_account); - runner.create_address(source_address, INITIAL_BALANCE.into(), INITIAL_NONCE.into()); + runner.create_address(source_address, INITIAL_BALANCE, INITIAL_NONCE.into()); let dest_address = test_utils::address_from_secret_key(&SecretKey::random(&mut rng)); (runner, source_account, dest_address) diff --git a/src/tests/standard_precompiles.rs b/src/tests/standard_precompiles.rs index 15e986182..bf2ca483a 100644 --- a/src/tests/standard_precompiles.rs +++ b/src/tests/standard_precompiles.rs @@ -2,9 +2,10 @@ use crate::test_utils::{ self, standard_precompiles::{PrecompilesConstructor, PrecompilesContract}, }; +use crate::types::Wei; use secp256k1::SecretKey; -const INITIAL_BALANCE: u64 = 1000; +const INITIAL_BALANCE: Wei = Wei::new_u64(1000); const INITIAL_NONCE: u64 = 0; #[test] @@ -14,7 +15,7 @@ fn standard_precompiles() { let source_account = SecretKey::random(&mut rng); runner.create_address( test_utils::address_from_secret_key(&source_account), - INITIAL_BALANCE.into(), + INITIAL_BALANCE, INITIAL_NONCE.into(), ); diff --git a/src/transaction.rs b/src/transaction.rs index 91152d292..fe1be41ba 100644 --- a/src/transaction.rs +++ b/src/transaction.rs @@ -1,4 +1,5 @@ use crate::prelude::{Address, Vec, U256}; +use crate::types::Wei; use rlp::{Decodable, DecoderError, Encodable, Rlp, RlpStream}; #[derive(Debug, Eq, PartialEq)] @@ -12,7 +13,7 @@ pub struct EthTransaction { /// The receiving address (`None` for the zero address) pub to: Option
, /// The amount of ETH to transfer - pub value: U256, + pub value: Wei, /// Arbitrary binary data for a contract call invocation pub data: Vec, } @@ -27,7 +28,7 @@ impl EthTransaction { None => s.append(&""), Some(address) => s.append(address), }; - s.append(&self.value); + s.append(&self.value.raw()); s.append(&self.data); if let Some(chain_id) = chain_id { s.append(&chain_id); @@ -87,7 +88,7 @@ impl Encodable for EthSignedTransaction { None => s.append(&""), Some(address) => s.append(address), }; - s.append(&self.transaction.value); + s.append(&self.transaction.value.raw()); s.append(&self.transaction.data); s.append(&self.v); s.append(&self.r); @@ -120,7 +121,7 @@ impl Decodable for EthSignedTransaction { } } }; - let value = rlp.val_at(4)?; + let value = Wei::new(rlp.val_at(4)?); let data = rlp.val_at(5)?; let v = rlp.val_at(6)?; let r = rlp.val_at(7)?; @@ -181,7 +182,7 @@ mod tests { to: Some(address_from_arr( &hex::decode("F0109fC8DF283027b6285cc889F5aA624EaC1F55").unwrap() )), - value: U256::from(1000000000), + value: Wei::new_u64(1000000000), data: vec![], } ); diff --git a/src/types.rs b/src/types.rs index 36e030ee0..b31f7c0f5 100644 --- a/src/types.rs +++ b/src/types.rs @@ -1,4 +1,7 @@ -use crate::prelude::{Address, String, Vec, H256, U256}; +use crate::prelude::{self, Address, String, Vec, H256, U256}; +#[cfg(feature = "engine")] +use alloc::str; +use borsh::{BorshDeserialize, BorshSerialize}; #[cfg(not(feature = "contract"))] use sha3::{Digest, Keccak256}; @@ -7,24 +10,116 @@ use sha3::{Digest, Keccak256}; use crate::sdk; pub type AccountId = String; +pub type Balance = u128; pub type RawAddress = [u8; 20]; pub type RawU256 = [u8; 32]; // Little-endian large integer type. pub type RawH256 = [u8; 32]; // Unformatted binary data of fixed length. +pub type EthAddress = [u8; 20]; +pub type Gas = u64; +pub type StorageUsage = u64; + +/// Newtype to distinguish balances (denominated in Wei) from other U256 types. +#[derive(Debug, Eq, PartialEq, Copy, Clone, Default)] +pub struct Wei(U256); +impl Wei { + const ETH_TO_WEI: U256 = U256([1_000_000_000_000_000_000, 0, 0, 0]); + + pub const fn zero() -> Self { + Self(U256([0, 0, 0, 0])) + } + + pub fn new(amount: U256) -> Self { + Self(amount) + } + + // Purposely not implementing `From` because I want the call site to always + // say `Wei::`. If `From` is implemented then the caller might write + // `amount.into()` without thinking too hard about the units. Explicitly writing + // `Wei` reminds the developer to think about whether the amount they enter is really + // in units of `Wei` or not. + pub const fn new_u64(amount: u64) -> Self { + Self(U256([amount, 0, 0, 0])) + } + + pub fn from_eth(amount: U256) -> Option { + amount.checked_mul(Self::ETH_TO_WEI).map(Self) + } + + pub fn to_bytes(&self) -> [u8; 32] { + u256_to_arr(&self.0) + } + + pub fn is_zero(&self) -> bool { + self.0.is_zero() + } + + pub fn raw(self) -> U256 { + self.0 + } +} +impl prelude::Sub for Wei { + type Output = Self; + + fn sub(self, other: Self) -> Self::Output { + Self(self.0 - other.0) + } +} +impl prelude::Add for Wei { + type Output = Self; + + fn add(self, other: Self) -> Self::Output { + Self(self.0 + other.0) + } +} + +#[derive(BorshSerialize, BorshDeserialize)] +pub struct U128(pub u128); pub const STORAGE_PRICE_PER_BYTE: u128 = 10_000_000_000_000_000_000; // 1e19yN, 0.00001N +pub const ERR_FAILED_PARSE: &str = "ERR_FAILED_PARSE"; /// Internal args format for meta call. #[derive(Debug)] pub struct InternalMetaCallArgs { pub sender: Address, pub nonce: U256, - pub fee_amount: U256, + pub fee_amount: Wei, pub fee_address: Address, pub contract_address: Address, - pub value: U256, + pub value: Wei, pub input: Vec, } +pub struct StorageBalanceBounds { + pub min: Balance, + pub max: Option, +} + +/// promise results structure +#[cfg(feature = "engine")] +pub enum PromiseResult { + NotReady, + Successful(Vec), + Failed, +} + +/// ft_resolve_transfer result of eth-connector +#[cfg(feature = "engine")] +pub struct FtResolveTransferResult { + pub amount: Balance, + pub refund_amount: Balance, +} + +/// Internal errors to propagate up and format in the single place. +pub enum ErrorKind { + ArgumentParseError, + InvalidMetaTransactionMethodName, + InvalidMetaTransactionFunctionArg, + InvalidEcRecoverSignature, +} + +pub type Result = core::result::Result; + #[allow(dead_code)] pub fn u256_to_arr(value: &U256) -> [u8; 32] { let mut result = [0u8; 32]; @@ -61,6 +156,36 @@ pub fn near_account_to_evm_address(addr: &[u8]) -> Address { Address::from_slice(&keccak(addr)[12..]) } +#[cfg(feature = "engine")] +pub fn str_from_slice(inp: &[u8]) -> &str { + str::from_utf8(inp).unwrap() +} + +#[cfg(feature = "contract")] +pub trait ExpectUtf8 { + fn expect_utf8(self, message: &[u8]) -> T; +} + +#[cfg(feature = "contract")] +impl ExpectUtf8 for Option { + fn expect_utf8(self, message: &[u8]) -> T { + match self { + Some(t) => t, + None => sdk::panic_utf8(message), + } + } +} + +#[cfg(feature = "contract")] +impl ExpectUtf8 for core::result::Result { + fn expect_utf8(self, message: &[u8]) -> T { + match self { + Ok(t) => t, + Err(_) => sdk::panic_utf8(message), + } + } +} + #[cfg(test)] mod tests { use super::*; @@ -72,4 +197,17 @@ mod tests { "0001ff10".to_string() ); } + + #[test] + fn test_wei_from_u64() { + let x: u64 = rand::random(); + assert_eq!(Wei::new_u64(x).raw().as_u64(), x); + } + + #[test] + fn test_wei_from_eth() { + let eth_amount: u64 = rand::random(); + let wei_amount = U256::from(eth_amount) * U256::from(10).pow(18.into()); + assert_eq!(Wei::from_eth(eth_amount.into()), Some(Wei::new(wei_amount))); + } } diff --git a/tests/test_connector.rs b/tests/test_connector.rs new file mode 100644 index 000000000..d0e4bda26 --- /dev/null +++ b/tests/test_connector.rs @@ -0,0 +1,1153 @@ +#![allow(dead_code)] + +use near_sdk::borsh::{BorshDeserialize, BorshSerialize}; +use near_sdk::serde::{Deserialize, Serialize}; +use near_sdk::serde_json; +use near_sdk::serde_json::json; +use near_sdk::test_utils::accounts; +use near_sdk_sim::{to_yocto, ExecutionResult, UserAccount, DEFAULT_GAS, STORAGE_AMOUNT}; + +use aurora_engine::parameters::NewCallArgs; +use aurora_engine::types::{Balance, EthAddress}; +use byte_slice_cast::AsByteSlice; +use near_sdk_sim::transaction::ExecutionStatus; +use primitive_types::U256; + +pub type PausedMask = u8; +const UNPAUSE_ALL: PausedMask = 0; +const PAUSE_DEPOSIT: PausedMask = 1 << 0; +const PAUSE_WITHDRAW: PausedMask = 1 << 1; + +const CONTRACT_ACC: &'static str = "eth_connector.root"; +const EXTERNAL_CONTRACT_ACC: &'static str = "eth_recipient.root"; +const PROOF_DATA_NEAR: &'static str = r#"{"log_index":0,"log_entry_data":[248,251,148,9,109,233,194,184,165,184,194,44,238,50,137,177,1,246,150,13,104,229,30,248,66,160,209,66,67,156,39,142,37,218,217,165,7,102,241,83,208,227,210,215,191,43,209,111,194,120,28,75,212,148,178,177,90,157,160,0,0,0,0,0,0,0,0,0,0,0,0,121,24,63,219,216,14,45,138,234,26,202,162,246,123,251,138,54,212,10,141,184,160,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,96,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,12,54,144,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,144,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,18,101,116,104,95,114,101,99,105,112,105,101,110,116,46,114,111,111,116,0,0,0,0,0,0,0,0,0,0,0,0,0,0],"receipt_index":0,"receipt_data":[249,2,6,1,130,107,17,185,1,0,0,0,8,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,128,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,128,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,32,0,0,0,0,0,0,0,0,0,0,128,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,8,0,0,0,0,0,0,0,0,0,0,0,0,0,128,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,128,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,248,253,248,251,148,9,109,233,194,184,165,184,194,44,238,50,137,177,1,246,150,13,104,229,30,248,66,160,209,66,67,156,39,142,37,218,217,165,7,102,241,83,208,227,210,215,191,43,209,111,194,120,28,75,212,148,178,177,90,157,160,0,0,0,0,0,0,0,0,0,0,0,0,121,24,63,219,216,14,45,138,234,26,202,162,246,123,251,138,54,212,10,141,184,160,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,96,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,12,54,144,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,144,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,18,101,116,104,95,114,101,99,105,112,105,101,110,116,46,114,111,111,116,0,0,0,0,0,0,0,0,0,0,0,0,0,0],"header_data":[249,2,10,160,177,33,112,26,26,176,12,12,163,2,249,133,245,12,51,201,55,50,148,156,122,67,27,26,101,178,36,153,54,100,53,137,160,29,204,77,232,222,199,93,122,171,133,181,103,182,204,212,26,211,18,69,27,148,138,116,19,240,161,66,253,64,212,147,71,148,124,28,230,160,8,239,64,193,62,78,177,68,166,204,116,240,224,174,172,126,160,197,65,5,202,188,134,5,164,246,19,133,35,57,28,114,241,186,81,123,163,166,161,24,32,157,168,170,13,108,58,61,46,160,6,199,163,13,91,119,225,39,168,255,213,10,107,252,143,246,138,241,108,139,59,35,187,185,162,223,53,108,222,73,181,109,160,27,154,49,63,26,170,15,177,97,255,6,204,84,221,234,197,159,172,114,47,148,126,32,199,241,127,101,120,182,51,52,100,185,1,0,0,0,8,0,0,0,0,0,0,0,32,0,0,0,0,0,2,0,8,0,0,0,0,0,0,0,0,0,0,2,0,0,0,0,0,0,0,0,8,32,0,32,0,0,128,0,2,0,0,0,1,0,32,0,0,0,2,0,0,0,0,32,0,0,0,0,0,4,0,0,0,0,0,0,0,8,0,0,0,0,0,0,0,128,64,0,0,0,0,1,32,0,0,0,0,0,0,96,32,0,64,0,0,0,128,1,0,0,0,0,1,0,0,0,8,0,0,0,18,32,0,0,64,145,1,8,0,4,0,128,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,32,33,16,0,128,0,0,0,0,0,0,128,0,2,0,0,0,0,0,0,0,0,0,0,2,0,80,0,0,0,0,0,0,0,0,1,128,0,8,0,0,0,0,4,0,0,0,128,2,0,32,0,128,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,128,0,0,0,0,0,0,0,16,0,8,0,0,0,0,0,0,0,0,0,0,128,0,64,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,132,25,1,227,23,131,157,85,14,131,122,18,0,131,75,91,132,132,96,174,58,224,140,115,112,105,100,101,114,49,48,1,2,8,230,160,188,212,199,183,154,22,223,85,103,215,24,122,240,235,79,129,44,93,184,88,161,218,79,5,44,226,106,100,50,40,163,97,136,155,158,202,3,149,91,200,78],"proof":[[248,113,160,46,156,31,85,241,226,241,13,5,56,73,146,176,67,195,109,6,189,172,104,44,103,44,88,32,15,181,152,136,29,121,252,160,191,48,87,174,71,151,208,114,164,150,51,200,171,90,90,106,46,200,79,77,222,145,95,89,141,137,138,149,67,73,8,87,128,128,128,128,128,128,160,175,9,219,77,174,13,247,133,55,172,92,185,202,7,160,10,204,112,44,133,36,96,30,234,235,134,30,209,205,166,212,255,128,128,128,128,128,128,128,128],[249,2,13,48,185,2,9,249,2,6,1,130,107,17,185,1,0,0,0,8,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,128,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,128,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,32,0,0,0,0,0,0,0,0,0,0,128,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,8,0,0,0,0,0,0,0,0,0,0,0,0,0,128,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,128,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,248,253,248,251,148,9,109,233,194,184,165,184,194,44,238,50,137,177,1,246,150,13,104,229,30,248,66,160,209,66,67,156,39,142,37,218,217,165,7,102,241,83,208,227,210,215,191,43,209,111,194,120,28,75,212,148,178,177,90,157,160,0,0,0,0,0,0,0,0,0,0,0,0,121,24,63,219,216,14,45,138,234,26,202,162,246,123,251,138,54,212,10,141,184,160,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,96,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,12,54,144,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,144,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,18,101,116,104,95,114,101,99,105,112,105,101,110,116,46,114,111,111,116,0,0,0,0,0,0,0,0,0,0,0,0,0,0]]}"#; +const PROOF_DATA_ETH: &'static str = r#"{"log_index":0,"log_entry_data":[249,1,27,148,9,109,233,194,184,165,184,194,44,238,50,137,177,1,246,150,13,104,229,30,248,66,160,209,66,67,156,39,142,37,218,217,165,7,102,241,83,208,227,210,215,191,43,209,111,194,120,28,75,212,148,178,177,90,157,160,0,0,0,0,0,0,0,0,0,0,0,0,121,24,63,219,216,14,45,138,234,26,202,162,246,123,251,138,54,212,10,141,184,192,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,96,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,39,216,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,200,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,59,101,116,104,95,99,111,110,110,101,99,116,111,114,46,114,111,111,116,58,56,57,49,66,50,55,52,57,50,51,56,66,50,55,102,70,53,56,101,57,53,49,48,56,56,101,53,53,98,48,52,100,101,55,49,68,99,51,55,52,0,0,0,0,0],"receipt_index":0,"receipt_data":[249,2,40,1,130,121,129,185,1,0,0,0,8,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,128,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,128,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,32,0,0,0,0,0,0,0,0,0,0,128,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,8,0,0,0,0,0,0,0,0,0,0,0,0,0,128,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,128,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,249,1,30,249,1,27,148,9,109,233,194,184,165,184,194,44,238,50,137,177,1,246,150,13,104,229,30,248,66,160,209,66,67,156,39,142,37,218,217,165,7,102,241,83,208,227,210,215,191,43,209,111,194,120,28,75,212,148,178,177,90,157,160,0,0,0,0,0,0,0,0,0,0,0,0,121,24,63,219,216,14,45,138,234,26,202,162,246,123,251,138,54,212,10,141,184,192,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,96,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,39,216,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,200,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,59,101,116,104,95,99,111,110,110,101,99,116,111,114,46,114,111,111,116,58,56,57,49,66,50,55,52,57,50,51,56,66,50,55,102,70,53,56,101,57,53,49,48,56,56,101,53,53,98,48,52,100,101,55,49,68,99,51,55,52,0,0,0,0,0],"header_data":[249,2,23,160,227,118,223,171,207,47,75,187,79,185,74,198,88,140,54,97,161,196,35,70,121,178,154,141,172,91,193,252,86,64,228,227,160,29,204,77,232,222,199,93,122,171,133,181,103,182,204,212,26,211,18,69,27,148,138,116,19,240,161,66,253,64,212,147,71,148,109,150,79,199,61,172,73,162,195,49,105,169,235,252,47,207,92,249,136,136,160,232,74,213,122,210,55,65,43,78,225,85,247,174,212,229,211,176,186,250,113,21,129,16,181,52,172,217,167,148,242,153,45,160,15,198,229,127,6,235,198,161,226,121,173,106,62,0,90,25,158,11,242,44,178,3,137,22,245,126,227,91,74,156,24,115,160,65,253,74,43,97,155,196,93,59,43,202,12,155,49,115,95,124,247,230,15,1,171,150,10,56,115,247,86,81,8,39,11,185,1,0,128,32,9,2,0,0,0,0,0,0,32,16,128,32,0,0,128,2,0,0,64,51,0,0,0,129,0,32,66,32,0,14,0,144,0,0,0,2,13,34,0,128,64,200,128,4,32,16,0,64,0,0,34,0,32,0,40,0,8,0,0,32,176,0,196,1,0,0,10,1,16,8,16,0,0,72,48,0,0,36,0,17,4,128,10,68,0,16,0,1,32,0,128,0,32,0,12,64,162,8,98,2,0,32,0,0,16,136,1,16,40,0,0,0,0,4,0,0,44,32,0,0,192,49,0,8,12,64,96,129,0,2,0,0,128,0,12,64,10,8,1,132,0,32,0,1,4,33,0,4,128,140,128,0,2,66,0,0,192,0,2,16,2,0,0,0,32,16,0,0,64,0,242,4,0,0,0,0,0,0,4,128,0,32,0,14,194,0,16,10,64,32,0,0,0,2,16,96,16,129,0,16,32,32,128,128,32,0,2,68,0,32,1,8,64,16,32,2,5,2,68,0,32,0,2,16,1,0,0,16,2,0,0,16,2,0,0,0,128,0,16,0,36,128,32,0,4,64,16,0,40,16,0,17,0,16,132,25,207,98,158,131,157,85,88,131,122,17,225,131,121,11,191,132,96,174,60,127,153,216,131,1,10,1,132,103,101,116,104,134,103,111,49,46,49,54,135,119,105,110,100,111,119,115,160,33,15,129,167,71,37,0,207,110,217,101,107,71,110,48,237,4,83,174,75,131,188,213,179,154,115,243,94,107,52,238,144,136,84,114,37,115,236,166,252,105],"proof":[[248,177,160,211,36,253,39,157,18,180,1,3,139,140,168,65,238,106,111,239,53,121,48,235,96,8,115,106,93,174,165,66,207,49,216,160,172,74,129,163,113,84,7,35,23,12,83,10,253,21,57,198,143,128,73,112,84,222,23,146,164,219,89,23,138,197,111,237,160,52,220,245,245,91,231,95,169,113,225,49,168,40,77,59,232,33,210,4,93,203,94,247,212,15,42,146,32,70,206,193,54,160,6,140,29,61,156,224,194,173,129,74,84,92,11,129,184,212,37,31,23,140,226,87,230,72,30,52,97,66,185,236,139,228,128,128,128,128,160,190,114,105,101,139,216,178,42,238,75,109,119,227,138,206,144,183,82,34,173,26,173,188,231,152,171,56,163,2,179,13,190,128,128,128,128,128,128,128,128],[249,2,47,48,185,2,43,249,2,40,1,130,121,129,185,1,0,0,0,8,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,128,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,128,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,32,0,0,0,0,0,0,0,0,0,0,128,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,8,0,0,0,0,0,0,0,0,0,0,0,0,0,128,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,128,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,249,1,30,249,1,27,148,9,109,233,194,184,165,184,194,44,238,50,137,177,1,246,150,13,104,229,30,248,66,160,209,66,67,156,39,142,37,218,217,165,7,102,241,83,208,227,210,215,191,43,209,111,194,120,28,75,212,148,178,177,90,157,160,0,0,0,0,0,0,0,0,0,0,0,0,121,24,63,219,216,14,45,138,234,26,202,162,246,123,251,138,54,212,10,141,184,192,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,96,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,39,216,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,200,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,59,101,116,104,95,99,111,110,110,101,99,116,111,114,46,114,111,111,116,58,56,57,49,66,50,55,52,57,50,51,56,66,50,55,102,70,53,56,101,57,53,49,48,56,56,101,53,53,98,48,52,100,101,55,49,68,99,51,55,52,0,0,0,0,0]]}"#; +const DEPOSITED_RECIPIENT: &'static str = "eth_recipient.root"; +const PROVER_ACCOUNT: &'static str = "eth_connector.root"; +const CUSTODIAN_ADDRESS: &'static str = "096DE9C2B8A5B8c22cEe3289B101f6960d68E51E"; +const DEPOSITED_AMOUNT: u128 = 800400; +const DEPOSITED_FEE: u128 = 400; +const RECIPIENT_ETH_ADDRESS: &'static str = "891b2749238b27ff58e951088e55b04de71dc374"; +const EVM_CUSTODIAN_ADDRESS: &'static str = "096DE9C2B8A5B8c22cEe3289B101f6960d68E51E"; +const DEPOSITED_EVM_AMOUNT: u128 = 10200; +const DEPOSITED_EVM_FEE: u128 = 200; +const ERR_NOT_ENOUGH_BALANCE_FOR_FEE: &'static str = "ERR_NOT_ENOUGH_BALANCE_FOR_FEE"; +const ERR_PAUSED: &'static str = "ERR_PAUSED"; + +near_sdk_sim::lazy_static_include::lazy_static_include_bytes! { + EVM_WASM_BYTES => "release.wasm" +} + +#[derive(BorshDeserialize, BorshSerialize, Serialize, Deserialize)] +#[serde(crate = "near_sdk::serde")] +pub struct Proof { + pub log_index: u64, + pub log_entry_data: Vec, + pub receipt_index: u64, + pub receipt_data: Vec, + pub header_data: Vec, + pub proof: Vec>, +} + +#[derive(BorshSerialize, BorshDeserialize)] +pub struct InitCallArgs { + pub prover_account: String, + pub eth_custodian_address: String, +} + +#[derive(BorshSerialize, BorshDeserialize)] +pub struct WithdrawCallArgs { + pub recipient_address: EthAddress, + pub amount: Balance, +} + +#[derive(BorshDeserialize, Debug)] +pub struct WithdrawResult { + pub amount: Balance, + pub recipient_id: EthAddress, + pub eth_custodian_address: EthAddress, +} + +fn init(custodian_address: &str) -> (UserAccount, UserAccount) { + let master_account = near_sdk_sim::init_simulator(None); + let contract = init_contract(&master_account, CONTRACT_ACC, custodian_address); + (master_account, contract) +} + +fn init_contract( + master_account: &UserAccount, + contract_name: &str, + custodian_address: &str, +) -> UserAccount { + let contract_account = master_account.deploy( + *EVM_WASM_BYTES, + contract_name.to_string(), + to_yocto("1000000"), + ); + contract_account + .call( + contract_name.to_string(), + "new", + &NewCallArgs { + chain_id: [0u8; 32], + owner_id: master_account.account_id.clone(), + bridge_prover_id: accounts(0).to_string(), + upgrade_delay_blocks: 1, + } + .try_to_vec() + .unwrap(), + DEFAULT_GAS, + STORAGE_AMOUNT, + ) + .assert_success(); + contract_account + .call( + contract_name.to_string(), + "new_eth_connector", + &InitCallArgs { + prover_account: PROVER_ACCOUNT.into(), + eth_custodian_address: custodian_address.into(), + } + .try_to_vec() + .unwrap(), + DEFAULT_GAS, + 0, + ) + .assert_success(); + contract_account +} + +fn validate_eth_address(address: &str) -> EthAddress { + let data = hex::decode(address).unwrap(); + assert_eq!(data.len(), 20); + let mut result = [0u8; 20]; + result.copy_from_slice(&data); + result +} + +fn call_deposit_near(master_account: &UserAccount, contract: &str) -> Vec> { + let proof: Proof = serde_json::from_str(PROOF_DATA_NEAR).unwrap(); + let res = master_account.call( + contract.to_string(), + "deposit", + &proof.try_to_vec().unwrap(), + DEFAULT_GAS, + 0, + ); + //println!("{:#?}", res.promise_results()); + // Calculate Gas burnt + // let total_gas_burnt = res + // .promise_results() + // .iter() + // .fold(0, |s, v| s + v.as_ref().unwrap().gas_burnt()); + // println!("{:#?}", total_gas_burnt); + res.promise_results() +} + +#[allow(dead_code)] +fn print_logs(logs: &Vec) { + for l in logs { + println!("[log] {}", l); + } +} + +fn call_deposit_eth(master_account: &UserAccount, contract: &str) { + let proof: Proof = serde_json::from_str(PROOF_DATA_ETH).unwrap(); + let res = master_account.call( + contract.to_string(), + "deposit", + &proof.try_to_vec().unwrap(), + DEFAULT_GAS, + 10, + ); + res.assert_success(); + //println!("{:#?}", res.promise_results()); +} + +fn get_near_balance(master_account: &UserAccount, acc: &str, contract: &str) -> u128 { + #[derive(BorshSerialize)] + pub struct BalanceOfCallArgs { + pub account_id: String, + } + + let balance = master_account.view( + contract.to_string(), + "ft_balance_of", + json!({ "account_id": acc }).to_string().as_bytes(), + ); + String::from_utf8(balance.unwrap()) + .unwrap() + .parse() + .unwrap() +} + +fn get_eth_balance(master_account: &UserAccount, address: EthAddress, contract: &str) -> u128 { + #[derive(BorshSerialize, BorshDeserialize)] + pub struct BalanceOfEthCallArgs { + pub address: EthAddress, + } + + let balance = master_account.view( + contract.to_string(), + "ft_balance_of_eth", + &BalanceOfEthCallArgs { address }.try_to_vec().unwrap(), + ); + String::from_utf8(balance.unwrap()) + .unwrap() + .parse() + .unwrap() +} + +fn total_supply(master_account: &UserAccount, contract: &str) -> u128 { + let balance = master_account.view(contract.to_string(), "ft_total_supply", &[]); + String::from_utf8(balance.unwrap()) + .unwrap() + .parse() + .unwrap() +} + +fn total_supply_near(master_account: &UserAccount, contract: &str) -> u128 { + let balance = master_account.view(contract.to_string(), "ft_total_supply_near", &[]); + String::from_utf8(balance.unwrap()) + .unwrap() + .parse() + .unwrap() +} + +fn total_supply_eth(master_account: &UserAccount, contract: &str) -> u128 { + let balance = master_account.view(contract.to_string(), "ft_total_supply_eth", &[]); + String::from_utf8(balance.unwrap()) + .unwrap() + .parse() + .unwrap() +} + +#[test] +fn test_near_deposit_balance_total_supply() { + let (master_account, contract) = init(CUSTODIAN_ADDRESS); + call_deposit_near(&contract, CONTRACT_ACC); + + let balance = get_near_balance(&master_account, DEPOSITED_RECIPIENT, CONTRACT_ACC); + assert_eq!(balance, DEPOSITED_AMOUNT - DEPOSITED_FEE); + + let balance = get_near_balance(&master_account, CONTRACT_ACC, CONTRACT_ACC); + assert_eq!(balance, DEPOSITED_FEE); + + let balance = total_supply(&master_account, CONTRACT_ACC); + assert_eq!(balance, DEPOSITED_AMOUNT); + + let balance = total_supply_near(&master_account, CONTRACT_ACC); + assert_eq!(balance, DEPOSITED_AMOUNT); + + let balance = total_supply_eth(&master_account, CONTRACT_ACC); + assert_eq!(balance, 0); +} + +#[derive(BorshSerialize, BorshDeserialize)] +pub struct RegisterRelayerCallArgs { + pub address: EthAddress, +} + +#[test] +fn test_eth_deposit_balance_total_supply() { + let (master_account, contract) = init(EVM_CUSTODIAN_ADDRESS); + let res = contract.call( + CONTRACT_ACC.to_string(), + "register_relayer", + &RegisterRelayerCallArgs { + address: validate_eth_address(CUSTODIAN_ADDRESS), + } + .try_to_vec() + .unwrap(), + DEFAULT_GAS, + 0, + ); + res.assert_success(); + + call_deposit_eth(&contract, CONTRACT_ACC); + + let balance = get_eth_balance( + &master_account, + validate_eth_address(RECIPIENT_ETH_ADDRESS), + CONTRACT_ACC, + ); + assert_eq!(balance, DEPOSITED_EVM_AMOUNT - DEPOSITED_EVM_FEE); + + let balance = get_eth_balance( + &master_account, + validate_eth_address(CUSTODIAN_ADDRESS), + CONTRACT_ACC, + ); + assert_eq!(balance, DEPOSITED_EVM_FEE); + + let balance = total_supply(&master_account, CONTRACT_ACC); + assert_eq!(balance, DEPOSITED_EVM_AMOUNT); + + let balance = total_supply_eth(&master_account, CONTRACT_ACC); + assert_eq!(balance, DEPOSITED_EVM_AMOUNT); + + let balance = total_supply_near(&master_account, CONTRACT_ACC); + assert_eq!(balance, 0); +} + +#[test] +fn test_withdraw_near() { + let (master_account, contract) = init(CUSTODIAN_ADDRESS); + call_deposit_near(&contract, CONTRACT_ACC); + + let withdraw_amount = 100; + let recipient_addr = validate_eth_address(RECIPIENT_ETH_ADDRESS); + let res = contract.call( + CONTRACT_ACC.to_string(), + "withdraw", + &WithdrawCallArgs { + recipient_address: recipient_addr, + amount: withdraw_amount, + } + .try_to_vec() + .unwrap(), + DEFAULT_GAS, + 1, + ); + res.assert_success(); + let data = res.promise_results(); + assert!(data.len() > 1); + assert!(data[0].is_some()); + match data[1].clone().unwrap().outcome().status { + ExecutionStatus::SuccessValue(ref v) => { + let d: WithdrawResult = WithdrawResult::try_from_slice(&v).unwrap(); + assert_eq!(d.amount, withdraw_amount); + assert_eq!(d.recipient_id, recipient_addr); + let custodian_addr = validate_eth_address(CUSTODIAN_ADDRESS); + assert_eq!(d.eth_custodian_address, custodian_addr); + } + _ => panic!(), + } + + let balance = get_near_balance(&master_account, CONTRACT_ACC, CONTRACT_ACC); + assert_eq!(balance, DEPOSITED_FEE - withdraw_amount as u128); + + let balance = get_near_balance(&master_account, DEPOSITED_RECIPIENT, CONTRACT_ACC); + assert_eq!(balance, DEPOSITED_AMOUNT - DEPOSITED_FEE); + + let balance = total_supply(&master_account, CONTRACT_ACC); + assert_eq!(balance, DEPOSITED_AMOUNT - withdraw_amount as u128); +} + +#[test] +fn test_ft_transfer() { + let (master_account, contract) = init(CUSTODIAN_ADDRESS); + call_deposit_near(&contract, CONTRACT_ACC); + + let transfer_amount = 70; + let res = contract.call( + CONTRACT_ACC.to_string(), + "ft_transfer", + json!({ + "receiver_id": DEPOSITED_RECIPIENT, + "amount": transfer_amount, + "memo": "transfer memo" + }) + .to_string() + .as_bytes(), + DEFAULT_GAS, + 1, + ); + res.assert_success(); + + let balance = get_near_balance(&master_account, DEPOSITED_RECIPIENT, CONTRACT_ACC); + assert_eq!( + balance, + DEPOSITED_AMOUNT - DEPOSITED_FEE + transfer_amount as u128 + ); + + let balance = get_near_balance(&master_account, CONTRACT_ACC, CONTRACT_ACC); + assert_eq!(balance, DEPOSITED_FEE - transfer_amount as u128); + + let balance = total_supply(&master_account, CONTRACT_ACC); + assert_eq!(balance, DEPOSITED_AMOUNT); + + let balance = total_supply_eth(&master_account, CONTRACT_ACC); + assert_eq!(balance, 0); + + let balance = total_supply_near(&master_account, CONTRACT_ACC); + assert_eq!(balance, DEPOSITED_AMOUNT); +} + +#[test] +fn test_ft_transfer_call_eth() { + let (master_account, contract) = init(CUSTODIAN_ADDRESS); + call_deposit_near(&contract, CONTRACT_ACC); + + let balance = get_near_balance(&master_account, DEPOSITED_RECIPIENT, CONTRACT_ACC); + assert_eq!(balance, DEPOSITED_AMOUNT - DEPOSITED_FEE); + + let balance = get_near_balance(&master_account, CONTRACT_ACC, CONTRACT_ACC); + assert_eq!(balance, DEPOSITED_FEE); + + let res = contract.call( + CONTRACT_ACC.to_string(), + "register_relayer", + &RegisterRelayerCallArgs { + address: validate_eth_address(CUSTODIAN_ADDRESS), + } + .try_to_vec() + .unwrap(), + DEFAULT_GAS, + 0, + ); + res.assert_success(); + + let transfer_amount = 50; + let fee = 30; + let mut msg = U256::from(fee).as_byte_slice().to_vec(); + msg.append(&mut validate_eth_address(RECIPIENT_ETH_ADDRESS).to_vec()); + let message = [CONTRACT_ACC, hex::encode(msg).as_str()].join(":"); + let res = contract.call( + CONTRACT_ACC.to_string(), + "ft_transfer_call", + json!({ + "receiver_id": CONTRACT_ACC, + "amount": transfer_amount as u64, + "msg": message, + }) + .to_string() + .as_bytes(), + DEFAULT_GAS, + 1, + ); + res.assert_success(); + + let balance = get_near_balance(&master_account, DEPOSITED_RECIPIENT, CONTRACT_ACC); + assert_eq!(balance, DEPOSITED_AMOUNT - DEPOSITED_FEE); + + let balance = get_near_balance(&master_account, CONTRACT_ACC, CONTRACT_ACC); + assert_eq!(balance, DEPOSITED_FEE - transfer_amount); + + let balance = get_eth_balance( + &master_account, + validate_eth_address(RECIPIENT_ETH_ADDRESS), + CONTRACT_ACC, + ); + assert_eq!(balance, transfer_amount - fee); + + let balance = get_eth_balance( + &master_account, + validate_eth_address(CUSTODIAN_ADDRESS), + CONTRACT_ACC, + ); + assert_eq!(balance, fee); + + let balance = total_supply(&master_account, CONTRACT_ACC); + assert_eq!(balance, DEPOSITED_AMOUNT); + + let balance = total_supply_near(&master_account, CONTRACT_ACC); + assert_eq!(balance, DEPOSITED_AMOUNT - transfer_amount); + + let balance = total_supply_eth(&master_account, CONTRACT_ACC); + assert_eq!(balance, transfer_amount); +} + +#[test] +fn test_deposit_with_same_proof() { + let (_master_account, contract) = init(CUSTODIAN_ADDRESS); + let promises = call_deposit_near(&contract, CONTRACT_ACC); + for p in promises.iter() { + assert!(p.is_some()); + let p = p.as_ref().unwrap(); + p.assert_success() + } + let promises = call_deposit_near(&contract, CONTRACT_ACC); + let promise = &promises[promises.len() - 2]; + assert_execution_status_failure( + promise.as_ref().unwrap().outcome().clone().status, + "ERR_PROOF_EXIST", + "Expected failure as the provided proof already exists, but deposit succeeded", + ); +} + +#[test] +fn test_deposit_wrong_custodian_address() { + let wrong_custodian_address = "0000000000000000000000000000000000000001"; + let (_master_account, contract) = init(wrong_custodian_address); + let promises = call_deposit_near(&contract, CONTRACT_ACC); + let promise = &promises[promises.len() - 2]; + assert_execution_status_failure( + promise.as_ref().unwrap().outcome().clone().status, + "ERR_WRONG_EVENT_ADDRESS", + "Expected failure as the provided proof originated from wrong EthCustodian contract, but deposit succeeded", + ); +} + +#[test] +fn test_ft_transfer_call_without_relayer() { + let (master_account, contract) = init(CUSTODIAN_ADDRESS); + call_deposit_near(&contract, CONTRACT_ACC); + + let balance = get_near_balance(&master_account, DEPOSITED_RECIPIENT, CONTRACT_ACC); + assert_eq!(balance, DEPOSITED_AMOUNT - DEPOSITED_FEE); + + let balance = get_near_balance(&master_account, CONTRACT_ACC, CONTRACT_ACC); + assert_eq!(balance, DEPOSITED_FEE); + + let transfer_amount = 50; + let fee = 30; + let mut msg = U256::from(fee).as_byte_slice().to_vec(); + msg.append(&mut validate_eth_address(RECIPIENT_ETH_ADDRESS).to_vec()); + let relayer_id = "relayer.root"; + let message = [relayer_id, hex::encode(msg).as_str()].join(":"); + let res = contract.call( + CONTRACT_ACC.to_string(), + "ft_transfer_call", + json!({ + "receiver_id": CONTRACT_ACC, + "amount": transfer_amount as u64, + "msg": message, + }) + .to_string() + .as_bytes(), + DEFAULT_GAS, + 1, + ); + res.assert_success(); + + let balance = get_near_balance(&master_account, DEPOSITED_RECIPIENT, CONTRACT_ACC); + assert_eq!(balance, DEPOSITED_AMOUNT - DEPOSITED_FEE); + + let balance = get_near_balance(&master_account, CONTRACT_ACC, CONTRACT_ACC); + assert_eq!(balance, DEPOSITED_FEE - transfer_amount); + + let balance = get_eth_balance( + &master_account, + validate_eth_address(RECIPIENT_ETH_ADDRESS), + CONTRACT_ACC, + ); + assert_eq!(balance, transfer_amount); + + let balance = get_eth_balance( + &master_account, + validate_eth_address(CUSTODIAN_ADDRESS), + CONTRACT_ACC, + ); + assert_eq!(balance, 0); + + let balance = total_supply(&master_account, CONTRACT_ACC); + assert_eq!(balance, DEPOSITED_AMOUNT); + + let balance = total_supply_near(&master_account, CONTRACT_ACC); + assert_eq!(balance, DEPOSITED_AMOUNT - transfer_amount); + + let balance = total_supply_eth(&master_account, CONTRACT_ACC); + assert_eq!(balance, transfer_amount); +} + +#[test] +fn test_ft_transfer_call_fee_greater_than_amount() { + let (master_account, contract) = init(CUSTODIAN_ADDRESS); + call_deposit_near(&contract, CONTRACT_ACC); + + let transfer_amount = 10; + let fee = transfer_amount + 10; + let mut msg = U256::from(fee).as_byte_slice().to_vec(); + msg.append(&mut validate_eth_address(RECIPIENT_ETH_ADDRESS).to_vec()); + let relayer_id = "relayer.root"; + let message = [relayer_id, hex::encode(msg).as_str()].join(":"); + let res = contract.call( + CONTRACT_ACC.to_string(), + "ft_transfer_call", + json!({ + "receiver_id": CONTRACT_ACC, + "amount": transfer_amount as u64, + "msg": message, + }) + .to_string() + .as_bytes(), + DEFAULT_GAS, + 1, + ); + match res.outcome().clone().status { + ExecutionStatus::Failure(_) => {} + _ => panic!(), + } + + let balance = get_near_balance(&master_account, DEPOSITED_RECIPIENT, CONTRACT_ACC); + assert_eq!(balance, DEPOSITED_AMOUNT - DEPOSITED_FEE); + + let balance = get_near_balance(&master_account, CONTRACT_ACC, CONTRACT_ACC); + assert_eq!(balance, DEPOSITED_FEE); + + let balance = get_eth_balance( + &master_account, + validate_eth_address(RECIPIENT_ETH_ADDRESS), + CONTRACT_ACC, + ); + assert_eq!(balance, 0); + + let balance = get_eth_balance( + &master_account, + validate_eth_address(CUSTODIAN_ADDRESS), + CONTRACT_ACC, + ); + assert_eq!(balance, 0); + + let balance = total_supply(&master_account, CONTRACT_ACC); + assert_eq!(balance, DEPOSITED_AMOUNT); + + let balance = total_supply_near(&master_account, CONTRACT_ACC); + assert_eq!(balance, DEPOSITED_AMOUNT); + + let balance = total_supply_eth(&master_account, CONTRACT_ACC); + assert_eq!(balance, 0); +} + +fn call_deposit_with_proof( + account: &UserAccount, + contract: &str, + proof: &str, +) -> Vec> { + let proof: Proof = serde_json::from_str(proof).unwrap(); + let res = account.call( + contract.to_string(), + "deposit", + &proof.try_to_vec().unwrap(), + DEFAULT_GAS, + 0, + ); + res.promise_results() +} + +fn call_set_paused_flags( + account: &UserAccount, + contract: &str, + paused_mask: PausedMask, +) -> ExecutionResult { + let res = account.call( + contract.to_string(), + "set_paused_flags", + &paused_mask.try_to_vec().unwrap(), + DEFAULT_GAS, + 0, + ); + res +} + +fn create_user_account(master_account: &UserAccount) -> UserAccount { + let user_account = master_account.create_user( + "eth_recipient.root".to_string(), + to_yocto("100"), // initial balance + ); + user_account +} + +#[test] +fn test_admin_controlled_only_admin_can_pause() { + let (master_account, contract) = init(CUSTODIAN_ADDRESS); + let user_account = create_user_account(&master_account); + + // Try to pause from the user - should fail + let res = call_set_paused_flags(&user_account, CONTRACT_ACC, PAUSE_DEPOSIT); + let promises = res.promise_results(); + let p = promises[1].clone(); + match p.unwrap().outcome().clone().status { + ExecutionStatus::Failure(_) => {} + _ => panic!("Expected failure as only admin can pause, but user successfully paused"), + } + + // Try to pause from the admin - should succeed + let res = call_set_paused_flags(&contract, CONTRACT_ACC, PAUSE_DEPOSIT); + res.assert_success(); +} + +#[test] +fn test_admin_controlled_admin_can_peform_actions_when_paused() { + let (_master_account, contract) = init(CUSTODIAN_ADDRESS); + + // 1st deposit call when unpaused - should succeed + let promises = call_deposit_with_proof(&contract, CONTRACT_ACC, PROOF_DATA_NEAR); + for p in promises.iter() { + assert!(p.is_some()); + let p = p.as_ref().unwrap(); + p.assert_success() + } + + let withdraw_amount = 100; + let recipient_addr = validate_eth_address(RECIPIENT_ETH_ADDRESS); + + // 1st withdraw call when unpaused - should succeed + let res = contract.call( + CONTRACT_ACC.to_string(), + "withdraw", + &WithdrawCallArgs { + recipient_address: recipient_addr, + amount: withdraw_amount, + } + .try_to_vec() + .unwrap(), + DEFAULT_GAS, + 1, + ); + res.assert_success(); + let promises = res.promise_results(); + for p in promises.iter() { + assert!(p.is_some()); + let p = p.as_ref().unwrap(); + p.assert_success() + } + + // Pause deposit + let res = call_set_paused_flags(&contract, CONTRACT_ACC, PAUSE_DEPOSIT); + res.assert_success(); + + // 2nd deposit call when paused, but the admin is calling it - should succeed + // NB: We can use `PROOF_DATA_ETH` this will be just a different proof but the same deposit + // method which should be paused + let promises = call_deposit_with_proof(&contract, CONTRACT_ACC, PROOF_DATA_ETH); + for p in promises.iter() { + assert!(p.is_some()); + let p = p.as_ref().unwrap(); + p.assert_success() + } + + // Pause withdraw + let res = call_set_paused_flags(&contract, CONTRACT_ACC, PAUSE_WITHDRAW); + res.assert_success(); + + // 2nd withdraw call when paused, but the admin is calling it - should succeed + let res = contract.call( + CONTRACT_ACC.to_string(), + "withdraw", + &WithdrawCallArgs { + recipient_address: recipient_addr, + amount: withdraw_amount, + } + .try_to_vec() + .unwrap(), + DEFAULT_GAS, + 1, + ); + res.assert_success(); + let promises = res.promise_results(); + for p in promises.iter() { + assert!(p.is_some()); + let p = p.as_ref().unwrap(); + p.assert_success() + } +} + +#[test] +fn test_deposit_pausability() { + let (master_account, contract) = init(CUSTODIAN_ADDRESS); + let user_account = create_user_account(&master_account); + + // 1st deposit call - should succeed + let promises = call_deposit_with_proof(&user_account, CONTRACT_ACC, PROOF_DATA_NEAR); + for p in promises.iter() { + assert!(p.is_some()); + let p = p.as_ref().unwrap(); + p.assert_success() + } + + // Pause deposit + let res = call_set_paused_flags(&contract, CONTRACT_ACC, PAUSE_DEPOSIT); + res.assert_success(); + + // 2nd deposit call - should fail + // NB: We can use `PROOF_DATA_ETH` this will be just a different proof but the same deposit + // method which should be paused + let promises = call_deposit_with_proof(&user_account, CONTRACT_ACC, PROOF_DATA_ETH); + let num_promises = promises.len(); + let p = promises[num_promises - 2].clone(); + assert_execution_status_failure( + p.unwrap().outcome().clone().status, + ERR_PAUSED, + "Expected failure due to pause, but deposit succeeded", + ); + + // Unpause all + let res = call_set_paused_flags(&contract, CONTRACT_ACC, UNPAUSE_ALL); + res.assert_success(); + + // 3rd deposit call - should succeed + let promises = call_deposit_with_proof(&user_account, CONTRACT_ACC, PROOF_DATA_ETH); + for p in promises.iter() { + assert!(p.is_some()); + let p = p.as_ref().unwrap(); + p.assert_success() + } +} + +#[test] +fn test_withdraw_near_pausability() { + let (master_account, contract) = init(CUSTODIAN_ADDRESS); + let user_account = create_user_account(&master_account); + + call_deposit_near(&contract, CONTRACT_ACC); + + let withdraw_amount = 100; + let recipient_addr = validate_eth_address(RECIPIENT_ETH_ADDRESS); + // 1st withdraw - should succeed + let res = user_account.call( + CONTRACT_ACC.to_string(), + "withdraw", + &WithdrawCallArgs { + recipient_address: recipient_addr, + amount: withdraw_amount, + } + .try_to_vec() + .unwrap(), + DEFAULT_GAS, + 1, + ); + res.assert_success(); + let promises = res.promise_results(); + assert!(promises.len() > 1); + for p in promises.iter() { + assert!(p.is_some()); + let p = p.as_ref().unwrap(); + p.assert_success() + } + + // Pause withdraw + let res = call_set_paused_flags(&contract, CONTRACT_ACC, PAUSE_WITHDRAW); + res.assert_success(); + + // 2nd withdraw - should fail + let res = user_account.call( + CONTRACT_ACC.to_string(), + "withdraw", + &WithdrawCallArgs { + recipient_address: recipient_addr, + amount: withdraw_amount, + } + .try_to_vec() + .unwrap(), + DEFAULT_GAS, + 1, + ); + let promises = res.promise_results(); + let p = promises[1].clone(); + assert_execution_status_failure( + p.unwrap().outcome().clone().status, + ERR_PAUSED, + "Expected failure due to pause, but withdraw succeeded", + ); + + // Unpause all + let res = call_set_paused_flags(&contract, CONTRACT_ACC, UNPAUSE_ALL); + res.assert_success(); + + let res = user_account.call( + CONTRACT_ACC.to_string(), + "withdraw", + &WithdrawCallArgs { + recipient_address: recipient_addr, + amount: withdraw_amount, + } + .try_to_vec() + .unwrap(), + DEFAULT_GAS, + 1, + ); + res.assert_success(); + let promises = res.promise_results(); + assert!(promises.len() > 1); + for p in promises.iter() { + assert!(p.is_some()); + let p = p.as_ref().unwrap(); + p.assert_success() + } +} + +#[test] +fn test_get_accounts_counter() { + let (master_account, contract) = init(CUSTODIAN_ADDRESS); + call_deposit_near(&contract, CONTRACT_ACC); + + let counter = master_account + .view(CONTRACT_ACC.into(), "get_accounts_counter", &[]) + .unwrap(); + assert_eq!(u64::try_from_slice(&counter[..]).unwrap(), 2); +} + +#[test] +fn test_get_accounts_counter_and_transfer() { + let (master_account, contract) = init(CUSTODIAN_ADDRESS); + call_deposit_near(&contract, CONTRACT_ACC); + + let counter = master_account + .view(CONTRACT_ACC.into(), "get_accounts_counter", &[]) + .unwrap(); + assert_eq!(u64::try_from_slice(&counter[..]).unwrap(), 2); + + let transfer_amount = 70; + let res = contract.call( + CONTRACT_ACC.to_string(), + "ft_transfer", + json!({ + "receiver_id": DEPOSITED_RECIPIENT, + "amount": transfer_amount, + "memo": "transfer memo" + }) + .to_string() + .as_bytes(), + DEFAULT_GAS, + 1, + ); + res.assert_success(); + + let balance = get_near_balance(&master_account, DEPOSITED_RECIPIENT, CONTRACT_ACC); + assert_eq!( + balance, + DEPOSITED_AMOUNT - DEPOSITED_FEE + transfer_amount as u128 + ); + + let balance = get_near_balance(&master_account, CONTRACT_ACC, CONTRACT_ACC); + assert_eq!(balance, DEPOSITED_FEE - transfer_amount as u128); + + let balance = total_supply(&master_account, CONTRACT_ACC); + assert_eq!(balance, DEPOSITED_AMOUNT); + + let balance = total_supply_eth(&master_account, CONTRACT_ACC); + assert_eq!(balance, 0); + + let balance = total_supply_near(&master_account, CONTRACT_ACC); + assert_eq!(balance, DEPOSITED_AMOUNT); + + let counter = master_account + .view(CONTRACT_ACC.into(), "get_accounts_counter", &[]) + .unwrap(); + assert_eq!(u64::try_from_slice(&counter[..]).unwrap(), 2); +} + +#[test] +fn test_deposit_near_with_zero_fee() { + let (master_account, _) = init(CUSTODIAN_ADDRESS); + let proof = r#"{"log_index":0,"log_entry_data":[248,251,148,9,109,233,194,184,165,184,194,44,238,50,137,177,1,246,150,13,104,229,30,248,66,160,209,66,67,156,39,142,37,218,217,165,7,102,241,83,208,227,210,215,191,43,209,111,194,120,28,75,212,148,178,177,90,157,160,0,0,0,0,0,0,0,0,0,0,0,0,121,24,63,219,216,14,45,138,234,26,202,162,246,123,251,138,54,212,10,141,184,160,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,96,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,11,184,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,18,101,116,104,95,114,101,99,105,112,105,101,110,116,46,114,111,111,116,0,0,0,0,0,0,0,0,0,0,0,0,0,0],"receipt_index":0,"receipt_data":[249,2,6,1,130,106,249,185,1,0,0,0,8,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,128,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,128,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,32,0,0,0,0,0,0,0,0,0,0,128,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,8,0,0,0,0,0,0,0,0,0,0,0,0,0,128,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,128,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,248,253,248,251,148,9,109,233,194,184,165,184,194,44,238,50,137,177,1,246,150,13,104,229,30,248,66,160,209,66,67,156,39,142,37,218,217,165,7,102,241,83,208,227,210,215,191,43,209,111,194,120,28,75,212,148,178,177,90,157,160,0,0,0,0,0,0,0,0,0,0,0,0,121,24,63,219,216,14,45,138,234,26,202,162,246,123,251,138,54,212,10,141,184,160,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,96,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,11,184,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,18,101,116,104,95,114,101,99,105,112,105,101,110,116,46,114,111,111,116,0,0,0,0,0,0,0,0,0,0,0,0,0,0],"header_data":[249,2,23,160,7,139,123,21,146,99,81,234,117,153,151,30,67,221,231,90,105,219,121,127,196,224,201,83,178,31,173,155,190,123,227,174,160,29,204,77,232,222,199,93,122,171,133,181,103,182,204,212,26,211,18,69,27,148,138,116,19,240,161,66,253,64,212,147,71,148,109,150,79,199,61,172,73,162,195,49,105,169,235,252,47,207,92,249,136,136,160,227,202,170,144,85,104,169,90,220,93,227,155,76,252,229,223,163,146,127,223,157,121,27,238,116,64,112,216,124,129,107,9,160,158,128,122,7,117,120,186,231,92,224,181,67,43,66,153,79,155,38,238,166,68,1,151,100,134,126,214,86,59,66,174,201,160,235,177,124,164,253,179,174,206,160,196,186,61,51,64,217,35,121,86,229,24,251,162,51,82,72,31,218,240,150,32,157,48,185,1,0,0,0,8,0,0,32,0,0,0,0,0,0,128,0,0,0,2,0,128,0,64,32,0,0,0,0,0,0,64,0,0,10,0,0,0,0,0,0,3,0,0,0,0,64,128,0,0,64,0,0,0,0,0,16,0,0,130,0,1,16,0,32,4,0,0,0,0,0,2,1,0,0,0,0,0,8,0,8,0,0,32,0,4,128,2,0,128,0,0,0,0,0,0,0,0,0,4,32,0,8,2,0,0,0,128,65,0,136,0,0,40,0,0,0,8,0,0,128,0,34,0,4,0,185,2,0,0,4,32,128,0,2,0,0,0,128,0,0,10,0,1,0,1,0,0,0,0,32,1,8,128,0,0,4,0,0,0,128,128,0,70,0,0,0,0,0,0,16,64,0,64,0,34,64,0,0,0,4,0,0,0,0,1,128,0,9,0,0,0,0,0,16,0,0,64,2,0,0,0,132,0,64,32,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,4,0,0,0,32,8,0,16,0,8,0,16,68,0,0,0,16,0,0,0,128,0,64,0,0,128,0,0,0,0,0,0,0,16,0,1,0,16,132,49,181,116,68,131,157,92,101,131,122,18,0,131,101,155,9,132,96,174,110,74,153,216,131,1,10,1,132,103,101,116,104,134,103,111,49,46,49,54,135,119,105,110,100,111,119,115,160,228,82,26,232,236,82,141,6,111,169,92,14,115,254,59,131,192,3,202,209,126,79,140,182,163,12,185,45,210,17,60,38,136,84,114,37,115,236,183,145,213],"proof":[[248,145,160,187,129,186,104,13,250,13,252,114,170,223,247,137,53,113,225,188,217,54,244,108,193,247,236,197,29,0,161,119,76,227,184,160,66,209,234,66,254,223,80,22,246,80,204,38,2,90,115,201,183,79,207,47,192,234,143,221,89,78,36,199,127,9,55,190,160,91,160,251,58,165,255,90,2,105,47,46,220,67,3,52,105,42,182,130,224,19,162,115,159,136,158,218,93,187,148,188,9,128,128,128,128,128,160,181,223,248,223,173,187,103,169,52,204,62,13,90,70,147,236,199,27,201,112,157,4,139,63,188,12,98,117,10,82,85,125,128,128,128,128,128,128,128,128],[249,2,13,48,185,2,9,249,2,6,1,130,106,249,185,1,0,0,0,8,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,128,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,128,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,32,0,0,0,0,0,0,0,0,0,0,128,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,8,0,0,0,0,0,0,0,0,0,0,0,0,0,128,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,128,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,248,253,248,251,148,9,109,233,194,184,165,184,194,44,238,50,137,177,1,246,150,13,104,229,30,248,66,160,209,66,67,156,39,142,37,218,217,165,7,102,241,83,208,227,210,215,191,43,209,111,194,120,28,75,212,148,178,177,90,157,160,0,0,0,0,0,0,0,0,0,0,0,0,121,24,63,219,216,14,45,138,234,26,202,162,246,123,251,138,54,212,10,141,184,160,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,96,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,11,184,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,18,101,116,104,95,114,101,99,105,112,105,101,110,116,46,114,111,111,116,0,0,0,0,0,0,0,0,0,0,0,0,0,0]]}"#; + let proof: Proof = serde_json::from_str(proof).unwrap(); + let res = master_account.call( + CONTRACT_ACC.to_string(), + "deposit", + &proof.try_to_vec().unwrap(), + DEFAULT_GAS, + 0, + ); + res.assert_success(); + + let deposited_amount = 3000; + + let balance = get_near_balance(&master_account, DEPOSITED_RECIPIENT, CONTRACT_ACC); + assert_eq!(balance, deposited_amount); + + let balance = get_near_balance(&master_account, CONTRACT_ACC, CONTRACT_ACC); + assert_eq!(balance, 0); + + let balance = total_supply(&master_account, CONTRACT_ACC); + assert_eq!(balance, deposited_amount); + + let balance = total_supply_near(&master_account, CONTRACT_ACC); + assert_eq!(balance, deposited_amount); + + let balance = total_supply_eth(&master_account, CONTRACT_ACC); + assert_eq!(balance, 0); +} + +#[test] +fn test_deposit_evm_with_zero_fee() { + let (master_account, contract) = init(EVM_CUSTODIAN_ADDRESS); + let res = contract.call( + CONTRACT_ACC.to_string(), + "register_relayer", + &RegisterRelayerCallArgs { + address: validate_eth_address(CUSTODIAN_ADDRESS), + } + .try_to_vec() + .unwrap(), + DEFAULT_GAS, + 0, + ); + res.assert_success(); + + let proof = r#"{"log_index":0,"log_entry_data":[249,1,27,148,9,109,233,194,184,165,184,194,44,238,50,137,177,1,246,150,13,104,229,30,248,66,160,209,66,67,156,39,142,37,218,217,165,7,102,241,83,208,227,210,215,191,43,209,111,194,120,28,75,212,148,178,177,90,157,160,0,0,0,0,0,0,0,0,0,0,0,0,121,24,63,219,216,14,45,138,234,26,202,162,246,123,251,138,54,212,10,141,184,192,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,96,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,7,208,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,59,101,116,104,95,99,111,110,110,101,99,116,111,114,46,114,111,111,116,58,56,57,49,66,50,55,52,57,50,51,56,66,50,55,102,70,53,56,101,57,53,49,48,56,56,101,53,53,98,48,52,100,101,55,49,68,99,51,55,52,0,0,0,0,0],"receipt_index":3,"receipt_data":[249,2,41,1,131,2,246,200,185,1,0,0,0,8,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,128,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,128,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,32,0,0,0,0,0,0,0,0,0,0,128,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,8,0,0,0,0,0,0,0,0,0,0,0,0,0,128,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,128,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,249,1,30,249,1,27,148,9,109,233,194,184,165,184,194,44,238,50,137,177,1,246,150,13,104,229,30,248,66,160,209,66,67,156,39,142,37,218,217,165,7,102,241,83,208,227,210,215,191,43,209,111,194,120,28,75,212,148,178,177,90,157,160,0,0,0,0,0,0,0,0,0,0,0,0,121,24,63,219,216,14,45,138,234,26,202,162,246,123,251,138,54,212,10,141,184,192,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,96,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,7,208,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,59,101,116,104,95,99,111,110,110,101,99,116,111,114,46,114,111,111,116,58,56,57,49,66,50,55,52,57,50,51,56,66,50,55,102,70,53,56,101,57,53,49,48,56,56,101,53,53,98,48,52,100,101,55,49,68,99,51,55,52,0,0,0,0,0],"header_data":[249,2,23,160,110,48,40,236,52,198,197,25,255,191,199,4,137,3,185,31,202,84,90,80,104,32,176,13,144,141,165,183,36,30,94,138,160,29,204,77,232,222,199,93,122,171,133,181,103,182,204,212,26,211,18,69,27,148,138,116,19,240,161,66,253,64,212,147,71,148,148,156,193,169,167,156,148,249,191,22,225,202,121,212,79,2,197,75,191,164,160,127,26,168,212,111,22,173,213,25,217,187,227,114,86,173,99,166,195,67,16,104,111,200,109,110,147,241,23,71,122,89,215,160,47,120,179,75,110,158,228,18,242,156,38,111,95,25,236,211,158,53,53,62,89,190,2,40,220,41,151,200,127,219,33,219,160,222,177,165,249,98,109,130,37,226,229,165,113,45,12,145,30,16,28,154,86,22,203,218,233,13,246,165,177,61,57,68,83,185,1,0,0,32,8,0,33,0,0,0,64,0,32,0,128,0,0,0,132,0,0,0,64,32,64,0,0,1,0,32,64,0,0,8,0,0,0,0,0,0,137,32,0,0,0,64,128,0,0,16,0,0,0,0,33,64,0,1,0,0,0,0,0,0,0,0,68,0,0,0,2,1,64,0,0,0,0,9,16,0,0,32,0,0,0,128,2,0,0,0,33,0,0,0,128,0,0,0,12,64,32,8,66,2,0,0,64,0,0,8,0,0,40,8,8,0,0,0,0,16,0,0,0,0,64,49,0,0,8,0,96,0,0,18,0,0,0,0,0,64,10,0,1,0,0,32,0,0,0,33,0,0,128,136,10,64,0,64,0,0,192,128,0,0,64,1,0,0,4,0,8,0,64,0,34,0,0,0,0,0,0,0,0,0,0,0,8,8,0,4,0,0,0,32,0,4,0,2,0,0,0,129,4,0,96,16,4,8,0,0,0,0,0,0,1,0,128,16,0,0,2,0,4,0,32,0,8,0,0,0,0,16,0,1,0,0,0,0,64,0,128,0,0,32,36,128,0,0,4,64,0,8,8,16,0,1,4,16,132,50,32,156,229,131,157,92,137,131,122,18,0,131,35,159,183,132,96,174,111,126,153,216,131,1,10,3,132,103,101,116,104,136,103,111,49,46,49,54,46,51,133,108,105,110,117,120,160,59,74,90,253,211,14,166,114,39,213,120,95,221,43,109,173,72,205,160,203,71,44,83,159,36,59,129,84,32,16,254,251,136,49,16,97,244,161,246,244,85],"proof":[[248,113,160,227,103,29,228,16,56,196,146,115,29,122,202,254,140,214,86,189,108,47,197,2,195,50,211,4,126,58,175,71,11,70,78,160,229,239,23,242,100,150,90,169,21,162,252,207,202,244,187,71,172,126,191,33,166,162,45,134,108,114,6,76,78,177,148,140,128,128,128,128,128,128,160,21,91,249,81,132,162,52,236,128,181,5,72,158,228,177,131,87,144,64,194,111,103,180,16,183,103,245,136,125,213,208,76,128,128,128,128,128,128,128,128],[249,1,241,128,160,52,154,34,8,39,210,121,1,151,92,91,225,198,154,204,207,11,204,187,59,223,154,187,102,115,110,193,141,201,198,95,253,160,218,19,188,241,210,48,51,3,76,125,48,152,171,188,45,136,109,71,236,171,242,162,10,34,245,160,191,5,120,9,80,129,160,147,160,142,184,113,171,112,171,131,124,150,117,65,27,207,149,119,136,120,65,7,99,155,114,169,57,91,125,26,117,49,67,160,173,217,104,114,149,170,18,227,251,73,78,11,220,243,240,66,117,32,199,64,138,173,169,43,8,122,39,47,210,54,41,192,160,139,116,124,73,113,242,225,65,167,48,33,13,149,51,152,196,79,93,126,103,116,48,177,25,80,186,34,55,15,116,2,13,160,67,10,207,13,108,228,254,73,175,10,166,107,144,157,150,135,173,179,140,112,129,205,168,132,194,4,191,175,239,50,66,245,160,26,193,195,232,40,106,60,72,133,32,204,205,104,90,20,60,166,16,214,184,115,44,216,62,82,30,141,124,160,72,173,62,160,67,5,174,33,105,28,248,245,48,15,129,153,96,27,97,125,29,194,233,139,228,8,243,221,79,2,151,52,75,30,47,136,160,103,94,192,58,117,224,88,80,21,183,254,178,135,21,78,20,233,250,7,22,243,14,41,56,12,118,206,224,75,42,96,77,160,225,64,237,254,248,145,134,195,166,49,205,129,233,54,142,136,235,242,10,14,175,76,73,131,26,135,102,237,64,23,102,213,160,167,104,45,101,228,93,89,216,167,142,125,0,216,77,167,4,245,156,140,98,117,19,165,25,185,204,84,161,175,153,193,20,160,53,22,192,197,176,225,102,6,251,115,216,238,53,110,254,106,193,134,232,100,173,93,211,71,195,10,192,107,97,190,165,12,160,104,206,244,51,77,131,79,209,64,233,97,35,142,75,42,205,198,120,222,90,199,168,126,235,12,225,30,240,214,56,253,168,160,230,94,127,56,22,169,3,159,236,49,217,88,2,175,168,22,104,177,154,127,106,165,176,238,236,141,83,64,123,28,177,206,160,140,137,2,195,227,9,182,245,76,62,215,174,168,254,15,125,111,241,30,50,110,189,66,58,230,2,252,104,182,247,223,94,128],[249,2,48,32,185,2,44,249,2,41,1,131,2,246,200,185,1,0,0,0,8,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,128,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,128,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,32,0,0,0,0,0,0,0,0,0,0,128,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,8,0,0,0,0,0,0,0,0,0,0,0,0,0,128,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,128,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,249,1,30,249,1,27,148,9,109,233,194,184,165,184,194,44,238,50,137,177,1,246,150,13,104,229,30,248,66,160,209,66,67,156,39,142,37,218,217,165,7,102,241,83,208,227,210,215,191,43,209,111,194,120,28,75,212,148,178,177,90,157,160,0,0,0,0,0,0,0,0,0,0,0,0,121,24,63,219,216,14,45,138,234,26,202,162,246,123,251,138,54,212,10,141,184,192,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,96,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,7,208,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,59,101,116,104,95,99,111,110,110,101,99,116,111,114,46,114,111,111,116,58,56,57,49,66,50,55,52,57,50,51,56,66,50,55,102,70,53,56,101,57,53,49,48,56,56,101,53,53,98,48,52,100,101,55,49,68,99,51,55,52,0,0,0,0,0]]}"#; + let proof: Proof = serde_json::from_str(proof).unwrap(); + let res = master_account.call( + CONTRACT_ACC.to_string(), + "deposit", + &proof.try_to_vec().unwrap(), + DEFAULT_GAS, + 0, + ); + res.assert_success(); + + let deposited_amount = 2000; + + let balance = get_eth_balance( + &master_account, + validate_eth_address(RECIPIENT_ETH_ADDRESS), + CONTRACT_ACC, + ); + assert_eq!(balance, deposited_amount); + + let balance = get_eth_balance( + &master_account, + validate_eth_address(CUSTODIAN_ADDRESS), + CONTRACT_ACC, + ); + assert_eq!(balance, 0); + + let balance = total_supply(&master_account, CONTRACT_ACC); + assert_eq!(balance, deposited_amount); + + let balance = total_supply_eth(&master_account, CONTRACT_ACC); + assert_eq!(balance, deposited_amount); + + let balance = total_supply_near(&master_account, CONTRACT_ACC); + assert_eq!(balance, 0); +} + +#[test] +fn test_deposit_near_amount_less_fee() { + let custodian_address = "73c8931CA2aD746d97a59A7ABDDa0a9205F7ffF9"; + let (master_account, _) = init(custodian_address); + let proof = r#"{"log_index":0,"log_entry_data":[248,251,148,115,200,147,28,162,173,116,109,151,165,154,122,189,218,10,146,5,247,255,249,248,66,160,209,66,67,156,39,142,37,218,217,165,7,102,241,83,208,227,210,215,191,43,209,111,194,120,28,75,212,148,178,177,90,157,160,0,0,0,0,0,0,0,0,0,0,0,0,121,24,63,219,216,14,45,138,234,26,202,162,246,123,251,138,54,212,10,141,184,160,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,96,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,150,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,2,88,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,18,101,116,104,95,114,101,99,105,112,105,101,110,116,46,114,111,111,116,0,0,0,0,0,0,0,0,0,0,0,0,0,0],"receipt_index":0,"receipt_data":[249,2,6,1,130,106,251,185,1,0,0,0,8,0,0,0,0,0,0,0,0,0,0,0,0,0,0,128,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,32,0,0,0,0,0,0,0,0,0,0,128,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,8,0,0,0,0,0,0,8,0,0,0,0,0,0,0,0,0,0,0,0,0,128,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,128,0,0,0,0,0,0,0,0,0,0,0,0,0,0,128,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,248,253,248,251,148,115,200,147,28,162,173,116,109,151,165,154,122,189,218,10,146,5,247,255,249,248,66,160,209,66,67,156,39,142,37,218,217,165,7,102,241,83,208,227,210,215,191,43,209,111,194,120,28,75,212,148,178,177,90,157,160,0,0,0,0,0,0,0,0,0,0,0,0,121,24,63,219,216,14,45,138,234,26,202,162,246,123,251,138,54,212,10,141,184,160,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,96,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,150,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,2,88,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,18,101,116,104,95,114,101,99,105,112,105,101,110,116,46,114,111,111,116,0,0,0,0,0,0,0,0,0,0,0,0,0,0],"header_data":[249,2,10,160,139,92,51,142,163,95,21,160,61,29,148,206,54,147,187,96,77,109,244,8,130,155,249,198,206,30,173,216,144,176,252,123,160,29,204,77,232,222,199,93,122,171,133,181,103,182,204,212,26,211,18,69,27,148,138,116,19,240,161,66,253,64,212,147,71,148,124,28,230,160,8,239,64,193,62,78,177,68,166,204,116,240,224,174,172,126,160,218,9,209,192,173,39,133,109,141,57,2,146,184,12,94,217,6,138,173,67,121,185,24,179,133,189,219,40,81,210,73,106,160,219,108,244,199,44,203,84,71,126,74,82,240,203,255,238,20,226,29,239,51,7,19,144,34,156,137,232,159,71,30,164,29,160,209,61,241,33,17,103,192,203,57,156,112,250,18,166,26,237,248,153,226,185,87,220,156,93,249,17,39,190,125,96,247,239,185,1,0,0,0,8,0,0,0,0,0,0,0,0,1,0,0,0,0,0,128,0,0,0,2,0,0,0,0,0,0,0,0,0,0,0,0,0,8,0,8,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,8,0,0,0,0,0,0,0,32,0,0,0,0,8,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,16,32,0,0,0,0,0,0,0,0,0,0,0,0,0,0,64,0,0,32,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,128,0,0,0,0,32,0,0,0,0,8,0,0,2,0,0,128,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,8,8,0,0,0,0,0,0,40,0,0,0,0,0,0,0,0,0,0,0,0,0,144,4,0,0,64,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,128,0,0,128,0,0,0,0,0,0,0,0,0,0,0,0,0,0,128,0,16,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,132,91,80,110,139,131,157,118,104,131,122,18,0,131,30,4,87,132,96,175,154,220,140,115,112,105,100,101,114,49,48,1,2,9,64,160,80,163,212,151,183,11,70,219,178,190,167,172,64,187,47,14,29,226,253,132,116,145,81,143,54,249,121,123,193,241,120,249,136,244,120,239,134,243,43,177,139],"proof":[[248,81,160,164,35,68,182,184,52,174,73,6,81,4,92,187,190,187,106,255,124,123,24,244,168,161,247,60,181,75,29,192,175,96,140,128,128,128,128,128,128,128,160,169,157,199,164,106,205,109,88,111,183,255,180,108,15,155,137,126,163,108,44,117,125,138,221,3,188,93,85,146,129,19,139,128,128,128,128,128,128,128,128],[249,2,13,48,185,2,9,249,2,6,1,130,106,251,185,1,0,0,0,8,0,0,0,0,0,0,0,0,0,0,0,0,0,0,128,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,32,0,0,0,0,0,0,0,0,0,0,128,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,8,0,0,0,0,0,0,8,0,0,0,0,0,0,0,0,0,0,0,0,0,128,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,128,0,0,0,0,0,0,0,0,0,0,0,0,0,0,128,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,248,253,248,251,148,115,200,147,28,162,173,116,109,151,165,154,122,189,218,10,146,5,247,255,249,248,66,160,209,66,67,156,39,142,37,218,217,165,7,102,241,83,208,227,210,215,191,43,209,111,194,120,28,75,212,148,178,177,90,157,160,0,0,0,0,0,0,0,0,0,0,0,0,121,24,63,219,216,14,45,138,234,26,202,162,246,123,251,138,54,212,10,141,184,160,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,96,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,150,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,2,88,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,18,101,116,104,95,114,101,99,105,112,105,101,110,116,46,114,111,111,116,0,0,0,0,0,0,0,0,0,0,0,0,0,0]]}"#; + let proof: Proof = serde_json::from_str(proof).unwrap(); + let res = master_account.call( + CONTRACT_ACC.to_string(), + "deposit", + &proof.try_to_vec().unwrap(), + DEFAULT_GAS, + 0, + ); + let promise = &res.promise_results()[res.promise_results().len() - 2]; + assert_execution_status_failure( + promise.as_ref().unwrap().outcome().clone().status, + ERR_NOT_ENOUGH_BALANCE_FOR_FEE, + "Expected failure as the deposited amount is less than fee, but deposit to NEP-141 succeeded", + ); +} + +#[test] +fn test_deposit_evm_amount_less_fee() { + let custodian_address = "73c8931CA2aD746d97a59A7ABDDa0a9205F7ffF9"; + let (master_account, _) = init(custodian_address); + let proof = r#"{"log_index":0,"log_entry_data":[249,1,27,148,115,200,147,28,162,173,116,109,151,165,154,122,189,218,10,146,5,247,255,249,248,66,160,209,66,67,156,39,142,37,218,217,165,7,102,241,83,208,227,210,215,191,43,209,111,194,120,28,75,212,148,178,177,90,157,160,0,0,0,0,0,0,0,0,0,0,0,0,121,24,63,219,216,14,45,138,234,26,202,162,246,123,251,138,54,212,10,141,184,192,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,96,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,150,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,3,132,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,59,101,116,104,95,99,111,110,110,101,99,116,111,114,46,114,111,111,116,58,56,57,49,66,50,55,52,57,50,51,56,66,50,55,102,70,53,56,101,57,53,49,48,56,56,101,53,53,98,48,52,100,101,55,49,68,99,51,55,52,0,0,0,0,0],"receipt_index":0,"receipt_data":[249,2,40,1,130,121,119,185,1,0,0,0,8,0,0,0,0,0,0,0,0,0,0,0,0,0,0,128,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,32,0,0,0,0,0,0,0,0,0,0,128,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,8,0,0,0,0,0,0,8,0,0,0,0,0,0,0,0,0,0,0,0,0,128,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,128,0,0,0,0,0,0,0,0,0,0,0,0,0,0,128,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,249,1,30,249,1,27,148,115,200,147,28,162,173,116,109,151,165,154,122,189,218,10,146,5,247,255,249,248,66,160,209,66,67,156,39,142,37,218,217,165,7,102,241,83,208,227,210,215,191,43,209,111,194,120,28,75,212,148,178,177,90,157,160,0,0,0,0,0,0,0,0,0,0,0,0,121,24,63,219,216,14,45,138,234,26,202,162,246,123,251,138,54,212,10,141,184,192,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,96,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,150,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,3,132,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,59,101,116,104,95,99,111,110,110,101,99,116,111,114,46,114,111,111,116,58,56,57,49,66,50,55,52,57,50,51,56,66,50,55,102,70,53,56,101,57,53,49,48,56,56,101,53,53,98,48,52,100,101,55,49,68,99,51,55,52,0,0,0,0,0],"header_data":[249,2,10,160,234,97,221,132,104,51,119,219,129,206,197,27,130,197,14,113,167,32,152,214,207,205,156,210,35,213,198,227,116,42,51,224,160,29,204,77,232,222,199,93,122,171,133,181,103,182,204,212,26,211,18,69,27,148,138,116,19,240,161,66,253,64,212,147,71,148,124,28,230,160,8,239,64,193,62,78,177,68,166,204,116,240,224,174,172,126,160,15,150,233,184,181,140,226,81,205,139,229,87,226,149,49,207,117,33,36,83,124,8,75,199,231,48,13,23,189,217,179,12,160,241,37,169,74,233,62,231,112,0,207,95,228,68,240,108,254,57,199,255,130,142,158,161,180,243,50,255,222,77,251,252,126,160,31,111,236,60,142,91,35,119,195,92,158,134,65,138,8,247,98,122,229,21,226,85,38,130,141,139,168,60,83,90,63,244,185,1,0,0,0,8,0,0,0,0,0,0,0,0,0,128,0,0,0,0,128,0,0,0,32,0,0,0,0,0,0,64,0,0,10,0,0,0,0,0,0,1,0,0,0,0,64,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,2,1,0,0,0,0,0,8,0,0,2,0,0,0,4,0,2,0,0,0,0,0,0,0,0,0,0,0,4,0,0,8,2,0,0,0,0,0,0,136,0,4,40,0,0,0,0,0,0,0,0,0,0,0,0,48,0,0,0,0,32,0,0,10,0,0,0,0,0,0,10,0,1,0,0,0,0,0,0,32,0,0,128,0,0,0,0,0,0,0,128,0,0,0,0,0,0,0,0,16,0,0,64,0,34,0,0,0,0,0,8,0,0,0,0,0,0,8,0,0,0,0,0,0,0,0,128,2,0,0,0,128,0,1,32,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,4,0,0,0,32,128,0,0,0,0,0,16,0,0,0,0,0,0,0,0,128,0,0,0,0,128,0,0,0,0,0,0,0,16,0,1,0,16,132,91,127,63,197,131,157,118,142,131,122,18,0,131,25,25,181,132,96,175,156,157,140,115,112,105,100,101,114,49,48,1,2,9,64,160,68,227,115,157,18,184,21,217,93,74,196,34,230,228,210,239,61,26,221,245,191,46,44,135,134,2,20,53,95,18,128,54,136,162,198,27,59,153,146,63,16],"proof":[[248,113,160,204,110,241,220,150,206,51,121,104,130,125,127,249,35,9,242,107,45,164,62,147,221,93,116,73,79,49,96,226,92,235,247,160,43,215,154,177,148,177,15,202,141,217,45,114,108,33,74,0,144,126,189,26,78,152,232,105,119,103,203,51,79,45,113,124,128,128,128,128,128,128,160,74,177,164,103,85,250,153,17,105,68,205,207,176,48,89,230,100,35,20,167,34,117,11,115,14,107,128,214,48,17,53,209,128,128,128,128,128,128,128,128],[249,2,47,48,185,2,43,249,2,40,1,130,121,119,185,1,0,0,0,8,0,0,0,0,0,0,0,0,0,0,0,0,0,0,128,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,32,0,0,0,0,0,0,0,0,0,0,128,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,8,0,0,0,0,0,0,8,0,0,0,0,0,0,0,0,0,0,0,0,0,128,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,128,0,0,0,0,0,0,0,0,0,0,0,0,0,0,128,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,249,1,30,249,1,27,148,115,200,147,28,162,173,116,109,151,165,154,122,189,218,10,146,5,247,255,249,248,66,160,209,66,67,156,39,142,37,218,217,165,7,102,241,83,208,227,210,215,191,43,209,111,194,120,28,75,212,148,178,177,90,157,160,0,0,0,0,0,0,0,0,0,0,0,0,121,24,63,219,216,14,45,138,234,26,202,162,246,123,251,138,54,212,10,141,184,192,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,96,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,150,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,3,132,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,59,101,116,104,95,99,111,110,110,101,99,116,111,114,46,114,111,111,116,58,56,57,49,66,50,55,52,57,50,51,56,66,50,55,102,70,53,56,101,57,53,49,48,56,56,101,53,53,98,48,52,100,101,55,49,68,99,51,55,52,0,0,0,0,0]]}"#; + let proof: Proof = serde_json::from_str(proof).unwrap(); + let res = master_account.call( + CONTRACT_ACC.to_string(), + "deposit", + &proof.try_to_vec().unwrap(), + DEFAULT_GAS, + 0, + ); + let promise = &res.promise_results()[res.promise_results().len() - 2]; + assert_execution_status_failure( + promise.as_ref().unwrap().outcome().clone().status, + ERR_NOT_ENOUGH_BALANCE_FOR_FEE, + "Expected failure as the deposited amount is less than fee, but deposit to Aurora succeeded", + ); +} + +#[test] +fn test_deposit_near_amount_zero_fee_non_zero() { + let custodian_address = "73c8931CA2aD746d97a59A7ABDDa0a9205F7ffF9"; + let (master_account, _) = init(custodian_address); + let proof = r#"{"log_index":0,"log_entry_data":[248,251,148,115,200,147,28,162,173,116,109,151,165,154,122,189,218,10,146,5,247,255,249,248,66,160,209,66,67,156,39,142,37,218,217,165,7,102,241,83,208,227,210,215,191,43,209,111,194,120,28,75,212,148,178,177,90,157,160,0,0,0,0,0,0,0,0,0,0,0,0,121,24,63,219,216,14,45,138,234,26,202,162,246,123,251,138,54,212,10,141,184,160,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,96,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,244,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,18,101,116,104,95,114,101,99,105,112,105,101,110,116,46,114,111,111,116,0,0,0,0,0,0,0,0,0,0,0,0,0,0],"receipt_index":0,"receipt_data":[249,2,6,1,130,106,251,185,1,0,0,0,8,0,0,0,0,0,0,0,0,0,0,0,0,0,0,128,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,32,0,0,0,0,0,0,0,0,0,0,128,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,8,0,0,0,0,0,0,8,0,0,0,0,0,0,0,0,0,0,0,0,0,128,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,128,0,0,0,0,0,0,0,0,0,0,0,0,0,0,128,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,248,253,248,251,148,115,200,147,28,162,173,116,109,151,165,154,122,189,218,10,146,5,247,255,249,248,66,160,209,66,67,156,39,142,37,218,217,165,7,102,241,83,208,227,210,215,191,43,209,111,194,120,28,75,212,148,178,177,90,157,160,0,0,0,0,0,0,0,0,0,0,0,0,121,24,63,219,216,14,45,138,234,26,202,162,246,123,251,138,54,212,10,141,184,160,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,96,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,244,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,18,101,116,104,95,114,101,99,105,112,105,101,110,116,46,114,111,111,116,0,0,0,0,0,0,0,0,0,0,0,0,0,0],"header_data":[249,2,10,160,47,76,8,45,83,192,115,218,108,188,181,117,148,40,254,44,169,118,92,188,207,7,122,246,133,75,100,184,134,128,91,12,160,29,204,77,232,222,199,93,122,171,133,181,103,182,204,212,26,211,18,69,27,148,138,116,19,240,161,66,253,64,212,147,71,148,124,28,230,160,8,239,64,193,62,78,177,68,166,204,116,240,224,174,172,126,160,225,211,110,129,173,98,101,150,55,116,11,30,26,161,226,8,234,249,90,46,245,112,225,68,76,26,215,135,27,181,140,22,160,229,44,239,5,102,141,42,118,174,163,144,225,90,152,120,60,150,25,144,217,154,234,25,69,35,226,103,149,188,127,81,106,160,177,89,93,76,113,24,117,182,174,52,148,6,239,129,151,18,222,56,245,9,232,80,7,129,118,118,108,72,76,247,238,101,185,1,0,1,4,200,10,0,0,0,0,8,0,32,0,128,3,1,0,0,145,4,33,72,8,0,2,0,128,0,18,64,26,38,0,4,16,8,1,136,65,40,32,0,0,1,72,0,2,0,128,0,64,0,0,48,0,32,0,0,0,0,192,0,100,9,0,12,0,16,0,0,1,2,8,8,0,8,12,128,64,0,192,2,0,0,64,2,68,129,0,128,1,0,0,128,128,68,0,64,64,32,0,67,0,32,0,0,41,20,1,0,16,40,0,16,16,32,0,0,0,128,0,0,0,64,48,4,8,8,0,0,0,0,66,32,64,0,0,48,0,16,8,1,64,0,0,16,32,0,33,32,0,0,128,0,2,2,128,0,0,192,0,2,40,0,0,0,0,0,1,0,67,1,0,131,32,6,8,0,0,8,96,128,0,0,0,0,12,0,0,0,65,2,160,2,64,0,2,4,32,0,128,0,1,34,0,105,0,160,0,32,18,32,16,1,0,0,0,20,0,32,0,20,0,96,128,0,16,0,0,64,16,2,192,1,0,4,32,0,32,130,2,0,0,32,0,0,0,4,64,12,64,0,0,4,0,0,1,132,93,96,3,163,131,157,117,205,131,122,18,0,131,113,87,104,132,96,175,145,182,140,115,112,105,100,101,114,49,48,1,2,9,64,160,179,183,88,73,3,20,234,255,8,238,6,186,173,204,149,149,235,233,232,35,158,194,53,246,218,39,221,246,90,7,34,255,136,176,36,100,161,146,27,98,29],"proof":[[248,177,160,93,101,188,48,5,53,36,126,41,0,92,130,188,117,104,230,178,29,27,194,22,86,212,235,193,20,241,42,157,88,117,205,160,141,83,180,197,22,126,217,34,74,50,114,118,42,157,161,171,8,158,98,92,183,124,137,130,211,1,106,44,222,37,13,32,160,62,131,146,138,69,63,89,98,140,64,187,93,207,160,0,4,134,154,205,47,168,231,136,249,129,230,137,29,3,210,67,173,160,76,91,176,245,81,3,198,111,175,230,185,70,220,111,189,88,15,154,173,107,239,121,185,13,159,197,61,37,231,252,22,200,128,128,128,128,160,13,246,139,212,38,202,103,201,31,80,247,136,186,58,17,52,66,119,115,128,23,123,59,166,177,68,79,182,9,242,60,106,128,128,128,128,128,128,128,128],[249,2,13,48,185,2,9,249,2,6,1,130,106,251,185,1,0,0,0,8,0,0,0,0,0,0,0,0,0,0,0,0,0,0,128,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,32,0,0,0,0,0,0,0,0,0,0,128,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,8,0,0,0,0,0,0,8,0,0,0,0,0,0,0,0,0,0,0,0,0,128,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,128,0,0,0,0,0,0,0,0,0,0,0,0,0,0,128,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,248,253,248,251,148,115,200,147,28,162,173,116,109,151,165,154,122,189,218,10,146,5,247,255,249,248,66,160,209,66,67,156,39,142,37,218,217,165,7,102,241,83,208,227,210,215,191,43,209,111,194,120,28,75,212,148,178,177,90,157,160,0,0,0,0,0,0,0,0,0,0,0,0,121,24,63,219,216,14,45,138,234,26,202,162,246,123,251,138,54,212,10,141,184,160,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,96,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,244,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,18,101,116,104,95,114,101,99,105,112,105,101,110,116,46,114,111,111,116,0,0,0,0,0,0,0,0,0,0,0,0,0,0]]}"#; + let proof: Proof = serde_json::from_str(proof).unwrap(); + let res = master_account.call( + CONTRACT_ACC.to_string(), + "deposit", + &proof.try_to_vec().unwrap(), + DEFAULT_GAS, + 0, + ); + let promise = &res.promise_results()[res.promise_results().len() - 2]; + assert_execution_status_failure( + promise.as_ref().unwrap().outcome().clone().status, + ERR_NOT_ENOUGH_BALANCE_FOR_FEE, + "Expected failure as the deposited amount is zero and the fee is not zero, but deposit to NEP-141 succeeded", + ); +} + +#[test] +fn test_deposit_evm_amount_zero_fee_non_zero() { + let custodian_address = "73c8931CA2aD746d97a59A7ABDDa0a9205F7ffF9"; + let (master_account, _) = init(custodian_address); + let proof = r#"{"log_index":0,"log_entry_data":[249,1,27,148,115,200,147,28,162,173,116,109,151,165,154,122,189,218,10,146,5,247,255,249,248,66,160,209,66,67,156,39,142,37,218,217,165,7,102,241,83,208,227,210,215,191,43,209,111,194,120,28,75,212,148,178,177,90,157,160,0,0,0,0,0,0,0,0,0,0,0,0,121,24,63,219,216,14,45,138,234,26,202,162,246,123,251,138,54,212,10,141,184,192,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,96,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,174,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,59,101,116,104,95,99,111,110,110,101,99,116,111,114,46,114,111,111,116,58,56,57,49,66,50,55,52,57,50,51,56,66,50,55,102,70,53,56,101,57,53,49,48,56,56,101,53,53,98,48,52,100,101,55,49,68,99,51,55,52,0,0,0,0,0],"receipt_index":1,"receipt_data":[249,2,41,1,131,1,110,54,185,1,0,0,0,8,0,0,0,0,0,0,0,0,0,0,0,0,0,0,128,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,32,0,0,0,0,0,0,0,0,0,0,128,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,8,0,0,0,0,0,0,8,0,0,0,0,0,0,0,0,0,0,0,0,0,128,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,128,0,0,0,0,0,0,0,0,0,0,0,0,0,0,128,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,249,1,30,249,1,27,148,115,200,147,28,162,173,116,109,151,165,154,122,189,218,10,146,5,247,255,249,248,66,160,209,66,67,156,39,142,37,218,217,165,7,102,241,83,208,227,210,215,191,43,209,111,194,120,28,75,212,148,178,177,90,157,160,0,0,0,0,0,0,0,0,0,0,0,0,121,24,63,219,216,14,45,138,234,26,202,162,246,123,251,138,54,212,10,141,184,192,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,96,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,174,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,59,101,116,104,95,99,111,110,110,101,99,116,111,114,46,114,111,111,116,58,56,57,49,66,50,55,52,57,50,51,56,66,50,55,102,70,53,56,101,57,53,49,48,56,56,101,53,53,98,48,52,100,101,55,49,68,99,51,55,52,0,0,0,0,0],"header_data":[249,2,21,160,60,128,9,36,168,69,207,249,164,88,177,15,74,221,137,160,110,246,3,133,209,132,169,179,31,86,142,216,160,11,162,137,160,29,204,77,232,222,199,93,122,171,133,181,103,182,204,212,26,211,18,69,27,148,138,116,19,240,161,66,253,64,212,147,71,148,28,255,226,5,233,121,118,187,157,30,192,6,245,34,35,96,168,147,83,224,160,182,206,231,252,255,115,166,11,152,156,84,169,204,36,0,94,3,17,113,103,104,252,225,161,115,85,74,227,104,249,187,232,160,211,106,68,136,2,141,5,14,201,111,68,218,251,84,103,176,66,10,190,123,58,119,216,141,192,197,222,181,211,87,117,192,160,162,200,112,106,166,13,220,187,223,164,251,102,104,106,40,84,17,101,93,131,125,204,193,62,96,110,167,214,54,41,154,191,185,1,0,0,40,72,0,32,0,0,0,0,0,0,5,128,2,0,8,0,128,144,136,0,34,0,0,32,1,0,0,64,16,0,10,0,16,8,28,0,17,9,0,0,0,0,72,0,16,4,0,0,0,0,128,2,18,0,0,0,0,1,16,0,36,0,1,1,32,8,0,2,1,0,64,64,0,0,8,0,16,0,40,2,0,13,0,2,8,0,0,0,8,0,0,16,0,4,16,36,0,52,8,130,128,8,0,0,0,0,10,0,2,40,64,0,34,32,2,0,2,0,0,0,0,0,48,4,32,128,0,32,0,0,2,96,0,0,0,0,64,10,0,33,64,0,0,0,66,0,32,0,0,192,138,0,0,0,70,0,129,128,0,66,32,0,0,16,64,0,0,0,0,97,0,34,0,6,0,0,32,8,0,1,200,128,48,0,41,128,0,128,0,224,0,0,0,0,2,0,64,0,148,0,0,32,72,8,0,96,0,36,128,25,48,33,0,128,16,0,0,4,2,128,4,32,144,0,20,0,0,0,16,2,0,4,0,2,8,0,0,128,0,16,0,0,128,0,0,16,0,128,0,72,16,0,129,0,80,132,91,116,53,37,131,157,118,157,131,122,18,0,131,48,97,222,132,96,175,157,102,151,214,131,1,10,2,132,103,101,116,104,134,103,111,49,46,49,54,133,108,105,110,117,120,160,218,71,54,233,233,153,85,103,64,10,4,159,150,224,130,134,111,78,188,224,102,166,96,148,216,222,134,254,219,185,88,110,136,87,173,68,252,252,248,190,64],"proof":[[248,177,160,174,171,108,131,83,47,244,139,23,122,146,226,84,189,175,114,176,131,196,80,85,155,220,172,151,31,138,121,78,34,1,37,160,104,209,167,107,221,53,22,163,251,61,251,80,40,239,108,253,251,47,253,90,163,103,58,194,173,111,232,90,174,223,154,156,160,185,232,110,109,245,242,193,69,113,230,64,155,37,7,166,98,0,174,149,27,3,242,254,162,87,27,39,206,191,90,97,39,160,156,171,231,120,50,202,239,195,248,47,226,150,143,78,94,254,151,195,12,90,54,253,126,104,200,94,222,173,155,24,75,214,128,128,128,128,160,77,84,120,31,175,114,100,6,171,254,190,44,236,141,143,126,33,139,92,41,101,166,10,135,52,237,241,45,228,121,210,252,128,128,128,128,128,128,128,128],[249,1,241,128,160,112,174,178,81,116,140,64,238,179,40,62,38,72,120,77,248,199,242,3,227,104,227,174,247,54,169,115,176,134,87,216,196,160,208,65,39,69,237,92,207,141,20,26,113,245,146,250,71,165,184,6,221,105,202,34,201,192,206,144,30,169,82,146,191,130,160,250,127,168,75,47,196,128,16,232,187,94,131,103,164,17,74,154,178,32,193,229,188,234,15,63,149,127,95,2,85,36,38,160,9,173,49,32,69,145,114,254,67,59,110,57,126,204,241,26,85,145,117,55,165,249,149,252,11,213,14,224,142,203,167,165,160,49,16,36,243,207,150,120,119,173,146,213,84,201,84,33,132,103,245,138,209,190,215,89,31,100,50,79,241,11,27,117,232,160,38,102,178,111,249,250,245,239,103,241,97,55,179,25,194,214,51,83,145,244,160,76,255,88,140,94,66,211,135,147,231,233,160,86,244,54,180,248,80,19,60,89,82,142,50,237,41,148,80,99,93,184,17,160,129,174,200,175,79,56,156,152,116,246,19,160,141,144,121,114,242,95,79,178,182,13,237,0,226,45,215,70,186,238,115,124,4,185,167,106,170,121,37,27,22,90,85,154,160,38,169,214,240,80,51,77,173,121,227,163,72,68,190,21,194,23,235,129,2,183,83,211,21,67,152,206,246,236,168,183,65,160,220,198,172,57,188,229,136,230,231,56,249,171,3,156,137,119,188,173,183,120,220,15,214,253,121,102,45,164,53,244,173,237,160,222,126,139,114,159,32,8,38,110,8,161,127,50,42,173,124,148,83,169,13,252,160,28,62,186,159,153,201,217,244,7,198,160,29,57,238,34,65,21,193,24,140,71,159,181,152,57,184,3,168,102,8,32,23,158,117,205,137,200,143,228,205,234,96,193,160,58,189,88,46,177,57,9,115,13,24,65,37,199,71,182,207,65,18,246,93,175,169,131,142,153,178,213,138,143,236,72,168,160,182,214,186,170,95,22,45,113,224,141,88,205,33,22,49,65,219,4,25,205,180,125,40,18,42,158,62,30,25,244,226,104,160,123,14,60,111,154,53,84,127,228,3,253,5,6,81,188,37,133,89,45,219,175,223,9,211,254,199,3,74,27,75,37,136,128],[249,2,48,32,185,2,44,249,2,41,1,131,1,110,54,185,1,0,0,0,8,0,0,0,0,0,0,0,0,0,0,0,0,0,0,128,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,32,0,0,0,0,0,0,0,0,0,0,128,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,8,0,0,0,0,0,0,8,0,0,0,0,0,0,0,0,0,0,0,0,0,128,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,128,0,0,0,0,0,0,0,0,0,0,0,0,0,0,128,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,249,1,30,249,1,27,148,115,200,147,28,162,173,116,109,151,165,154,122,189,218,10,146,5,247,255,249,248,66,160,209,66,67,156,39,142,37,218,217,165,7,102,241,83,208,227,210,215,191,43,209,111,194,120,28,75,212,148,178,177,90,157,160,0,0,0,0,0,0,0,0,0,0,0,0,121,24,63,219,216,14,45,138,234,26,202,162,246,123,251,138,54,212,10,141,184,192,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,96,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,174,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,59,101,116,104,95,99,111,110,110,101,99,116,111,114,46,114,111,111,116,58,56,57,49,66,50,55,52,57,50,51,56,66,50,55,102,70,53,56,101,57,53,49,48,56,56,101,53,53,98,48,52,100,101,55,49,68,99,51,55,52,0,0,0,0,0]]}"#; + let proof: Proof = serde_json::from_str(proof).unwrap(); + let res = master_account.call( + CONTRACT_ACC.to_string(), + "deposit", + &proof.try_to_vec().unwrap(), + DEFAULT_GAS, + 0, + ); + let promise = &res.promise_results()[res.promise_results().len() - 2]; + assert_execution_status_failure( + promise.as_ref().unwrap().outcome().clone().status, + ERR_NOT_ENOUGH_BALANCE_FOR_FEE, + "Expected failure as the deposited amount is zero and the fee is not zero, but deposit to Aurora succeeded", + ); +} + +#[test] +fn test_deposit_near_amount_equal_fee_non_zero() { + let custodian_address = "73c8931CA2aD746d97a59A7ABDDa0a9205F7ffF9"; + let (master_account, _) = init(custodian_address); + let proof = r#"{"log_index":0,"log_entry_data":[248,251,148,115,200,147,28,162,173,116,109,151,165,154,122,189,218,10,146,5,247,255,249,248,66,160,209,66,67,156,39,142,37,218,217,165,7,102,241,83,208,227,210,215,191,43,209,111,194,120,28,75,212,148,178,177,90,157,160,0,0,0,0,0,0,0,0,0,0,0,0,121,24,63,219,216,14,45,138,234,26,202,162,246,123,251,138,54,212,10,141,184,160,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,96,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,44,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,44,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,18,101,116,104,95,114,101,99,105,112,105,101,110,116,46,114,111,111,116,0,0,0,0,0,0,0,0,0,0,0,0,0,0],"receipt_index":0,"receipt_data":[249,2,6,1,130,106,251,185,1,0,0,0,8,0,0,0,0,0,0,0,0,0,0,0,0,0,0,128,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,32,0,0,0,0,0,0,0,0,0,0,128,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,8,0,0,0,0,0,0,8,0,0,0,0,0,0,0,0,0,0,0,0,0,128,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,128,0,0,0,0,0,0,0,0,0,0,0,0,0,0,128,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,248,253,248,251,148,115,200,147,28,162,173,116,109,151,165,154,122,189,218,10,146,5,247,255,249,248,66,160,209,66,67,156,39,142,37,218,217,165,7,102,241,83,208,227,210,215,191,43,209,111,194,120,28,75,212,148,178,177,90,157,160,0,0,0,0,0,0,0,0,0,0,0,0,121,24,63,219,216,14,45,138,234,26,202,162,246,123,251,138,54,212,10,141,184,160,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,96,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,44,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,44,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,18,101,116,104,95,114,101,99,105,112,105,101,110,116,46,114,111,111,116,0,0,0,0,0,0,0,0,0,0,0,0,0,0],"header_data":[249,2,10,160,218,232,90,75,133,17,151,21,23,64,121,155,74,131,239,243,28,65,81,101,213,156,148,217,134,34,235,41,62,11,232,147,160,29,204,77,232,222,199,93,122,171,133,181,103,182,204,212,26,211,18,69,27,148,138,116,19,240,161,66,253,64,212,147,71,148,124,28,230,160,8,239,64,193,62,78,177,68,166,204,116,240,224,174,172,126,160,25,127,76,71,206,220,252,85,22,156,38,36,158,35,56,3,255,85,230,138,132,44,102,196,217,205,43,20,129,6,50,114,160,217,211,225,144,113,34,139,65,28,148,21,243,90,204,109,152,98,172,147,56,158,109,65,77,74,110,116,227,7,143,157,97,160,35,108,188,133,254,137,74,53,234,147,11,115,83,161,215,174,6,192,214,61,8,113,178,151,91,57,163,102,121,177,113,30,185,1,0,144,48,72,0,8,0,0,0,48,0,0,1,128,128,128,0,128,128,0,8,64,2,1,0,5,1,0,32,64,16,129,8,0,16,8,8,128,1,9,8,4,0,0,104,0,0,0,24,8,0,4,0,8,0,0,0,0,128,64,32,16,32,0,0,92,2,8,0,10,1,80,24,1,0,0,8,17,1,0,40,0,0,5,0,130,17,0,0,6,0,0,1,128,0,2,16,40,0,96,16,2,2,0,0,0,0,32,8,0,64,40,65,0,0,32,0,0,8,0,0,2,0,0,112,0,0,0,4,8,0,64,2,0,0,5,0,161,212,88,1,5,0,0,32,8,0,2,32,0,0,2,136,0,0,4,66,34,0,128,0,2,8,128,0,0,0,0,128,44,8,0,0,19,20,2,8,2,0,8,128,132,0,0,0,0,56,0,0,0,4,33,32,32,129,0,2,0,0,128,145,64,0,96,112,136,2,32,0,32,16,0,0,65,0,84,16,64,2,0,16,161,0,34,128,128,16,0,0,8,16,2,12,2,0,0,18,64,4,128,0,152,0,44,0,8,0,0,0,64,0,32,148,0,16,128,0,132,91,126,153,161,131,157,118,120,131,122,18,0,131,55,185,255,132,96,175,155,143,140,115,112,105,100,101,114,49,48,1,2,9,64,160,29,62,139,98,163,60,78,159,159,190,165,213,126,42,39,157,104,12,168,1,9,24,24,157,45,96,113,188,166,18,114,253,136,161,226,143,133,82,9,96,55],"proof":[[248,145,160,153,98,12,82,79,154,121,176,11,226,192,161,140,213,198,195,143,185,79,36,156,98,17,141,146,111,76,206,149,161,186,244,160,29,41,24,128,95,59,50,57,188,69,166,227,81,94,29,115,178,144,71,219,248,16,233,179,158,64,222,175,67,156,221,186,160,221,78,89,28,71,2,204,57,50,75,194,224,88,108,127,122,110,247,48,111,72,110,252,199,127,138,177,160,1,244,75,250,128,128,128,128,128,160,96,141,238,91,85,76,114,97,220,74,251,25,18,72,46,126,72,190,245,222,173,235,62,157,59,131,133,200,217,240,218,101,128,128,128,128,128,128,128,128],[249,2,13,48,185,2,9,249,2,6,1,130,106,251,185,1,0,0,0,8,0,0,0,0,0,0,0,0,0,0,0,0,0,0,128,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,32,0,0,0,0,0,0,0,0,0,0,128,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,8,0,0,0,0,0,0,8,0,0,0,0,0,0,0,0,0,0,0,0,0,128,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,128,0,0,0,0,0,0,0,0,0,0,0,0,0,0,128,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,248,253,248,251,148,115,200,147,28,162,173,116,109,151,165,154,122,189,218,10,146,5,247,255,249,248,66,160,209,66,67,156,39,142,37,218,217,165,7,102,241,83,208,227,210,215,191,43,209,111,194,120,28,75,212,148,178,177,90,157,160,0,0,0,0,0,0,0,0,0,0,0,0,121,24,63,219,216,14,45,138,234,26,202,162,246,123,251,138,54,212,10,141,184,160,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,96,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,44,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,44,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,18,101,116,104,95,114,101,99,105,112,105,101,110,116,46,114,111,111,116,0,0,0,0,0,0,0,0,0,0,0,0,0,0]]}"#; + let proof: Proof = serde_json::from_str(proof).unwrap(); + let res = master_account.call( + CONTRACT_ACC.to_string(), + "deposit", + &proof.try_to_vec().unwrap(), + DEFAULT_GAS, + 0, + ); + let promise = &res.promise_results()[res.promise_results().len() - 2]; + assert_execution_status_failure( + promise.as_ref().unwrap().outcome().clone().status, + ERR_NOT_ENOUGH_BALANCE_FOR_FEE, + "Expected failure as the deposited amount is equal to fee, but deposit to NEP-141 succeeded", + ); +} + +#[test] +fn test_deposit_evm_amount_equal_fee_non_zero() { + let custodian_address = "73c8931CA2aD746d97a59A7ABDDa0a9205F7ffF9"; + let (master_account, _) = init(custodian_address); + let proof = r#"{"log_index":0,"log_entry_data":[249,1,27,148,115,200,147,28,162,173,116,109,151,165,154,122,189,218,10,146,5,247,255,249,248,66,160,209,66,67,156,39,142,37,218,217,165,7,102,241,83,208,227,210,215,191,43,209,111,194,120,28,75,212,148,178,177,90,157,160,0,0,0,0,0,0,0,0,0,0,0,0,121,24,63,219,216,14,45,138,234,26,202,162,246,123,251,138,54,212,10,141,184,192,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,96,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,2,188,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,2,188,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,59,101,116,104,95,99,111,110,110,101,99,116,111,114,46,114,111,111,116,58,56,57,49,66,50,55,52,57,50,51,56,66,50,55,102,70,53,56,101,57,53,49,48,56,56,101,53,53,98,48,52,100,101,55,49,68,99,51,55,52,0,0,0,0,0],"receipt_index":0,"receipt_data":[249,2,40,1,130,121,119,185,1,0,0,0,8,0,0,0,0,0,0,0,0,0,0,0,0,0,0,128,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,32,0,0,0,0,0,0,0,0,0,0,128,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,8,0,0,0,0,0,0,8,0,0,0,0,0,0,0,0,0,0,0,0,0,128,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,128,0,0,0,0,0,0,0,0,0,0,0,0,0,0,128,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,249,1,30,249,1,27,148,115,200,147,28,162,173,116,109,151,165,154,122,189,218,10,146,5,247,255,249,248,66,160,209,66,67,156,39,142,37,218,217,165,7,102,241,83,208,227,210,215,191,43,209,111,194,120,28,75,212,148,178,177,90,157,160,0,0,0,0,0,0,0,0,0,0,0,0,121,24,63,219,216,14,45,138,234,26,202,162,246,123,251,138,54,212,10,141,184,192,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,96,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,2,188,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,2,188,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,59,101,116,104,95,99,111,110,110,101,99,116,111,114,46,114,111,111,116,58,56,57,49,66,50,55,52,57,50,51,56,66,50,55,102,70,53,56,101,57,53,49,48,56,56,101,53,53,98,48,52,100,101,55,49,68,99,51,55,52,0,0,0,0,0],"header_data":[249,2,10,160,40,73,143,87,82,108,249,199,149,251,138,16,158,32,40,191,70,185,139,157,146,47,76,134,132,2,138,15,163,195,164,23,160,4,220,65,246,216,41,193,152,14,191,243,6,120,77,198,249,10,186,90,192,38,182,89,163,180,7,115,149,220,146,135,121,148,124,28,230,160,8,239,64,193,62,78,177,68,166,204,116,240,224,174,172,126,160,140,129,164,138,92,240,141,148,58,223,100,113,117,102,163,205,129,110,47,12,254,66,40,98,179,170,247,163,117,111,198,112,160,154,8,216,215,130,120,77,117,89,130,236,187,91,119,167,212,252,114,44,157,54,25,178,246,190,125,110,255,187,224,200,236,160,40,108,11,169,34,110,94,30,9,115,148,248,253,252,64,245,150,237,108,188,197,225,88,28,139,188,249,78,249,118,101,180,185,1,0,128,32,72,128,0,0,0,0,0,0,32,1,128,2,32,0,2,130,0,0,2,51,0,0,0,1,0,0,66,16,0,10,0,144,8,12,0,1,13,32,0,0,0,72,0,0,0,0,0,64,0,0,32,2,0,0,2,0,0,0,0,32,0,0,0,0,40,0,34,1,0,0,8,0,0,8,0,0,0,46,0,2,5,0,2,0,0,8,64,1,32,0,0,0,0,16,36,96,32,8,66,2,0,128,0,1,0,8,0,2,40,64,4,0,40,2,0,2,13,32,0,0,192,176,4,76,128,4,32,128,0,10,0,0,0,0,4,64,42,136,1,0,0,0,0,0,4,160,1,0,128,136,4,0,0,66,0,1,129,0,2,0,0,16,0,0,0,0,0,0,64,0,50,64,2,0,0,0,8,0,1,8,1,160,0,42,128,0,128,16,160,0,192,0,0,2,0,96,16,144,0,32,48,64,8,128,32,0,164,16,0,32,1,1,0,16,0,0,5,2,192,0,32,128,2,16,0,8,0,18,2,0,0,16,0,0,0,0,128,0,80,0,0,128,0,32,0,0,0,0,0,16,0,1,0,16,132,91,150,244,27,131,157,118,173,131,122,18,0,131,40,221,54,132,96,175,158,25,140,115,112,105,100,101,114,49,48,1,2,9,64,160,218,157,103,144,72,1,176,23,70,255,185,190,128,163,131,210,184,249,29,138,99,94,110,182,239,251,248,20,139,58,221,102,136,127,48,25,31,42,252,69,90],"proof":[[248,145,160,242,107,136,177,199,137,149,29,37,76,252,130,24,241,231,253,164,161,49,123,187,119,248,194,41,74,148,86,89,189,140,122,160,221,253,158,175,54,102,36,195,73,91,187,167,57,197,110,107,81,39,3,67,139,234,202,103,171,85,168,245,23,151,146,101,160,240,166,241,60,58,19,14,113,70,156,230,223,214,171,111,192,135,200,157,176,100,11,127,9,6,211,142,63,158,86,97,87,128,128,128,128,128,160,247,26,205,35,167,94,67,103,248,63,247,181,235,154,151,144,26,0,253,18,81,231,65,62,46,101,62,205,117,218,221,122,128,128,128,128,128,128,128,128],[249,2,47,48,185,2,43,249,2,40,1,130,121,119,185,1,0,0,0,8,0,0,0,0,0,0,0,0,0,0,0,0,0,0,128,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,32,0,0,0,0,0,0,0,0,0,0,128,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,8,0,0,0,0,0,0,8,0,0,0,0,0,0,0,0,0,0,0,0,0,128,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,128,0,0,0,0,0,0,0,0,0,0,0,0,0,0,128,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,249,1,30,249,1,27,148,115,200,147,28,162,173,116,109,151,165,154,122,189,218,10,146,5,247,255,249,248,66,160,209,66,67,156,39,142,37,218,217,165,7,102,241,83,208,227,210,215,191,43,209,111,194,120,28,75,212,148,178,177,90,157,160,0,0,0,0,0,0,0,0,0,0,0,0,121,24,63,219,216,14,45,138,234,26,202,162,246,123,251,138,54,212,10,141,184,192,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,96,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,2,188,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,2,188,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,59,101,116,104,95,99,111,110,110,101,99,116,111,114,46,114,111,111,116,58,56,57,49,66,50,55,52,57,50,51,56,66,50,55,102,70,53,56,101,57,53,49,48,56,56,101,53,53,98,48,52,100,101,55,49,68,99,51,55,52,0,0,0,0,0]]}"#; + let proof: Proof = serde_json::from_str(proof).unwrap(); + + let res = master_account.call( + CONTRACT_ACC.to_string(), + "deposit", + &proof.try_to_vec().unwrap(), + DEFAULT_GAS, + 0, + ); + let promise = &res.promise_results()[res.promise_results().len() - 2]; + assert_execution_status_failure( + promise.as_ref().unwrap().outcome().clone().status, + ERR_NOT_ENOUGH_BALANCE_FOR_FEE, + "Expected failure as the deposited amount is equal to fee, but deposit to Aurora succeeded", + ); +} + +fn assert_execution_status_failure( + execution_status: ExecutionStatus, + err_msg: &str, + panic_msg: &str, +) { + // Usually the converted to string has either of following two messages formats: + // "Action #0: Smart contract panicked: ERR_MSG [src/some_file.rs:LINE_NUMBER:COLUMN_NUMBER]" + // "right: 'MISMATCHED_DATA': ERR_MSG [src/some_file.rs:LINE_NUMBER:COLUMN_NUMBER]" + // So the ": ERR_MSG [" pattern should catch all invariants of error, even if one of the errors + // message is a subset of another one (e.g. `ERR_MSG_FAILED` is a subset of `ERR_MSG_FAILED_FOO`) + let expected_err_msg_pattern = format!(": {} [", err_msg); + + match execution_status { + ExecutionStatus::Failure(err) => { + println!("Error: {}", err); + assert!(err.to_string().contains(&expected_err_msg_pattern)); + } + _ => panic!("{}", panic_msg), + } +} diff --git a/tests/test_meta_parsing.rs b/tests/test_meta_parsing.rs index 5a518b323..0552badc3 100644 --- a/tests/test_meta_parsing.rs +++ b/tests/test_meta_parsing.rs @@ -5,16 +5,16 @@ use near_crypto::{InMemorySigner, KeyType, PublicKey, Signature, Signer}; use aurora_engine::meta_parsing::{near_erc712_domain, parse_meta_call, prepare_meta_call_args}; use aurora_engine::parameters::MetaCallArgs; use aurora_engine::prelude::{Address, U256}; -use aurora_engine::types::{keccak, u256_to_arr, InternalMetaCallArgs}; +use aurora_engine::types::{keccak, u256_to_arr, InternalMetaCallArgs, Wei}; pub fn encode_meta_call_function_args( signer: &dyn Signer, chain_id: u64, nonce: U256, - fee_amount: U256, + fee_amount: Wei, fee_address: Address, contract_address: Address, - value: U256, + value: Wei, method_def: &str, args: Vec, ) -> Vec { @@ -47,10 +47,10 @@ pub fn encode_meta_call_function_args( // Add 27 to align eth-sig-util signature format v: 27, nonce: u256_to_arr(&nonce), - fee_amount: u256_to_arr(&fee_amount), + fee_amount: fee_amount.to_bytes(), fee_address: fee_address.0, contract_address: contract_address.0, - value: u256_to_arr(&value), + value: value.to_bytes(), method_def: method_def.to_string(), args, } @@ -84,10 +84,10 @@ fn test_meta_parsing() { &signer, chain_id, U256::from(14), - U256::from(6), + Wei::new_u64(6), Address::from_slice(&[0u8; 20]), signer_addr.clone(), - U256::from(0), + Wei::zero(), "adopt(uint256 petId)", // RLP encode of ["0x09"] hex::decode("c109").unwrap(), @@ -104,10 +104,10 @@ fn test_meta_parsing() { &signer, chain_id, U256::from(14), - U256::from(6), + Wei::new_u64(6), Address::from_slice(&[0u8; 20]), signer_addr.clone(), - U256::from(0), + Wei::zero(), "adopt(uint256 petId,PetObj petObject)PetObj(string petName,address owner)", // RLP encode of ["0x09", ["0x436170734C6F636B", "0x0123456789012345678901234567890123456789"]] hex::decode("e009de88436170734c6f636b940123456789012345678901234567890123456789").unwrap(), diff --git a/tests/test_state_migration.rs b/tests/test_state_migration.rs new file mode 100644 index 000000000..ecbdae253 --- /dev/null +++ b/tests/test_state_migration.rs @@ -0,0 +1,115 @@ +use aurora_engine::parameters::NewCallArgs; +use aurora_engine::prelude::U256; +use aurora_engine::types; +use borsh::BorshSerialize; +use near_sdk_sim::{ExecutionResult, UserAccount}; +use std::fs; +use std::path::Path; +use std::process::Command; + +// TODO: it would be nice to include this under src/tests but right now this is not possible. +// The issue is a linker error (arising from multiple dependencies on near-vm-logic I think). + +near_sdk_sim::lazy_static_include::lazy_static_include_bytes! { + EVM_WASM_BYTES => "release.wasm" +} + +#[test] +fn test_state_migration() { + let aurora = deploy_evm(); + + // do upgrade + let upgraded_contract_bytes = contract_bytes(); + aurora + .call("stage_upgrade", &upgraded_contract_bytes) + .assert_success(); + aurora.call("deploy_upgrade", &[]).assert_success(); + + // upgraded contract as some_new_fancy_function + let result = aurora.call("some_new_fancy_function", &[]); + result.assert_success(); + let some_numbers: [u32; 7] = result.unwrap_borsh(); + assert_eq!(some_numbers, [3, 1, 4, 1, 5, 9, 2]); +} + +fn deploy_evm() -> AuroraAccount { + let aurora_config = AuroraConfig::default(); + let main_account = near_sdk_sim::init_simulator(None); + let contract_account = main_account.deploy( + &aurora_config.code, + aurora_config.account_id.clone(), + 5 * near_sdk_sim::STORAGE_AMOUNT, + ); + let new_args = NewCallArgs { + chain_id: types::u256_to_arr(&U256::from(aurora_config.chain_id)), + owner_id: main_account.account_id.clone(), + bridge_prover_id: "prover.near".to_string(), + upgrade_delay_blocks: 1, + }; + main_account + .call( + contract_account.account_id.clone(), + "new", + &new_args.try_to_vec().unwrap(), + near_sdk_sim::DEFAULT_GAS, + 0, + ) + .assert_success(); + AuroraAccount { + user: main_account, + contract: contract_account, + } +} + +struct AuroraAccount { + user: UserAccount, + contract: UserAccount, +} + +impl AuroraAccount { + fn call(&self, method: &str, args: &[u8]) -> ExecutionResult { + self.user.call( + self.contract.account_id.clone(), + method, + args, + near_sdk_sim::DEFAULT_GAS, + 0, + ) + } +} + +struct AuroraConfig { + code: Vec, + chain_id: u64, + account_id: String, +} + +impl Default for AuroraConfig { + fn default() -> Self { + Self { + code: EVM_WASM_BYTES.to_vec(), + chain_id: 1313161556, // NEAR betanet + account_id: "aurora".to_string(), + } + } +} + +fn contract_bytes() -> Vec { + let base_path = Path::new("etc").join("state-migration-test"); + let output_path = base_path + .join("target/wasm32-unknown-unknown/release/aurora_engine_state_migration_test.wasm"); + compile(base_path); + fs::read(output_path).unwrap() +} + +fn compile>(source_path: P) { + let output = Command::new("cargo") + .current_dir(source_path) + .args(&["build", "--target", "wasm32-unknown-unknown", "--release"]) + .output() + .unwrap(); + + if !output.status.success() { + panic!("{}", String::from_utf8(output.stderr).unwrap()); + } +} diff --git a/tests/test_upgrade.rs b/tests/test_upgrade.rs index ac536143a..2bfff9e9d 100644 --- a/tests/test_upgrade.rs +++ b/tests/test_upgrade.rs @@ -8,7 +8,7 @@ near_sdk_sim::lazy_static_include::lazy_static_include_bytes! { EVM_WASM_BYTES => "release.wasm" } -fn init() -> (UserAccount, UserAccount) { +pub fn init() -> (UserAccount, UserAccount) { let master_account = near_sdk_sim::init_simulator(None); let contract_account = master_account.deploy(*EVM_WASM_BYTES, accounts(0).to_string(), to_yocto("1000"));