diff --git a/bridge-common/Cargo.lock b/bridge-common/Cargo.lock index 3553d961..1311b1d2 100644 --- a/bridge-common/Cargo.lock +++ b/bridge-common/Cargo.lock @@ -8,14 +8,6 @@ version = "0.11.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fe438c63458706e03479442743baae6c88256498e6431708f6dfc520a26515d3" -[[package]] -name = "admin-controlled" -version = "0.1.0" -source = "git+https://github.com/near/rainbow-bridge?rev=67966a151122815039f07b02065ab3a2bba09804#67966a151122815039f07b02065ab3a2bba09804" -dependencies = [ - "near-sdk", -] - [[package]] name = "ahash" version = "0.7.6" @@ -163,7 +155,6 @@ dependencies = [ name = "bridge-common" version = "0.1.0" dependencies = [ - "admin-controlled", "eth-types", "ethabi", "hex", @@ -308,41 +299,6 @@ dependencies = [ "zeroize", ] -[[package]] -name = "darling" -version = "0.13.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a01d95850c592940db9b8194bc39f4bc0e89dee5c4265e4b1807c34a9aba453c" -dependencies = [ - "darling_core", - "darling_macro", -] - -[[package]] -name = "darling_core" -version = "0.13.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "859d65a907b6852c9361e3185c862aae7fafd2887876799fa55f5f99dc40d610" -dependencies = [ - "fnv", - "ident_case", - "proc-macro2", - "quote", - "strsim", - "syn", -] - -[[package]] -name = "darling_macro" -version = "0.13.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9c972679f83bdf9c42bd905396b6c3588a843a17f0f16dfcfa3e2c5d57441835" -dependencies = [ - "darling_core", - "quote", - "syn", -] - [[package]] name = "derive_more" version = "0.99.17" @@ -413,13 +369,10 @@ checksum = "e78d4f1cc4ae33bbfc157ed5d5a5ef3bc29227303d595861deb238fcec4e9457" [[package]] name = "eth-types" version = "0.1.0" -source = "git+https://github.com/near/rainbow-bridge?rev=67966a151122815039f07b02065ab3a2bba09804#67966a151122815039f07b02065ab3a2bba09804" +source = "git+https://github.com/near/rainbow-bridge?rev=4b24cbd5a6e098fd8a9cd24fbb194fa8e0fc51e4#4b24cbd5a6e098fd8a9cd24fbb194fa8e0fc51e4" dependencies = [ "borsh", "derive_more", - "eth2_serde_utils", - "eth2_ssz", - "eth2_ssz_derive", "ethereum-types", "hex", "near-sdk", @@ -427,49 +380,6 @@ dependencies = [ "rlp-derive", "serde", "serde-big-array", - "tree_hash", - "tree_hash_derive", -] - -[[package]] -name = "eth2_hashing" -version = "0.3.0" -source = "git+https://github.com/near/rainbow-bridge?rev=67966a151122815039f07b02065ab3a2bba09804#67966a151122815039f07b02065ab3a2bba09804" -dependencies = [ - "lazy_static", - "near-sdk", -] - -[[package]] -name = "eth2_serde_utils" -version = "0.1.1" -source = "git+https://github.com/aurora-is-near/lighthouse.git?rev=b624c3f0d3c5bc9ea46faa14c9cb2d90ee1e1dec#b624c3f0d3c5bc9ea46faa14c9cb2d90ee1e1dec" -dependencies = [ - "ethereum-types", - "hex", - "serde", - "serde_derive", - "serde_json", -] - -[[package]] -name = "eth2_ssz" -version = "0.4.1" -source = "git+https://github.com/aurora-is-near/lighthouse.git?rev=b624c3f0d3c5bc9ea46faa14c9cb2d90ee1e1dec#b624c3f0d3c5bc9ea46faa14c9cb2d90ee1e1dec" -dependencies = [ - "ethereum-types", - "smallvec", -] - -[[package]] -name = "eth2_ssz_derive" -version = "0.3.0" -source = "git+https://github.com/aurora-is-near/lighthouse.git?rev=b624c3f0d3c5bc9ea46faa14c9cb2d90ee1e1dec#b624c3f0d3c5bc9ea46faa14c9cb2d90ee1e1dec" -dependencies = [ - "darling", - "proc-macro2", - "quote", - "syn", ] [[package]] @@ -537,12 +447,6 @@ dependencies = [ "static_assertions", ] -[[package]] -name = "fnv" -version = "1.0.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" - [[package]] name = "funty" version = "1.1.0" @@ -602,12 +506,6 @@ version = "0.4.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" -[[package]] -name = "ident_case" -version = "1.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b9e0384b61958566e926dc50660321d12159025e767c18e043daf26b70104c39" - [[package]] name = "impl-codec" version = "0.4.2" @@ -667,12 +565,6 @@ 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.121" @@ -1290,12 +1182,6 @@ version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f" -[[package]] -name = "strsim" -version = "0.10.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623" - [[package]] name = "strum" version = "0.24.1" @@ -1410,26 +1296,6 @@ dependencies = [ "serde", ] -[[package]] -name = "tree_hash" -version = "0.4.1" -source = "git+https://github.com/aurora-is-near/lighthouse.git?rev=b624c3f0d3c5bc9ea46faa14c9cb2d90ee1e1dec#b624c3f0d3c5bc9ea46faa14c9cb2d90ee1e1dec" -dependencies = [ - "eth2_hashing", - "ethereum-types", - "smallvec", -] - -[[package]] -name = "tree_hash_derive" -version = "0.4.0" -source = "git+https://github.com/aurora-is-near/lighthouse.git?rev=b624c3f0d3c5bc9ea46faa14c9cb2d90ee1e1dec#b624c3f0d3c5bc9ea46faa14c9cb2d90ee1e1dec" -dependencies = [ - "darling", - "quote", - "syn", -] - [[package]] name = "typenum" version = "1.15.0" @@ -1544,3 +1410,8 @@ dependencies = [ "syn", "synstructure", ] + +[[patch.unused]] +name = "eth2_hashing" +version = "0.3.0" +source = "git+https://github.com/near/rainbow-bridge?rev=67966a151122815039f07b02065ab3a2bba09804#67966a151122815039f07b02065ab3a2bba09804" diff --git a/bridge-common/Cargo.toml b/bridge-common/Cargo.toml index 62aa8c2b..dee72ff0 100644 --- a/bridge-common/Cargo.toml +++ b/bridge-common/Cargo.toml @@ -7,8 +7,7 @@ edition = "2021" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] -eth-types = { git = "https://github.com/near/rainbow-bridge", rev = "67966a151122815039f07b02065ab3a2bba09804" } -admin-controlled = { git = "https://github.com/near/rainbow-bridge", rev = "67966a151122815039f07b02065ab3a2bba09804" } +eth-types = { git = "https://github.com/near/rainbow-bridge", rev = "4b24cbd5a6e098fd8a9cd24fbb194fa8e0fc51e4", default-features = false} ethabi = "12.0.0" near-sdk = "4.0.0" tiny-keccak = "1.4.0" diff --git a/bridge-common/src/prover.rs b/bridge-common/src/prover.rs index 823273ef..527e0062 100644 --- a/bridge-common/src/prover.rs +++ b/bridge-common/src/prover.rs @@ -1,6 +1,5 @@ use std::convert::From; -use admin_controlled::Mask; use eth_types::*; use ethabi::param_type::Writer; use ethabi::{Event, EventParam, Hash, Log, ParamType, RawLog, Token}; @@ -14,8 +13,6 @@ pub type EthAddress = [u8; 20]; pub const NO_DEPOSIT: Balance = 0; -pub const PAUSE_DEPOSIT: Mask = 1 << 1; - /// Gas to call verify_log_entry on prover. pub const VERIFY_LOG_ENTRY_GAS: Gas = Gas(Gas::ONE_TERA.0 * 50); diff --git a/bridge-token-factory/Cargo.lock b/bridge-token-factory/Cargo.lock index 099dbb38..a5f2f868 100644 --- a/bridge-token-factory/Cargo.lock +++ b/bridge-token-factory/Cargo.lock @@ -68,14 +68,6 @@ version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe" -[[package]] -name = "admin-controlled" -version = "0.1.0" -source = "git+https://github.com/near/rainbow-bridge?rev=67966a151122815039f07b02065ab3a2bba09804#67966a151122815039f07b02065ab3a2bba09804" -dependencies = [ - "near-sdk", -] - [[package]] name = "ahash" version = "0.7.6" @@ -373,7 +365,6 @@ dependencies = [ name = "bridge-common" version = "0.1.0" dependencies = [ - "admin-controlled", "eth-types", "ethabi", "hex 0.4.3", @@ -385,25 +376,23 @@ dependencies = [ [[package]] name = "bridge-token" -version = "0.1.0" +version = "0.2.0" dependencies = [ - "admin-controlled", "near-contract-standards", "near-sdk", ] [[package]] name = "bridge-token-factory" -version = "0.1.5" +version = "0.2.0" dependencies = [ - "admin-controlled", "anyhow", "bridge-common", "bridge-token", - "eth-types", "ethabi", "hex 0.4.3", "near-contract-standards", + "near-plugins", "near-sdk", "rand 0.7.3", "serde", @@ -777,6 +766,12 @@ dependencies = [ "winapi", ] +[[package]] +name = "dyn-clone" +version = "1.0.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "68b0cf012f1230e43cd00ebb729c6bb58707ecfa8ad08b52ef3a4ccd2697fc30" + [[package]] name = "easy-ext" version = "0.2.9" @@ -824,13 +819,10 @@ dependencies = [ [[package]] name = "eth-types" version = "0.1.0" -source = "git+https://github.com/near/rainbow-bridge?rev=67966a151122815039f07b02065ab3a2bba09804#67966a151122815039f07b02065ab3a2bba09804" +source = "git+https://github.com/near/rainbow-bridge?rev=4b24cbd5a6e098fd8a9cd24fbb194fa8e0fc51e4#4b24cbd5a6e098fd8a9cd24fbb194fa8e0fc51e4" dependencies = [ "borsh", "derive_more", - "eth2_serde_utils", - "eth2_ssz", - "eth2_ssz_derive", "ethereum-types", "hex 0.4.3", "near-sdk", @@ -838,49 +830,6 @@ dependencies = [ "rlp-derive", "serde", "serde-big-array", - "tree_hash", - "tree_hash_derive", -] - -[[package]] -name = "eth2_hashing" -version = "0.3.0" -source = "git+https://github.com/near/rainbow-bridge?rev=67966a151122815039f07b02065ab3a2bba09804#67966a151122815039f07b02065ab3a2bba09804" -dependencies = [ - "lazy_static", - "near-sdk", -] - -[[package]] -name = "eth2_serde_utils" -version = "0.1.1" -source = "git+https://github.com/aurora-is-near/lighthouse.git?rev=b624c3f0d3c5bc9ea46faa14c9cb2d90ee1e1dec#b624c3f0d3c5bc9ea46faa14c9cb2d90ee1e1dec" -dependencies = [ - "ethereum-types", - "hex 0.4.3", - "serde", - "serde_derive", - "serde_json", -] - -[[package]] -name = "eth2_ssz" -version = "0.4.1" -source = "git+https://github.com/aurora-is-near/lighthouse.git?rev=b624c3f0d3c5bc9ea46faa14c9cb2d90ee1e1dec#b624c3f0d3c5bc9ea46faa14c9cb2d90ee1e1dec" -dependencies = [ - "ethereum-types", - "smallvec", -] - -[[package]] -name = "eth2_ssz_derive" -version = "0.3.0" -source = "git+https://github.com/aurora-is-near/lighthouse.git?rev=b624c3f0d3c5bc9ea46faa14c9cb2d90ee1e1dec#b624c3f0d3c5bc9ea46faa14c9cb2d90ee1e1dec" -dependencies = [ - "darling", - "proc-macro2", - "quote", - "syn", ] [[package]] @@ -1420,6 +1369,9 @@ name = "lazy_static" version = "1.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" +dependencies = [ + "spin", +] [[package]] name = "libc" @@ -1522,12 +1474,14 @@ dependencies = [ ] [[package]] -name = "near-account-id" -version = "0.13.0" +name = "near-abi" +version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "de83d74a9241be8cc4eb3055216966b58bf8c463e8e285c0dc553925acdd19fa" +checksum = "885db39b08518fa700b73fa2214e8adbbfba316ba82dd510f50519173eadaf73" dependencies = [ "borsh", + "schemars", + "semver", "serde", ] @@ -1550,8 +1504,8 @@ dependencies = [ "anyhow", "chrono", "derive_more", - "near-crypto 0.14.0", - "near-primitives 0.14.0", + "near-crypto", + "near-primitives", "num-rational", "serde", "serde_json", @@ -1567,8 +1521,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a734353027698b21633a49d478e564c61ae0171c32f6912bb8844add15d72ebe" dependencies = [ "chrono", - "near-crypto 0.14.0", - "near-primitives 0.14.0", + "near-crypto", + "near-primitives", "thiserror", "tracing", ] @@ -1580,7 +1534,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c17f6f22f1ab710731dfba4101f12a99cac120d6af80b99899bd335bb8971477" dependencies = [ "near-chain-primitives", - "near-primitives 0.14.0", + "near-primitives", ] [[package]] @@ -1594,9 +1548,9 @@ dependencies = [ "near-chain-configs", "near-chain-primitives", "near-chunks-primitives", - "near-crypto 0.14.0", + "near-crypto", "near-network-primitives", - "near-primitives 0.14.0", + "near-primitives", "serde", "serde_json", "strum", @@ -1605,42 +1559,16 @@ dependencies = [ [[package]] name = "near-contract-standards" -version = "4.0.0" +version = "4.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6466c6aaad18800ff6a3cd104427dc8fd79e144714135224b8289b1dc43f7167" +checksum = "7bacc932e79b26472797adfb21689294b6f90960d1570daaf1e0b682b59fcb35" dependencies = [ "near-sdk", + "schemars", "serde", "serde_json", ] -[[package]] -name = "near-crypto" -version = "0.13.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b8ecf0b8b31aa7f4e60f629f72213a2617ca4a5f45cd1ae9ed2cf7cecfebdbb7" -dependencies = [ - "arrayref", - "blake2", - "borsh", - "bs58", - "c2-chacha", - "curve25519-dalek", - "derive_more", - "ed25519-dalek", - "libc", - "near-account-id 0.13.0", - "once_cell", - "parity-secp256k1", - "primitive-types 0.10.1", - "rand 0.7.3", - "rand_core 0.5.1", - "serde", - "serde_json", - "subtle", - "thiserror", -] - [[package]] name = "near-crypto" version = "0.14.0" @@ -1655,7 +1583,7 @@ dependencies = [ "curve25519-dalek", "derive_more", "ed25519-dalek", - "near-account-id 0.14.0", + "near-account-id", "once_cell", "parity-secp256k1", "primitive-types 0.10.1", @@ -1677,9 +1605,9 @@ dependencies = [ "lazy_static", "log", "near-chain-configs", - "near-crypto 0.14.0", + "near-crypto", "near-jsonrpc-primitives", - "near-primitives 0.14.0", + "near-primitives", "reqwest", "serde", "serde_json", @@ -1695,9 +1623,9 @@ checksum = "34a14ee8ca393c0140cb232789259ebc61b13b4cceb177267d0131f50d0eda6c" dependencies = [ "near-chain-configs", "near-client-primitives", - "near-crypto 0.14.0", - "near-primitives 0.14.0", - "near-rpc-error-macro 0.14.0", + "near-crypto", + "near-primitives", + "near-rpc-error-macro", "serde", "serde_json", "thiserror", @@ -1714,8 +1642,8 @@ dependencies = [ "anyhow", "borsh", "chrono", - "near-crypto 0.14.0", - "near-primitives 0.14.0", + "near-crypto", + "near-primitives", "serde", "strum", "tokio", @@ -1723,32 +1651,26 @@ dependencies = [ ] [[package]] -name = "near-primitives" -version = "0.13.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0a2ba19282e79a4485a77736b679d276b09870bbf8042a18e0f0ae36347489c5" +name = "near-plugins" +version = "0.1.0" +source = "git+https://github.com/aurora-is-near/near-plugins?rev=93e1a30d79e72d51c4349ba71d454865c76ac690#93e1a30d79e72d51c4349ba71d454865c76ac690" dependencies = [ - "borsh", - "byteorder", - "bytesize", - "chrono", - "derive_more", - "easy-ext", - "hex 0.4.3", - "near-crypto 0.13.0", - "near-primitives-core 0.13.0", - "near-rpc-error-macro 0.13.0", - "near-vm-errors 0.13.0", - "num-rational", - "once_cell", - "primitive-types 0.10.1", - "rand 0.7.3", - "reed-solomon-erasure", + "bitflags", + "near-plugins-derive", + "near-sdk", "serde", - "serde_json", - "smart-default", - "strum", - "thiserror", +] + +[[package]] +name = "near-plugins-derive" +version = "0.1.0" +source = "git+https://github.com/aurora-is-near/near-plugins?rev=93e1a30d79e72d51c4349ba71d454865c76ac690#93e1a30d79e72d51c4349ba71d454865c76ac690" +dependencies = [ + "darling", + "proc-macro-crate 0.1.5", + "proc-macro2", + "quote", + "syn", ] [[package]] @@ -1764,10 +1686,10 @@ dependencies = [ "derive_more", "easy-ext", "hex 0.4.3", - "near-crypto 0.14.0", - "near-primitives-core 0.14.0", - "near-rpc-error-macro 0.14.0", - "near-vm-errors 0.14.0", + "near-crypto", + "near-primitives-core", + "near-rpc-error-macro", + "near-vm-errors", "num-rational", "once_cell", "primitive-types 0.10.1", @@ -1780,23 +1702,6 @@ dependencies = [ "thiserror", ] -[[package]] -name = "near-primitives-core" -version = "0.13.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bb561feb392bb8c4f540256073446e6689af087bf6356e8dddcf75fc279f201f" -dependencies = [ - "base64 0.11.0", - "borsh", - "bs58", - "derive_more", - "near-account-id 0.13.0", - "num-rational", - "serde", - "sha2 0.10.2", - "strum", -] - [[package]] name = "near-primitives-core" version = "0.14.0" @@ -1807,24 +1712,13 @@ dependencies = [ "borsh", "bs58", "derive_more", - "near-account-id 0.14.0", + "near-account-id", "num-rational", "serde", "sha2 0.10.2", "strum", ] -[[package]] -name = "near-rpc-error-core" -version = "0.13.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "77fdd7ea8d8f786878651c37691515d5053f827ae60894aa40c16882b78f77c9" -dependencies = [ - "quote", - "serde", - "syn", -] - [[package]] name = "near-rpc-error-core" version = "0.14.0" @@ -1836,24 +1730,13 @@ dependencies = [ "syn", ] -[[package]] -name = "near-rpc-error-macro" -version = "0.13.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e521842b6ae864dfe5391afbbe2df9e9d8427c26e9333b2e0b65cd42094f7607" -dependencies = [ - "near-rpc-error-core 0.13.0", - "serde", - "syn", -] - [[package]] name = "near-rpc-error-macro" version = "0.14.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8e837bd4bacd807073ec5ceb85708da7f721b46a4c2a978de86027fb0034ce31" dependencies = [ - "near-rpc-error-core 0.14.0", + "near-rpc-error-core", "serde", "syn", ] @@ -1874,19 +1757,22 @@ dependencies = [ [[package]] name = "near-sdk" -version = "4.0.0" +version = "4.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bda34e06e28fb9a09ac54efbdc49f0c9308780fc932aaa81c49c493fde974045" +checksum = "15eb3de2defe3626260cc209a6cdb985c6b27b0bd4619fad97dcfae002c3c5bd" dependencies = [ "base64 0.13.0", "borsh", "bs58", - "near-crypto 0.13.0", - "near-primitives 0.13.0", - "near-primitives-core 0.13.0", + "near-abi", + "near-crypto", + "near-primitives", + "near-primitives-core", "near-sdk-macros", "near-sys", "near-vm-logic", + "once_cell", + "schemars", "serde", "serde_json", "wee_alloc", @@ -1894,9 +1780,9 @@ dependencies = [ [[package]] name = "near-sdk-macros" -version = "4.0.0" +version = "4.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "72064fcc15a623a0d40a6c199ea5cbdc30a83cae4816889d46f218acf31bfba8" +checksum = "4907affc9f5ed559456509188ff0024f1f2099c0830e6bdb66eb61d5b75912c0" dependencies = [ "Inflector", "proc-macro2", @@ -1910,18 +1796,6 @@ version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e307313276eaeced2ca95740b5639e1f3125b7c97f0a1151809d105f1aa8c6d3" -[[package]] -name = "near-vm-errors" -version = "0.13.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0e02faf2bc1f6ef82b965cfe44389808fb5594f7aca4b596766117f4ce74df20" -dependencies = [ - "borsh", - "near-account-id 0.13.0", - "near-rpc-error-macro 0.13.0", - "serde", -] - [[package]] name = "near-vm-errors" version = "0.14.0" @@ -1929,30 +1803,31 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d0da466a30f0446639cbd788c30865086fac3e8dcb07a79e51d2b0775ed4261e" dependencies = [ "borsh", - "near-account-id 0.14.0", - "near-rpc-error-macro 0.14.0", + "near-account-id", + "near-rpc-error-macro", "serde", ] [[package]] name = "near-vm-logic" -version = "0.13.0" +version = "0.14.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f024d90451cd3c24d7a0a5cabf3636b192a60eb8e3ff0456f6c18b91152c346d" +checksum = "81b534828419bacbf1f7b11ef7b00420f248c548c485d3f0cfda8bb6931152f2" dependencies = [ "base64 0.13.0", "borsh", "bs58", "byteorder", - "near-account-id 0.13.0", - "near-crypto 0.13.0", - "near-primitives 0.13.0", - "near-primitives-core 0.13.0", - "near-vm-errors 0.13.0", + "near-account-id", + "near-crypto", + "near-primitives", + "near-primitives-core", + "near-vm-errors", "ripemd", "serde", "sha2 0.10.2", "sha3", + "zeropool-bn", ] [[package]] @@ -2547,6 +2422,30 @@ dependencies = [ "windows-sys", ] +[[package]] +name = "schemars" +version = "0.8.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "02c613288622e5f0c3fdc5dbd4db1c5fbe752746b1d1a56a0630b78fd00de44f" +dependencies = [ + "dyn-clone", + "schemars_derive", + "serde", + "serde_json", +] + +[[package]] +name = "schemars_derive" +version = "0.8.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "109da1e6b197438deb6db99952990c7f959572794b80ff93707d55a232545e7c" +dependencies = [ + "proc-macro2", + "quote", + "serde_derive_internals", + "syn", +] + [[package]] name = "scopeguard" version = "1.1.0" @@ -2611,6 +2510,17 @@ dependencies = [ "syn", ] +[[package]] +name = "serde_derive_internals" +version = "0.26.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85bf8229e7920a9f636479437026331ce11aa132b4dde37d121944a44d6e5f3c" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "serde_json" version = "1.0.83" @@ -2735,6 +2645,12 @@ dependencies = [ "winapi", ] +[[package]] +name = "spin" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6e63cff320ae2c57904679ba7cb63280a3dc4613885beafb148ee7bf9aa9042d" + [[package]] name = "static_assertions" version = "1.1.0" @@ -3007,26 +2923,6 @@ dependencies = [ "once_cell", ] -[[package]] -name = "tree_hash" -version = "0.4.1" -source = "git+https://github.com/aurora-is-near/lighthouse.git?rev=b624c3f0d3c5bc9ea46faa14c9cb2d90ee1e1dec#b624c3f0d3c5bc9ea46faa14c9cb2d90ee1e1dec" -dependencies = [ - "eth2_hashing", - "ethereum-types", - "smallvec", -] - -[[package]] -name = "tree_hash_derive" -version = "0.4.0" -source = "git+https://github.com/aurora-is-near/lighthouse.git?rev=b624c3f0d3c5bc9ea46faa14c9cb2d90ee1e1dec#b624c3f0d3c5bc9ea46faa14c9cb2d90ee1e1dec" -dependencies = [ - "darling", - "quote", - "syn", -] - [[package]] name = "try-lock" version = "0.2.3" @@ -3343,11 +3239,11 @@ dependencies = [ "dirs 3.0.2", "hex 0.4.3", "libc", - "near-account-id 0.14.0", - "near-crypto 0.14.0", + "near-account-id", + "near-crypto", "near-jsonrpc-client", "near-jsonrpc-primitives", - "near-primitives 0.14.0", + "near-primitives", "near-sandbox-utils", "portpicker", "rand 0.8.5", @@ -3397,6 +3293,20 @@ dependencies = [ "synstructure", ] +[[package]] +name = "zeropool-bn" +version = "0.5.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "71e61de68ede9ffdd69c01664f65a178c5188b73f78faa21f0936016a888ff7c" +dependencies = [ + "borsh", + "byteorder", + "crunchy", + "lazy_static", + "rand 0.8.5", + "rustc-hex", +] + [[package]] name = "zip" version = "0.5.13" diff --git a/bridge-token-factory/Cargo.toml b/bridge-token-factory/Cargo.toml index c6cd1339..b23126f4 100644 --- a/bridge-token-factory/Cargo.toml +++ b/bridge-token-factory/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "bridge-token-factory" -version = "0.1.5" +version = "0.2.0" authors = ["Near Inc "] edition = "2021" publish = false @@ -18,24 +18,19 @@ panic = "abort" overflow-checks = true [dependencies] -near-sdk = "4.0.0" +near-sdk = "4.1.0" near-contract-standards = "4.0.0" -eth-types = { git = "https://github.com/near/rainbow-bridge", rev = "67966a151122815039f07b02065ab3a2bba09804" } -admin-controlled = { git = "https://github.com/near/rainbow-bridge", rev = "67966a151122815039f07b02065ab3a2bba09804" } ethabi = "12.0.0" bridge-common = { path = "../bridge-common", default-features = false } hex = "0.4.2" bridge-token = { path = "../bridge-token" } +near-plugins = { git = "https://github.com/aurora-is-near/near-plugins", rev = "93e1a30d79e72d51c4349ba71d454865c76ac690" } +serde = { version = "*", features = ["derive"] } [dev-dependencies] uint = { version = "0.8.3", default-features = false } rand = "0.7.3" -serde = { version = "*", features = ["derive"] } serde_json = "*" workspaces = "0.5.0" tokio = { version = "1.1", features = ["rt", "macros"] } anyhow = "*" - -[patch] -[patch.crates-io] -eth2_hashing = { git = "https://github.com/near/rainbow-bridge", rev = "67966a151122815039f07b02065ab3a2bba09804" } \ No newline at end of file diff --git a/bridge-token-factory/readme.md b/bridge-token-factory/readme.md new file mode 100644 index 00000000..e8060a42 --- /dev/null +++ b/bridge-token-factory/readme.md @@ -0,0 +1,30 @@ +# Bridge-Token-Factory + +## Fee Integration for deposit and withdrawal of tokens + +**NOTE**: + +1. To set fee-percentage and fee-bounds caller (predecessor) must have `FeeSetter` role. +2. Fee-percentage has **6-decimal** precision for eg. to set deposit fee percentage as 10%, value for function param is 0.1 \* 10^6 ie. 10^5. + +- ### For deposit: + + - `set_deposit_fee`: method to set deposit fee for eth -> near and eth -> aurora. + - `set_deposit_fee_per_silo`: method to set deposit fee for different aurora-silos per different tokens. + - `unset_deposit_fee`: method to unset deposit fee for eth -> near and eth -> aurora. + - `unset_deposit_fee_per_silo`: method to unset deposit fee for different aurora-silos per different tokens. + - `get_deposit_fee`: returns deposit token fee for specific erc-20 token address passed. + - `get_deposit_fee_per_silo`: returns deposit fee for different silos per different tokens. It also returns the default value, if have any. + +- ### For withdraw: + + - `set_withdraw_fee`: method to set withdraw fee for near -> eth and aurora -> eth. + - `set_withdraw_fee_per_silo`: method to set withdraw fee for different aurora-silos per different tokens. + - `unset_withdraw_fee`: method to unset withdraw fee for near -> eth and aurora -> eth. + - `unset_withdraw_fee_per_silo`: method to unset withdraw fee for different aurora-silos per different tokens. + - `get_withdraw_token_fee`: returns withdraw fee for specific erc-20 token address. + - `get_withdraw_fee_per_silo`: returns withdraw fee-percentage for different silos per different tokens. It also returns the default value, if have any. + +- ### To claim the accumulated fee:- + - `claim_fee`: method to claim the fee-amount accumulated, by passing Near-token-address and the desired amount. + **NOTE**: To claim fee caller or predecessor must have access-role:`FeeClaimer` diff --git a/bridge-token-factory/res/bridge_token_factory_v0.1.6.wasm b/bridge-token-factory/res/bridge_token_factory_v0.1.6.wasm new file mode 100644 index 00000000..78863f38 Binary files /dev/null and b/bridge-token-factory/res/bridge_token_factory_v0.1.6.wasm differ diff --git a/bridge-token-factory/rust-toolchain b/bridge-token-factory/rust-toolchain index d5a27893..0cf440a7 100644 --- a/bridge-token-factory/rust-toolchain +++ b/bridge-token-factory/rust-toolchain @@ -1 +1,3 @@ -stable-2022-07-19 +[toolchain] +channel = "1.68.0" +components = ["clippy", "rustfmt"] diff --git a/bridge-token-factory/src/fee.rs b/bridge-token-factory/src/fee.rs new file mode 100644 index 00000000..fd1d949b --- /dev/null +++ b/bridge-token-factory/src/fee.rs @@ -0,0 +1,653 @@ +use near_sdk::{assert_one_yocto, ONE_YOCTO}; + +use crate::*; + +fn calculate_fee_amount(transfer_amount: u128, fee: &Fee) -> u128 { + let fee_amount = (transfer_amount * fee.fee_percentage.0) / FEE_DECIMAL_PRECISION; + + let bounded_fee_amount = if fee.lower_bound.map_or(false, |bound| fee_amount < bound.0) { + fee.lower_bound.unwrap().0 + } else if fee.upper_bound.map_or(false, |bound| fee_amount > bound.0) { + fee.upper_bound.unwrap().0 + } else { + fee_amount + }; + + std::cmp::min(bounded_fee_amount, transfer_amount) +} + +#[near_bindgen] +impl BridgeTokenFactory { + //this should be added as per: 10% -> 0.1 = 0.1*10^6 + #[access_control_any(roles(Role::FeeSetter))] + pub fn set_deposit_fee( + &mut self, + token: EthAddressHex, + fee_percentage: U128, + lower_bound: Option, + upper_bound: Option, + ) { + self.deposit_fee.insert( + &token.0, + &Fee { + fee_percentage, + lower_bound, + upper_bound, + }, + ); + } + + #[access_control_any(roles(Role::FeeSetter))] + pub fn unset_deposit_fee(&mut self, token: EthAddressHex) { + self.deposit_fee.remove(&token.0); + } + + // Fee should be added as per: 10% -> 0.1 = 0.1*10^6 with proper fee amount bounds + #[access_control_any(roles(Role::FeeSetter))] + pub fn set_deposit_fee_per_silo( + &mut self, + silo_account_id: AccountId, + token: Option, + fee_percentage: U128, + lower_bound: Option, + upper_bound: Option, + ) { + self.deposit_fee_per_silo.insert( + &get_silo_fee_map_key(&silo_account_id, token.as_ref()), + &Fee { + fee_percentage, + lower_bound, + upper_bound, + }, + ); + } + + #[access_control_any(roles(Role::FeeSetter))] + pub fn unset_deposit_fee_per_silo( + &mut self, + silo_account_id: AccountId, + token: Option, + ) { + self.deposit_fee_per_silo + .remove(&get_silo_fee_map_key(&silo_account_id, token.as_ref())); + } + + // Fee should be added as per: 10% -> 0.1 = 0.1*10^6 with proper fee amount bounds + #[access_control_any(roles(Role::FeeSetter))] + pub fn set_withdraw_fee( + &mut self, + token: EthAddressHex, + fee_percentage: U128, + lower_bound: Option, + upper_bound: Option, + ) { + self.withdraw_fee.insert( + &token.0, + &Fee { + fee_percentage, + lower_bound, + upper_bound, + }, + ); + } + + #[access_control_any(roles(Role::FeeSetter))] + pub fn unset_withdraw_fee(&mut self, token: EthAddressHex) { + self.withdraw_fee.remove(&token.0); + } + + // Fee should be added as per: 10% -> 0.1 = 0.1*10^6 with proper fee amount bounds + #[access_control_any(roles(Role::FeeSetter))] + pub fn set_withdraw_fee_per_silo( + &mut self, + silo_account_id: AccountId, + token: Option, + fee_percentage: U128, + lower_bound: Option, + upper_bound: Option, + ) { + self.withdraw_fee_per_silo.insert( + &get_silo_fee_map_key(&silo_account_id, token.as_ref()), + &Fee { + fee_percentage, + lower_bound, + upper_bound, + }, + ); + } + + #[access_control_any(roles(Role::FeeSetter))] + pub fn unset_withdraw_fee_per_silo( + &mut self, + silo_account_id: AccountId, + token: Option, + ) { + self.withdraw_fee_per_silo + .remove(&get_silo_fee_map_key(&silo_account_id, token.as_ref())); + } + + pub fn get_deposit_fee(&self, token: &EthAddressHex) -> Option { + self.deposit_fee.get(&token.0) + } + + pub fn get_withdraw_fee(&self, token: &EthAddressHex) -> Option { + self.withdraw_fee.get(&token.0) + } + + // Returns withdraw fee for different tokens per silo + pub fn get_withdraw_fee_per_silo( + &self, + silo_account_id: AccountId, + token: Option, + ) -> Option { + self.get_withdraw_fee_per_silo_internal(&silo_account_id, token.as_ref()) + } + + // Returns desposit fee for different tokens per silo + pub fn get_desposit_fee_per_silo( + &self, + silo_account_id: AccountId, + token: Option, + ) -> Option { + self.get_deposit_fee_per_silo_internal(&silo_account_id, token.as_ref()) + } + + // Accumulated fee should be claimed from here. + #[payable] + #[access_control_any(roles(Role::FeeClaimer))] + pub fn claim_fee(&mut self, token: AccountId, amount: Balance) -> Promise { + assert_one_yocto(); + ext_bridge_token::ext(token) + .with_static_gas(FT_TRANSFER_GAS) + .with_attached_deposit(ONE_YOCTO) + .ft_transfer(env::predecessor_account_id(), amount.into(), None) + } + + pub fn calculate_deposit_fee_amount( + &self, + token: &EthAddressHex, + amount: U128, + target: Option, + ) -> U128 { + let Some(deposit_fee) = target + .and_then(|target| self.get_deposit_fee_per_silo_internal(&target, Some(token))) + .or_else(|| self.get_deposit_fee(token)) + else { return U128(0) }; + + U128(calculate_fee_amount(amount.0, &deposit_fee)) + } + + pub fn calculate_withdraw_fee_amount( + &self, + token: &EthAddressHex, + amount: U128, + withdrawer: &AccountId, + ) -> U128 { + let Some(withdraw_fee) = self.get_withdraw_fee_per_silo_internal(withdrawer, Some(token)) + .or_else(|| self.get_withdraw_fee(token)) + else { return U128(0) }; + + U128(calculate_fee_amount(amount.0, &withdraw_fee)) + } + + pub(crate) fn get_withdraw_fee_per_silo_internal( + &self, + silo_account_id: &AccountId, + token: Option<&EthAddressHex>, + ) -> Option { + if token.is_some() { + self.withdraw_fee_per_silo + .get(&get_silo_fee_map_key(&silo_account_id, token)) + .or_else(|| { + self.withdraw_fee_per_silo + .get(&get_silo_fee_map_key(&silo_account_id, None)) + }) + } else { + self.withdraw_fee_per_silo + .get(&get_silo_fee_map_key(&silo_account_id, None)) + } + } + + pub(crate) fn get_deposit_fee_per_silo_internal( + &self, + silo_account_id: &AccountId, + token: Option<&EthAddressHex>, + ) -> Option { + if token.is_some() { + self.deposit_fee_per_silo + .get(&get_silo_fee_map_key(&silo_account_id, token)) + .or_else(|| { + self.deposit_fee_per_silo + .get(&get_silo_fee_map_key(&silo_account_id, None)) + }) + } else { + self.deposit_fee_per_silo + .get(&get_silo_fee_map_key(&silo_account_id, None)) + } + } +} + +#[cfg(not(target_arch = "wasm32"))] +#[cfg(test)] +mod tests { + use super::*; + use crate::tests::*; + use near_sdk::test_utils::VMContextBuilder; + use near_sdk::testing_env; + use std::convert::TryInto; + + fn fee_setter() -> AccountId { + "fee_setter.near".parse().unwrap() + } + + fn silo_account() -> AccountId { + "silo.aurora".parse().unwrap() + } + + #[test] + fn test_fee_token_bound_for_deposit_fee() { + set_env!(predecessor_account_id: alice(), current_account_id: alice()); + let token_address = ethereum_address_from_id(2); + let mut contract = BridgeTokenFactory::new(prover(), token_locker()); + contract.acl_grant_role("FeeSetter".to_string(), fee_setter()); + set_env!( + predecessor_account_id: fee_setter(), + attached_deposit: BRIDGE_TOKEN_INIT_BALANCE * 2 + ); + contract.deploy_bridge_token(token_address.clone()); + // setting fee-percentage and bounds + contract.set_deposit_fee( + token_address.clone(), + U128(50000), + Some(U128(100)), + Some(U128(200)), + ); + let fee = contract.get_deposit_fee(&token_address).unwrap(); + + assert_eq!( + U128(100), + fee.lower_bound.unwrap(), + "Lower bound not matched" + ); + assert_eq!( + U128(200), + fee.upper_bound.unwrap(), + "Upper bound not matched" + ); + } + + #[test] + fn test_adujsted_fee_amount() { + set_env!(predecessor_account_id: alice(), current_account_id: alice()); + let token_address = ethereum_address_from_id(2); + let mut contract = BridgeTokenFactory::new(prover(), token_locker()); + contract.acl_grant_role("FeeSetter".to_string(), fee_setter()); + set_env!( + predecessor_account_id: fee_setter(), + attached_deposit: BRIDGE_TOKEN_INIT_BALANCE * 2 + ); + contract.deploy_bridge_token(token_address.clone()); + // setting fee-percentage and bounds + contract.set_deposit_fee( + token_address.clone(), + U128(50000), + Some(U128(100)), + Some(U128(200)), + ); + // let deposit_bound = contract.set_deposit_fee_bound(&token_address, , U128(100)); + let expected_fee = contract.get_deposit_fee(&token_address).unwrap(); + assert_eq!( + U128(100), + expected_fee.lower_bound.unwrap(), + "Lower bound not matched" + ); + assert_eq!( + U128(200), + expected_fee.upper_bound.unwrap(), + "Upper bound not matched" + ); + + let expected_fee = contract.get_deposit_fee(&token_address).unwrap(); + assert_eq!( + U128(50000), + expected_fee.fee_percentage, + "Eth -> Near fee percentage not matched for deposit" + ); + let adjusted_fee_amount = calculate_fee_amount(100u128, &expected_fee); + assert_eq!( + adjusted_fee_amount, + expected_fee.lower_bound.unwrap().0, + "Adjusted fee amount didn't matched as expected" + ); + } + + #[test] + #[should_panic] + fn test_fee_setter_for_deposit_with_unallowed_role() { + set_env!(predecessor_account_id: alice(), current_account_id: alice()); + let token_address = ethereum_address_from_id(2); + let mut contract = BridgeTokenFactory::new(prover(), token_locker()); + set_env!(predecessor_account_id: bob()); // bob is not allowed (has no role) to set deposit-fees; + contract.set_deposit_fee(token_address, U128(50000), Some(U128(100)), Some(U128(200))); + } + + #[test] + fn test_fee_token_bound_for_withdraw_fee() { + set_env!(predecessor_account_id: alice(), current_account_id: alice()); + let token_address = ethereum_address_from_id(4); + let mut contract = BridgeTokenFactory::new(prover(), token_locker()); + contract.acl_grant_role("FeeSetter".to_string(), fee_setter()); + set_env!( + predecessor_account_id: fee_setter(), + attached_deposit: BRIDGE_TOKEN_INIT_BALANCE * 2 + ); + contract.deploy_bridge_token(token_address.clone()); + contract.set_withdraw_fee( + token_address.clone(), + U128(50000), + Some(U128(100)), + Some(U128(200)), + ); + let fee = contract.get_withdraw_fee(&token_address).unwrap(); + assert_eq!( + U128(100), + fee.lower_bound.unwrap(), + "Lower bound not matched" + ); + assert_eq!( + U128(200), + fee.upper_bound.unwrap(), + "Upper bound not matched" + ); + } + + #[test] + #[should_panic] + fn test_fee_setter_for_withdraw_with_unallowed_role() { + set_env!(predecessor_account_id: alice(), current_account_id: alice()); + let token_address = ethereum_address_from_id(4); + let mut contract = BridgeTokenFactory::new(prover(), token_locker()); + contract.acl_grant_role("FeeSetter".to_string(), fee_setter()); + set_env!(predecessor_account_id: bob()); // bob is not allowed (has no role) to set deposit-fees; + contract.set_withdraw_fee( + token_address.clone(), + U128(50000), + Some(U128(100)), + Some(U128(200)), + ); + let fee = contract.get_withdraw_fee(&token_address).unwrap(); + assert_eq!( + U128(100), + fee.lower_bound.unwrap(), + "Lower bound not matched" + ); + assert_eq!( + U128(200), + fee.upper_bound.unwrap(), + "Upper bound not matched" + ); + } + + #[test] + fn test_fee_token_percentage_for_deposit_fee() { + set_env!(predecessor_account_id: alice(), current_account_id: alice()); + let token_address = ethereum_address_from_id(2); + let mut contract = BridgeTokenFactory::new(prover(), token_locker()); + contract.acl_grant_role("FeeSetter".to_string(), fee_setter()); + set_env!( + predecessor_account_id: fee_setter(), + attached_deposit: BRIDGE_TOKEN_INIT_BALANCE * 2 + ); + contract.deploy_bridge_token(token_address.clone()); + contract.set_deposit_fee( + token_address.clone(), + U128(50000), + Some(U128(100)), + Some(U128(200)), + ); // 0.05% + contract.set_deposit_fee_per_silo( + silo_account(), + Some(token_address.clone()), + U128(20000), + Some(U128(100)), + Some(U128(200)), + ); // 0.02% + + let expected_fee_percentage = contract + .get_desposit_fee_per_silo(silo_account(), Some(token_address.clone())) + .unwrap() + .fee_percentage; + assert_eq!( + U128(20000), + expected_fee_percentage, + "Eth -> Aurora fee percentage not matched for deposit" + ); + + let expected_fee_percentage = contract + .get_deposit_fee(&token_address) + .unwrap() + .fee_percentage; + assert_eq!( + U128(50000), + expected_fee_percentage, + "Eth -> Near fee percentage not matched for deposit" + ); + } + + #[test] + fn test_fee_token_percentage_setter_for_withdraw() { + set_env!(predecessor_account_id: alice(), current_account_id: alice()); + let token_address = ethereum_address_from_id(2); + let mut contract = BridgeTokenFactory::new(prover(), token_locker()); + contract.acl_grant_role("FeeSetter".to_string(), fee_setter()); + set_env!( + predecessor_account_id: fee_setter(), + attached_deposit: BRIDGE_TOKEN_INIT_BALANCE * 2 + ); + contract.deploy_bridge_token(token_address.clone()); + contract.set_withdraw_fee( + token_address.clone(), + U128(90000), + Some(U128(100)), + Some(U128(200)), + ); // 0.09% + contract.set_withdraw_fee_per_silo( + silo_account(), + Some(token_address.clone()), + U128(40000), + Some(U128(100)), + Some(U128(200)), + ); // 0.04% + let expected_fee_percentage = contract + .get_withdraw_fee_per_silo(silo_account(), Some(token_address.clone())) + .unwrap() + .fee_percentage; + assert_eq!( + U128(40000), + expected_fee_percentage, + "Aurora -> Eth fee percentage not matched for withdraw" + ); + + let expected_fee_percentage = contract + .get_withdraw_fee(&token_address) + .unwrap() + .fee_percentage; + assert_eq!( + U128(90000), + expected_fee_percentage, + "Near -> Eth fee percentage not matched for withdraw" + ); + } + + #[test] + fn test_withdraw_fee_setter_silo() { + set_env!(predecessor_account_id: alice(), current_account_id: alice()); + let token_address = ethereum_address_from_id(2); + let mut contract = BridgeTokenFactory::new(prover(), token_locker()); + contract.acl_grant_role("FeeSetter".to_string(), fee_setter()); + set_env!(predecessor_account_id: fee_setter()); + + let withdraw_fee1 = Fee { + fee_percentage: U128(100000), + lower_bound: None, + upper_bound: None, + }; + contract.set_withdraw_fee_per_silo( + silo_account(), + Some(token_address.clone()), + withdraw_fee1.fee_percentage, + withdraw_fee1.lower_bound, + withdraw_fee1.upper_bound, + ); // 10% fee + let expected_fee1 = + contract.get_withdraw_fee_per_silo(silo_account(), Some(token_address.clone())); + + let withdraw_fee2 = Fee { + fee_percentage: U128(200000), + lower_bound: None, + upper_bound: None, + }; + contract.set_withdraw_fee_per_silo( + silo_account(), + Some(token_address.clone()), + withdraw_fee2.fee_percentage, + withdraw_fee2.lower_bound, + withdraw_fee2.upper_bound, + ); //20% fee + let expected_fee2 = contract.get_withdraw_fee_per_silo(silo_account(), Some(token_address)); + + assert_eq!( + withdraw_fee1, + expected_fee1.unwrap(), + "Aurora -> Eth fee percentage not matched for withdraw" + ); + assert_eq!( + withdraw_fee2, + expected_fee2.unwrap(), + "Aurora -> Eth fee percentage not matched for withdraw" + ); + } + + #[test] + fn test_get_silo_with_token() { + let _contract = BridgeTokenFactory::new(prover(), token_locker()); + let token_address = ethereum_address_from_id(1); + let expected_key_with_token = get_silo_fee_map_key(&silo_account(), Some(&token_address)); + let expected_key_without_token = get_silo_fee_map_key(&silo_account(), None); + assert_eq!( + expected_key_with_token, + format!("{}:{}", silo_account(), token_address.0), + "Expected silo with token address not matched" + ); + assert_eq!( + expected_key_without_token, + silo_account().to_string(), + "Expected key without token is not matched" + ); + } + #[test] + fn test_withdraw_fee_setter_silo_for_2_different_tokens() { + set_env!(predecessor_account_id: alice(), current_account_id: alice()); + let token1_address = ethereum_address_from_id(1); + let token2_address = ethereum_address_from_id(2); + let token3_address = ethereum_address_from_id(3); + let mut contract = BridgeTokenFactory::new(prover(), token_locker()); + contract.acl_grant_role("FeeSetter".to_string(), fee_setter()); + set_env!(predecessor_account_id: fee_setter()); + + let withdraw_fee_percentage1 = U128(100000); + contract.set_withdraw_fee_per_silo( + silo_account(), + Some(token1_address.clone()), + U128(100000), + None, + None, + ); // 10% fee + + let withdraw_fee_percentage2 = U128(100000); + contract.set_withdraw_fee_per_silo( + silo_account(), + Some(token2_address.clone()), + U128(100000), + None, + None, + ); //20% fee + let expected_fee_percentage1 = contract + .get_withdraw_fee_per_silo(silo_account(), Some(token1_address)) + .unwrap() + .fee_percentage; + let expected_fee_percentage2 = contract + .get_withdraw_fee_per_silo(silo_account(), Some(token2_address)) + .unwrap() + .fee_percentage; + // for token-3 fee is not set + let expected_fee_percentage3 = + contract.get_withdraw_fee_per_silo(silo_account(), Some(token3_address)); + + assert_eq!( + withdraw_fee_percentage1, expected_fee_percentage1, + "Aurora -> Eth fee percentage not matched for withdraw" + ); + assert_eq!( + withdraw_fee_percentage2, expected_fee_percentage2, + "Aurora -> Eth fee percentage not matched for withdraw" + ); + assert_eq!( + expected_fee_percentage3, None, + "Aurora -> Eth fee percentage not matched for withdraw" + ); + } + + #[test] + fn test_withdraw_fee_setter_silo_with_default_fee_and_per_token_fee() { + set_env!(predecessor_account_id: alice(), current_account_id: alice()); + let token_address = ethereum_address_from_id(2); + let mut contract = BridgeTokenFactory::new(prover(), token_locker()); + contract.acl_grant_role("FeeSetter".to_string(), fee_setter()); + set_env!(predecessor_account_id: fee_setter()); + + let withdraw_fee_percentage = U128(200000); + contract.set_withdraw_fee_per_silo( + silo_account(), + Some(token_address.clone()), + U128(200000), + None, + None, + ); // 20% fee + + let default_withdraw_fee_percentage = U128(100000); + contract.set_withdraw_fee_per_silo(silo_account(), None, U128(100000), None, None); // 10% fee + // Below token is not registered token therefore fees is default one's. + + let expected_fee_percentage = contract + .get_withdraw_fee_per_silo(silo_account(), Some(token_address)) + .unwrap() + .fee_percentage; + let expected_default_fee_percentage = contract + .get_withdraw_fee_per_silo(silo_account(), None) + .unwrap() + .fee_percentage; + + assert_eq!( + withdraw_fee_percentage, expected_fee_percentage, + "Aurora -> Eth fee percentage not matched for withdraw" + ); + assert_eq!( + default_withdraw_fee_percentage, expected_default_fee_percentage, + "Aurora -> Eth fee percentage not matched for withdraw" + ); + } + + #[test] + fn test_withdraw_fee_setter_silo_without_default_fee_and_per_token_fee() { + set_env!(predecessor_account_id: alice(), current_account_id: alice()); + + let contract = BridgeTokenFactory::new(prover(), token_locker()); + + let expected_fee_percentage = contract.get_withdraw_fee_per_silo(silo_account(), None); + assert_eq!( + expected_fee_percentage, None, + "Aurora -> Eth fee percentage not matched for withdraw" + ); + } +} diff --git a/bridge-token-factory/src/lib.rs b/bridge-token-factory/src/lib.rs index 61c1335b..56a09d18 100644 --- a/bridge-token-factory/src/lib.rs +++ b/bridge-token-factory/src/lib.rs @@ -1,19 +1,29 @@ -use admin_controlled::{AdminControlled, Mask}; +use near_plugins::{ + access_control, access_control_any, pause, AccessControlRole, AccessControllable, Pausable, + Upgradable, +}; use near_sdk::borsh::{self, BorshDeserialize, BorshSerialize}; use near_sdk::collections::{UnorderedMap, UnorderedSet}; use near_sdk::json_types::{Base64VecU8, U128}; +use near_sdk::serde::{Deserialize, Serialize}; use near_sdk::{ - env, ext_contract, near_bindgen, AccountId, Balance, Gas, PanicOnDefault, Promise, PublicKey, + env, ext_contract, near_bindgen, require, AccountId, Balance, BorshStorageKey, Gas, + PanicOnDefault, Promise, PromiseOrValue, PublicKey, ONE_NEAR, ONE_YOCTO, }; pub use bridge_common::prover::{validate_eth_address, Proof}; use bridge_common::{parse_recipient, prover::*, result_types, Recipient}; pub use lock_event::EthLockedEvent; pub use log_metadata_event::TokenMetadataEvent; -use near_sdk_inner::{PromiseOrValue, ONE_NEAR}; +use types::{EthAddressHex, Fee}; + +use crate::types::SdkUnwrap; +mod fee; mod lock_event; mod log_metadata_event; +mod migration; +mod types; const BRIDGE_TOKEN_BINARY: &'static [u8] = include_bytes!(std::env!( "BRIDGE_TOKEN", @@ -43,6 +53,12 @@ const OUTER_SET_METADATA_GAS: Gas = Gas(Gas::ONE_TERA.0 * 15); /// Amount of gas used by bridge token to set the metadata. const SET_METADATA_GAS: Gas = Gas(Gas::ONE_TERA.0 * 5); +/// Amount of gas used by bridge token to pause withdraw. +const SET_PAUSED_GAS: Gas = Gas(Gas::ONE_TERA.0 * 5); + +/// Amount of gas used upgrade and migrate bridge token. +const UPGRADE_TOKEN_GAS: Gas = Gas(Gas::ONE_TERA.0 * 200); + /// Controller storage key. const CONTROLLER_STORAGE_KEY: &[u8] = b"aCONTROLLER"; @@ -54,17 +70,58 @@ const METADATA_CONNECTOR_ETH_ADDRESS_STORAGE_KEY: &[u8] = b"aM_CONNECTOR"; /// The prefix is made specially short since it becomes more expensive with larger prefixes. const TOKEN_TIMESTAMP_MAP_PREFIX: &[u8] = b"aTT"; -const PAUSE_DEPLOY_TOKEN: Mask = 1 << 0; -const PAUSE_DEPOSIT: Mask = 1 << 1; +const FEE_DECIMAL_PRECISION: u128 = 1000000; + +#[derive(AccessControlRole, Deserialize, Serialize, Copy, Clone)] +#[serde(crate = "near_sdk::serde")] +pub enum Role { + PauseManager, + UpgradableManager, + UpgradableCodeStager, + UpgradableCodeDeployer, + UpgradableDurationManager, + ConfigManager, + UnrestrictedDeposit, + UnrestrictedDeployBridgeToken, + MetadataManager, + FeeSetter, + FeeClaimer, +} + +#[derive(BorshSerialize, BorshStorageKey)] +enum StorageKey { + DepositFee, + WihdrawFee, + WithdrawFeePerSilo, + DespositFeePerSilo, +} + +fn get_silo_fee_map_key(silo_account_id: &AccountId, token: Option<&EthAddressHex>) -> String { + if let Some(token) = token { + format!("{}:{}", silo_account_id, token.0) + } else { + silo_account_id.to_string() + } +} #[near_bindgen] -#[derive(BorshDeserialize, BorshSerialize, PanicOnDefault)] +#[derive(BorshDeserialize, BorshSerialize, PanicOnDefault, Pausable, Upgradable)] +#[access_control(role_type(Role))] +#[pausable(manager_roles(Role::PauseManager))] +#[upgradable(access_control_roles( + code_stagers(Role::UpgradableCodeStager, Role::UpgradableManager), + code_deployers(Role::UpgradableCodeDeployer, Role::UpgradableManager), + duration_initializers(Role::UpgradableDurationManager, Role::UpgradableManager), + duration_update_stagers(Role::UpgradableDurationManager, Role::UpgradableManager), + duration_update_appliers(Role::UpgradableDurationManager, Role::UpgradableManager), +))] pub struct BridgeTokenFactory { /// The account of the prover that we can use to prove pub prover_account: AccountId, /// Address of the Ethereum locker contract. pub locker_address: EthAddress, /// Set of created BridgeToken contracts. + /// Key: ETH token address as a lowercase hex string without `0x`. pub tokens: UnorderedSet, /// Hashes of the events that were already used. pub used_events: UnorderedSet>, @@ -72,8 +129,18 @@ pub struct BridgeTokenFactory { pub owner_pk: PublicKey, /// Balance required to register a new account in the BridgeToken pub bridge_token_storage_deposit_required: Balance, - /// Mask determining all paused functions - paused: Mask, + /// Deposit fee storage + /// Key: ETH token address as a lowercase hex string without `0x`. + pub deposit_fee: UnorderedMap, + /// Withdraw fee storage. + /// Key: ETH token address as a lowercase hex string without `0x`. + pub withdraw_fee: UnorderedMap, + /// Withdraw fee for each different silos for different tokens + /// Key: `silo_account_id:eth_token_address` or `silo_account_id`. + pub withdraw_fee_per_silo: UnorderedMap, + /// Deposit fee for each different silos for different tokens + /// Key: `silo_account_id:eth_token_address` or `silo_account_id`. + pub deposit_fee_per_silo: UnorderedMap, } #[ext_contract(ext_self)] @@ -104,11 +171,6 @@ pub trait ExtBridgeTokenFactory { ) -> Promise; } -#[ext_contract(ext_fungible_token)] -pub trait FungibleToken { - fn ft_transfer(&mut self, receiver_id: AccountId, amount: U128, memo: Option); -} - #[ext_contract(ext_bridge_token)] pub trait ExtBridgeToken { fn mint(&self, account_id: AccountId, amount: U128); @@ -130,6 +192,14 @@ pub trait ExtBridgeToken { decimals: Option, icon: Option, ); + + fn ft_transfer(&mut self, receiver_id: AccountId, amount: U128, memo: Option); + + fn set_paused(&mut self, paused: bool); + + fn upgrade_and_migrate(&mut self, code: &[u8]); + + fn storage_deposit(&mut self, account_id: Option, registration_only: Option); } pub fn assert_self() { @@ -144,7 +214,8 @@ impl BridgeTokenFactory { #[init] pub fn new(prover_account: AccountId, locker_address: String) -> Self { assert!(!env::state_exists(), "Already initialized"); - Self { + #[allow(deprecated)] + let mut contract = Self { prover_account, locker_address: validate_eth_address(locker_address), tokens: UnorderedSet::new(b"t".to_vec()), @@ -154,8 +225,14 @@ impl BridgeTokenFactory { near_contract_standards::fungible_token::FungibleToken::new(b"t".to_vec()) .account_storage_usage as Balance * env::storage_byte_cost(), - paused: Mask::default(), - } + deposit_fee: UnorderedMap::new(StorageKey::DepositFee), + withdraw_fee: UnorderedMap::new(StorageKey::WihdrawFee), + withdraw_fee_per_silo: UnorderedMap::new(StorageKey::WithdrawFeePerSilo), + deposit_fee_per_silo: UnorderedMap::new(StorageKey::DespositFeePerSilo), + }; + + contract.acl_init_super_admin(near_sdk::env::predecessor_account_id()); + contract } pub fn update_metadata(&mut self, #[serializer(borsh)] proof: Proof) -> Promise { @@ -213,11 +290,12 @@ impl BridgeTokenFactory { ), ) } + /// Deposit from Ethereum to NEAR based on the proof of the locked tokens. /// Must attach enough NEAR funds to cover for storage of the proof. #[payable] + #[pause(except(roles(Role::UnrestrictedDeposit)))] pub fn deposit(&mut self, #[serializer(borsh)] proof: Proof) -> Promise { - self.check_not_paused(PAUSE_DEPOSIT); let event = EthLockedEvent::from_log_entry_data(&proof.log_entry_data); assert_eq!( event.locker_address, @@ -295,7 +373,7 @@ impl BridgeTokenFactory { let reference_hash = None; let icon = None; - ext_bridge_token::ext(self.get_bridge_token_account_id(token.clone())) + ext_bridge_token::ext(self.get_bridge_token_account_id(&EthAddressHex(token))) .with_static_gas(SET_METADATA_GAS) .with_attached_deposit(env::attached_deposit() - required_deposit) .set_metadata( @@ -326,9 +404,9 @@ impl BridgeTokenFactory { let required_deposit = self.record_proof(&proof); - assert!( + require!( env::attached_deposit() - >= required_deposit + self.bridge_token_storage_deposit_required + >= required_deposit + self.bridge_token_storage_deposit_required + ONE_YOCTO ); let Recipient { target, message } = parse_recipient(new_owner_id); @@ -338,21 +416,63 @@ impl BridgeTokenFactory { target, message )); + let token = EthAddressHex(token); + let fee_amount = self + .calculate_deposit_fee_amount( + &token, + amount.into(), + message.as_ref().and_then(|_| Some(target.clone())), + ) + .0; + let amount_to_transfer = amount.checked_sub(fee_amount).sdk_unwrap(); + + env::log_str(&format!( + "Finish deposit. Target:{} Message:{:?}", + target, message + )); + match message { - Some(message) => ext_bridge_token::ext(self.get_bridge_token_account_id(token.clone())) - .with_static_gas(MINT_GAS) - .with_attached_deposit(env::attached_deposit() - required_deposit) - .mint(env::current_account_id(), amount.into()) - .then( - ext_bridge_token::ext(self.get_bridge_token_account_id(token)) - .with_static_gas(FT_TRANSFER_CALL_GAS) - .with_attached_deposit(1) - .ft_transfer_call(target, amount.into(), None, message), - ), - None => ext_bridge_token::ext(self.get_bridge_token_account_id(token)) - .with_static_gas(MINT_GAS) - .with_attached_deposit(env::attached_deposit() - required_deposit) - .mint(target, amount.into()), + Some(message) => { + let mint_self_promise = + ext_bridge_token::ext(self.get_bridge_token_account_id(&token)) + .with_static_gas(MINT_GAS) + .with_attached_deposit(self.bridge_token_storage_deposit_required) + .mint(env::current_account_id(), amount.into()); + + if amount_to_transfer > 0 { + mint_self_promise.then( + ext_bridge_token::ext(self.get_bridge_token_account_id(&token)) + .with_static_gas(FT_TRANSFER_CALL_GAS) + .with_attached_deposit(ONE_YOCTO) + .ft_transfer_call(target, amount_to_transfer.into(), None, message), + ) + } else { + mint_self_promise + } + } + None => { + if fee_amount > 0 { + let mint_fee_promise = + ext_bridge_token::ext(self.get_bridge_token_account_id(&token)) + .with_static_gas(MINT_GAS) + .mint(env::current_account_id(), fee_amount.into()); + + if amount_to_transfer > 0 { + ext_bridge_token::ext(self.get_bridge_token_account_id(&token)) + .with_static_gas(MINT_GAS) + .with_attached_deposit(self.bridge_token_storage_deposit_required) + .mint(target, amount_to_transfer.into()) + .then(mint_fee_promise) + } else { + mint_fee_promise + } + } else { + ext_bridge_token::ext(self.get_bridge_token_account_id(&token)) + .with_static_gas(MINT_GAS) + .with_attached_deposit(self.bridge_token_storage_deposit_required) + .mint(target, amount_to_transfer.into()) + } + } } } @@ -363,36 +483,50 @@ impl BridgeTokenFactory { #[result_serializer(borsh)] pub fn finish_withdraw( &mut self, + #[serializer(borsh)] withdrawer: AccountId, #[serializer(borsh)] amount: Balance, #[serializer(borsh)] recipient: String, ) -> result_types::Withdraw { let token = env::predecessor_account_id(); let parts: Vec<&str> = token.as_str().split('.').collect(); + let token_address_hex = EthAddressHex(parts[0].to_string()); + assert_eq!( token.to_string(), - format!("{}.{}", parts[0], env::current_account_id()), + format!("{}.{}", token_address_hex.0, env::current_account_id()), "Only sub accounts of BridgeTokenFactory can call this method." ); assert!( - self.tokens.contains(&parts[0].to_string()), + self.tokens.contains(&token_address_hex.0), "Such BridgeToken does not exist." ); - let token_address = validate_eth_address(parts[0].to_string()); + + let fee_amount = self + .calculate_withdraw_fee_amount(&token_address_hex, amount.into(), &withdrawer) + .0; + let token_address = validate_eth_address(token_address_hex.0); let recipient_address = validate_eth_address(recipient); - result_types::Withdraw::new(amount, token_address, recipient_address) + + if fee_amount != 0 { + ext_bridge_token::ext(token) + .with_static_gas(MINT_GAS) + .with_attached_deposit(env::attached_deposit()) + .mint(env::current_account_id(), fee_amount.into()); + }; + + let amount_to_transfer = amount.checked_sub(fee_amount).sdk_unwrap(); + result_types::Withdraw::new(amount_to_transfer, token_address, recipient_address) } #[payable] - pub fn deploy_bridge_token(&mut self, address: String) -> Promise { - self.check_not_paused(PAUSE_DEPLOY_TOKEN); - let address = address.to_lowercase(); - let _ = validate_eth_address(address.clone()); + #[pause(except(roles(Role::UnrestrictedDeployBridgeToken)))] + pub fn deploy_bridge_token(&mut self, address: EthAddressHex) -> Promise { assert!( - !self.tokens.contains(&address), + !self.tokens.contains(&address.0), "BridgeToken contract already exists." ); let initial_storage = env::storage_usage() as u128; - self.tokens.insert(&address); + self.tokens.insert(&address.0); let current_storage = env::storage_usage() as u128; assert!( env::attached_deposit() @@ -400,7 +534,7 @@ impl BridgeTokenFactory { + env::storage_byte_cost() * (current_storage - initial_storage), "Not enough attached deposit to complete bridge token creation" ); - let bridge_token_account_id = format!("{}.{}", address, env::current_account_id()); + let bridge_token_account_id = format!("{}.{}", address.0, env::current_account_id()); Promise::new(bridge_token_account_id.parse().unwrap()) .create_account() .transfer(BRIDGE_TOKEN_INIT_BALANCE) @@ -414,14 +548,29 @@ impl BridgeTokenFactory { ) } - pub fn get_bridge_token_account_id(&self, address: String) -> AccountId { - let address = address.to_lowercase(); - let _ = validate_eth_address(address.clone()); + #[access_control_any(roles(Role::UpgradableCodeDeployer, Role::UpgradableManager))] + pub fn upgrade_bridge_token(&self, address: EthAddressHex) -> Promise { + Promise::new(self.get_bridge_token_account_id(&address)).function_call( + "upgrade_and_migrate".to_string(), + BRIDGE_TOKEN_BINARY.into(), + 0, + UPGRADE_TOKEN_GAS, + ) + } + + #[access_control_any(roles(Role::PauseManager))] + pub fn set_paused_withdraw(&mut self, address: EthAddressHex, paused: bool) -> Promise { + ext_bridge_token::ext(self.get_bridge_token_account_id(&address)) + .with_static_gas(SET_PAUSED_GAS) + .set_paused(paused) + } + + pub fn get_bridge_token_account_id(&self, address: &EthAddressHex) -> AccountId { assert!( - self.tokens.contains(&address), + self.tokens.contains(&address.0), "BridgeToken with such address does not exist." ); - format!("{}.{}", address, env::current_account_id()) + format!("{}.{}", address.0, env::current_account_id()) .parse() .unwrap() } @@ -452,10 +601,11 @@ impl BridgeTokenFactory { required_deposit } - /// Admin method to set metadata with admin/controller access + /// Admin method to set metadata + #[access_control_any(roles(Role::MetadataManager))] pub fn set_metadata( &mut self, - address: String, + address: EthAddressHex, name: Option, symbol: Option, reference: Option, @@ -463,8 +613,7 @@ impl BridgeTokenFactory { decimals: Option, icon: Option, ) -> Promise { - assert!(self.controller_or_self()); - ext_bridge_token::ext(self.get_bridge_token_account_id(address)) + ext_bridge_token::ext(self.get_bridge_token_account_id(&address)) .with_static_gas(env::prepaid_gas() - OUTER_SET_METADATA_GAS) .with_attached_deposit(env::attached_deposit()) .set_metadata(name, symbol, reference, reference_hash, decimals, icon) @@ -486,21 +635,6 @@ impl BridgeTokenFactory { }) } - pub fn set_controller(&mut self, controller: AccountId) { - assert!(self.controller_or_self()); - assert!(env::is_valid_account_id(controller.as_bytes())); - env::storage_write(CONTROLLER_STORAGE_KEY, controller.as_bytes()); - } - - pub fn controller_or_self(&self) -> bool { - let caller = env::predecessor_account_id(); - caller == env::current_account_id() - || self - .controller() - .map(|controller| controller == caller) - .unwrap_or(false) - } - /// Ethereum Metadata Connector. This is the address where the contract that emits metadata from tokens /// on ethereum is deployed. Address is encoded as hex. pub fn metadata_connector(&self) -> Option { @@ -508,17 +642,19 @@ impl BridgeTokenFactory { .map(|value| String::from_utf8(value).expect("Invalid metadata connector address")) } + #[access_control_any(roles(Role::ConfigManager))] pub fn set_metadata_connector(&mut self, metadata_connector: String) { - assert!(self.controller_or_self()); validate_eth_address(metadata_connector.clone()); env::storage_write( METADATA_CONNECTOR_ETH_ADDRESS_STORAGE_KEY, metadata_connector.as_bytes(), ); } -} -admin_controlled::impl_admin_controlled!(BridgeTokenFactory, paused); + pub fn version(&self) -> String { + env!("CARGO_PKG_VERSION").to_owned() + } +} #[cfg(not(target_arch = "wasm32"))] #[cfg(test)] @@ -533,8 +669,6 @@ mod tests { use super::*; - const UNPAUSE_ALL: Mask = 0; - macro_rules! inner_set_env { ($builder:ident) => { $builder @@ -557,30 +691,43 @@ mod tests { }; } - fn alice() -> AccountId { + pub(crate) use inner_set_env; + pub(crate) use set_env; + + pub fn alice() -> AccountId { "alice.near".parse().unwrap() } - fn prover() -> AccountId { + pub fn bob() -> AccountId { + "bob.near".parse().unwrap() + } + + pub fn prover() -> AccountId { "prover".parse().unwrap() } - fn bridge_token_factory() -> AccountId { + pub fn bridge_token_factory() -> AccountId { "bridge".parse().unwrap() } - fn token_locker() -> String { + pub fn pause_manager() -> AccountId { + "pause_manager".parse().unwrap() + } + + pub fn token_locker() -> String { "6b175474e89094c44da98b954eedeac495271d0f".to_string() } /// Generate a valid ethereum address - fn ethereum_address_from_id(id: u8) -> String { + pub fn ethereum_address_from_id(id: u8) -> EthAddressHex { let mut buffer = vec![id]; - sha256(buffer.as_mut()) - .into_iter() - .take(20) - .collect::>() - .to_hex() + EthAddressHex( + sha256(buffer.as_mut()) + .into_iter() + .take(20) + .collect::>() + .to_hex(), + ) } fn sample_proof() -> Proof { @@ -628,7 +775,7 @@ mod tests { predecessor_account_id: alice(), attached_deposit: BRIDGE_TOKEN_INIT_BALANCE, ); - contract.deploy_bridge_token(token_locker()); + contract.deploy_bridge_token(token_locker().parse().unwrap()); } #[test] @@ -653,19 +800,20 @@ mod tests { attached_deposit: BRIDGE_TOKEN_INIT_BALANCE * 2, ); - contract.deploy_bridge_token(token_locker()); + contract.deploy_bridge_token(token_locker().parse().unwrap()); assert_eq!( contract - .get_bridge_token_account_id(token_locker()) + .get_bridge_token_account_id(&EthAddressHex(token_locker())) .to_string(), format!("{}.{}", token_locker(), bridge_token_factory()) ); let uppercase_address = "0f5Ea0A652E851678Ebf77B69484bFcD31F9459B".to_string(); - contract.deploy_bridge_token(uppercase_address.clone()); + let parsed_address: EthAddressHex = uppercase_address.parse().unwrap(); + contract.deploy_bridge_token(parsed_address.clone()); assert_eq!( contract - .get_bridge_token_account_id(uppercase_address.clone()) + .get_bridge_token_account_id(&parsed_address) .to_string(), format!( "{}.{}", @@ -684,7 +832,7 @@ mod tests { predecessor_account_id: alice(), attached_deposit: BRIDGE_TOKEN_INIT_BALANCE * 2 ); - contract.deploy_bridge_token(token_locker()); + contract.deploy_bridge_token(token_locker().parse().unwrap()); set_env!( current_account_id: bridge_token_factory(), @@ -693,17 +841,30 @@ mod tests { let address = validate_eth_address(token_locker()); assert_eq!( - contract.finish_withdraw(1_000, token_locker()), + contract.finish_withdraw(alice(), 1_000, token_locker()), result_types::Withdraw::new(1_000, address, address) ); } #[test] fn deploy_bridge_token_paused() { - set_env!(predecessor_account_id: alice()); + set_env!( + current_account_id: bridge_token_factory(), + predecessor_account_id: bridge_token_factory(), + ); // User alice can deploy a new bridge token let mut contract = BridgeTokenFactory::new(prover(), token_locker()); + assert!(contract + .acl_grant_role("PauseManager".to_owned(), pause_manager()) + .unwrap()); + let unrestricted_deploy_account: AccountId = "unrestricted_account".parse().unwrap(); + assert!(contract + .acl_grant_role( + "UnrestrictedDeployBridgeToken".to_owned(), + unrestricted_deploy_account.clone() + ) + .unwrap()); set_env!( current_account_id: bridge_token_factory(), predecessor_account_id: alice(), @@ -711,20 +872,20 @@ mod tests { ); contract.deploy_bridge_token(ethereum_address_from_id(0)); - // Admin pause deployment of new token + // Pause manager pause deployment of new token set_env!( current_account_id: bridge_token_factory(), - predecessor_account_id: bridge_token_factory(), + predecessor_account_id: pause_manager(), attached_deposit: BRIDGE_TOKEN_INIT_BALANCE * 2 ); - contract.set_paused(PAUSE_DEPLOY_TOKEN); + contract.pa_pause_feature("deploy_bridge_token".to_string()); set_env!( current_account_id: bridge_token_factory(), - predecessor_account_id: bridge_token_factory(), + predecessor_account_id: unrestricted_deploy_account, attached_deposit: BRIDGE_TOKEN_INIT_BALANCE * 2 ); - // Admin can still deploy new tokens after paused + // Account with role `UnrestrictedDeployBridgeToken` can still deploy new tokens after paused contract.deploy_bridge_token(ethereum_address_from_id(1)); // User alice can't deploy a new bridge token when it is paused @@ -740,16 +901,22 @@ mod tests { } #[test] - fn only_admin_can_pause() { - set_env!(predecessor_account_id: alice()); - let mut contract = BridgeTokenFactory::new(prover(), token_locker()); - - // Admin can pause + fn only_pause_manager_can_pause() { set_env!( current_account_id: bridge_token_factory(), predecessor_account_id: bridge_token_factory(), ); - contract.set_paused(0b1111); + let mut contract = BridgeTokenFactory::new(prover(), token_locker()); + assert!(contract + .acl_grant_role("PauseManager".to_owned(), pause_manager()) + .unwrap()); + + // Pause manager can pause + set_env!( + current_account_id: pause_manager(), + predecessor_account_id: pause_manager(), + ); + assert!(contract.pa_pause_feature("deposit".to_string())); // Alice can't pause set_env!( @@ -758,15 +925,21 @@ mod tests { ); panic::catch_unwind(move || { - contract.set_paused(0); + contract.pa_pause_feature("deposit".to_string()); }) .unwrap_err(); } #[test] fn deposit_paused() { - set_env!(predecessor_account_id: alice()); + set_env!( + current_account_id: bridge_token_factory(), + predecessor_account_id: bridge_token_factory(), + ); let mut contract = BridgeTokenFactory::new(prover(), token_locker()); + assert!(contract + .acl_grant_role("PauseManager".to_owned(), pause_manager()) + .unwrap()); set_env!( current_account_id: bridge_token_factory(), @@ -777,15 +950,15 @@ mod tests { contract.deploy_bridge_token(erc20_address.clone()); // Check it is possible to use deposit while the contract is NOT paused - contract.deposit(create_proof(token_locker(), erc20_address.clone())); + contract.deposit(create_proof(token_locker(), erc20_address.0.clone())); // Pause deposit set_env!( current_account_id: bridge_token_factory(), - predecessor_account_id: bridge_token_factory(), + predecessor_account_id: pause_manager(), attached_deposit: BRIDGE_TOKEN_INIT_BALANCE * 2 ); - contract.set_paused(PAUSE_DEPOSIT); + contract.pa_pause_feature("deposit".to_string()); set_env!( current_account_id: bridge_token_factory(), @@ -795,7 +968,7 @@ mod tests { // Check it is NOT possible to use deposit while the contract is paused panic::catch_unwind(move || { - contract.deposit(create_proof(token_locker(), erc20_address.clone())); + contract.deposit(create_proof(token_locker(), erc20_address.0.clone())); }) .unwrap_err(); } @@ -803,8 +976,14 @@ mod tests { /// Check after all is paused deposit is not available #[test] fn all_paused() { - set_env!(predecessor_account_id: alice()); + set_env!( + current_account_id: bridge_token_factory(), + predecessor_account_id: bridge_token_factory(), + ); let mut contract = BridgeTokenFactory::new(prover(), token_locker()); + assert!(contract + .acl_grant_role("PauseManager".to_owned(), pause_manager()) + .unwrap()); set_env!( current_account_id: bridge_token_factory(), @@ -815,15 +994,15 @@ mod tests { contract.deploy_bridge_token(erc20_address.clone()); // Check it is possible to use deposit while the contract is NOT paused - contract.deposit(create_proof(token_locker(), erc20_address.clone())); + contract.deposit(create_proof(token_locker(), erc20_address.0.clone())); // Pause everything set_env!( current_account_id: bridge_token_factory(), - predecessor_account_id: bridge_token_factory(), + predecessor_account_id: pause_manager(), attached_deposit: BRIDGE_TOKEN_INIT_BALANCE * 2 ); - contract.set_paused(PAUSE_DEPLOY_TOKEN | PAUSE_DEPOSIT); + contract.pa_pause_feature("ALL".to_string()); set_env!( current_account_id: bridge_token_factory(), @@ -833,7 +1012,7 @@ mod tests { // Check it is NOT possible to use deposit while the contract is paused panic::catch_unwind(move || { - contract.deposit(create_proof(token_locker(), erc20_address)); + contract.deposit(create_proof(token_locker(), erc20_address.0)); }) .unwrap_err(); } @@ -841,8 +1020,14 @@ mod tests { /// Check after all is paused and unpaused deposit works #[test] fn no_paused() { - set_env!(predecessor_account_id: alice()); + set_env!( + current_account_id: bridge_token_factory(), + predecessor_account_id: bridge_token_factory(), + ); let mut contract = BridgeTokenFactory::new(prover(), token_locker()); + assert!(contract + .acl_grant_role("PauseManager".to_owned(), pause_manager()) + .unwrap()); set_env!( current_account_id: bridge_token_factory(), @@ -853,17 +1038,17 @@ mod tests { contract.deploy_bridge_token(erc20_address.clone()); // Check it is possible to use deposit while the contract is NOT paused - contract.deposit(create_proof(token_locker(), erc20_address.clone())); + contract.deposit(create_proof(token_locker(), erc20_address.0.clone())); // Pause everything set_env!( current_account_id: bridge_token_factory(), - predecessor_account_id: bridge_token_factory(), + predecessor_account_id: pause_manager(), attached_deposit: BRIDGE_TOKEN_INIT_BALANCE * 2 ); - contract.set_paused(PAUSE_DEPLOY_TOKEN | PAUSE_DEPOSIT); - contract.set_paused(UNPAUSE_ALL); + contract.pa_pause_feature("ALL".to_string()); + contract.pa_unpause_feature("ALL".to_string()); set_env!( current_account_id: bridge_token_factory(), @@ -872,6 +1057,6 @@ mod tests { ); // Check the deposit works after pausing and unpausing everything - contract.deposit(create_proof(token_locker(), erc20_address)); + contract.deposit(create_proof(token_locker(), erc20_address.0)); } } diff --git a/bridge-token-factory/src/migration.rs b/bridge-token-factory/src/migration.rs new file mode 100644 index 00000000..c2ce7e85 --- /dev/null +++ b/bridge-token-factory/src/migration.rs @@ -0,0 +1,38 @@ +use crate::*; + +pub type Mask = u128; + +#[derive(BorshDeserialize, BorshSerialize)] +pub struct OldState { + pub prover_account: AccountId, + pub locker_address: EthAddress, + pub tokens: UnorderedSet, + pub used_events: UnorderedSet>, + pub owner_pk: PublicKey, + pub bridge_token_storage_deposit_required: Balance, + pub paused: Mask, +} + +#[near_bindgen] +impl BridgeTokenFactory { + #[private] + #[init(ignore_state)] + pub fn migrate() -> Self { + // retrieve the current state from the contract + let old_state: OldState = env::state_read().expect("failed"); + + // return the new state + Self { + prover_account: old_state.prover_account, + locker_address: old_state.locker_address, + tokens: old_state.tokens, + used_events: old_state.used_events, + owner_pk: old_state.owner_pk, + bridge_token_storage_deposit_required: old_state.bridge_token_storage_deposit_required, + deposit_fee: UnorderedMap::new(StorageKey::DepositFee), + withdraw_fee: UnorderedMap::new(StorageKey::WihdrawFee), + withdraw_fee_per_silo: UnorderedMap::new(StorageKey::WithdrawFeePerSilo), + deposit_fee_per_silo: UnorderedMap::new(StorageKey::DespositFeePerSilo), + } + } +} diff --git a/bridge-token-factory/src/types.rs b/bridge-token-factory/src/types.rs new file mode 100644 index 00000000..537d7a54 --- /dev/null +++ b/bridge-token-factory/src/types.rs @@ -0,0 +1,86 @@ +use std::fmt; +use std::str::FromStr; + +use near_sdk::borsh::{self, BorshDeserialize, BorshSerialize}; +use near_sdk::json_types::U128; +use near_sdk::serde::{Deserialize, Serialize}; + +#[derive(BorshDeserialize, BorshSerialize, Serialize, Debug, Clone, PartialEq)] +pub struct EthAddressHex(pub String); + +impl<'de> Deserialize<'de> for EthAddressHex { + fn deserialize(deserializer: D) -> Result>::Error> + where + D: serde::Deserializer<'de>, + { + let s = ::deserialize(deserializer)?; + EthAddressHex::from_str(&s).map_err(|e| serde::de::Error::custom(e.0)) + } +} + +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct ParseEthAddressError(String); + +impl fmt::Display for ParseEthAddressError { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "The hex eth address is invalid") + } +} + +impl FromStr for EthAddressHex { + type Err = ParseEthAddressError; + fn from_str(s: &str) -> Result { + let s = if s.starts_with("0x") { + s[2..].to_lowercase() + } else { + s.to_lowercase() + }; + + if !is_hex_string(&s) { + return Err(ParseEthAddressError("Invalid hex character".to_owned())); + } + if s.len() != 40 { + return Err(ParseEthAddressError( + "Address should be 20 bytes long".to_owned(), + )); + } + + Ok(EthAddressHex(s)) + } +} + +fn is_hex_string(hex_str: &str) -> bool { + for c in hex_str.chars() { + if !c.is_ascii_hexdigit() { + return false; + } + } + true +} + +#[derive(BorshDeserialize, BorshSerialize, Debug, Clone, Serialize, Deserialize, PartialEq)] +pub struct Fee { + pub fee_percentage: U128, + pub lower_bound: Option, + pub upper_bound: Option, +} + +pub trait SdkUnwrap { + fn sdk_unwrap(self) -> T; +} + +impl SdkUnwrap for Option { + fn sdk_unwrap(self) -> T { + self.unwrap_or_else(|| near_sdk::env::panic_str("ERR_UNWRAP")) + } +} + +impl> SdkUnwrap for Result { + fn sdk_unwrap(self) -> T { + self.unwrap_or_else(|e| near_sdk::env::panic_str(err_to_str(&e))) + } +} + +fn err_to_str>(err: &E) -> &str { + std::str::from_utf8(err.as_ref()).unwrap_or("INVALID_UTF8_ERR_STRING") +} diff --git a/bridge-token-factory/tests/fee_tests.rs b/bridge-token-factory/tests/fee_tests.rs new file mode 100644 index 00000000..0456560b --- /dev/null +++ b/bridge-token-factory/tests/fee_tests.rs @@ -0,0 +1,1630 @@ +use bridge_token_factory::{validate_eth_address, EthLockedEvent, Proof}; +use near_sdk::borsh::{self, BorshSerialize}; +use near_sdk::{Balance, ONE_NEAR, ONE_YOCTO}; +use serde_json::json; +use tokio::runtime::Runtime; +use workspaces::{network::Sandbox, Account, Contract, Worker}; + +const FACTORY: &str = "bridge"; +const LOCKER_ADDRESS: &str = "11111474e89094c44da98b954eedeac495271d0f"; +const DAI_ADDRESS: &str = "6b175474e89094c44da98b954eedeac495271d0f"; +const SENDER_ADDRESS: &str = "00005474e89094c44da98b954eedeac495271d0f"; +const ALICE: &str = "alice.test.near"; + +const FACTORY_WASM_PATH: &str = "../res/bridge_token_factory.wasm"; +const MOCK_PROVER_WASM_PATH: &str = "../res/mock_prover.wasm"; + +const DEFAULT_DEPOSIT: u128 = ONE_NEAR; + +fn create_contract() -> (Account, Contract, Account, Worker) { + let rt = Runtime::new().unwrap(); + + let worker: Worker = rt.block_on(workspaces::sandbox()).unwrap(); + let prover_wasm = std::fs::read(MOCK_PROVER_WASM_PATH).unwrap(); + let prover_contract: Contract = rt.block_on(worker.dev_deploy(&prover_wasm)).unwrap(); + let prover_id = prover_contract.id(); + + let owner = worker.root_account().unwrap(); + let factory_account = rt + .block_on( + owner + .create_subaccount(FACTORY) + .initial_balance(200 * ONE_NEAR) + .transact(), + ) + .unwrap() + .into_result() + .unwrap(); + + let factory_wasm = std::fs::read(FACTORY_WASM_PATH).unwrap(); + let factory_contract: Contract = rt + .block_on(factory_account.deploy(&factory_wasm)) + .unwrap() + .result; + + let alice = rt + .block_on( + owner + .create_subaccount("alice") + .initial_balance(200 * ONE_NEAR) + .transact(), + ) + .unwrap() + .into_result() + .unwrap(); + + let _result = rt.block_on( + factory_account + .call(factory_contract.id(), "new") + .args( + json!({"prover_account": prover_id, "locker_address": LOCKER_ADDRESS.to_string()}) + .to_string() + .into_bytes(), + ) + .transact(), + ) + .unwrap(); + + let _grant_fee_setter_call = rt + .block_on( + factory_account + .call(factory_contract.id(), "acl_grant_role") + .args( + json!({"role": "FeeSetter".to_string(), "account_id": alice.id()}) + .to_string() + .into_bytes(), + ) + .transact(), + ) + .unwrap(); + assert!( + _grant_fee_setter_call.is_success(), + "fee setter grant role failed" + ); + + (alice, factory_contract, factory_account, worker) +} + +#[derive(BorshSerialize)] +struct AugmentedProof { + proof: Proof, + skip_call: bool, +} + +impl From for AugmentedProof { + fn from(proof: Proof) -> Self { + Self { + proof, + skip_call: false, + } + } +} + +fn remove_quotes(s: &mut String) -> String { + s.pop(); + s.remove(0); + s.to_string() +} + +fn run_view_function( + rt: &Runtime, + contract_id: &String, + worker: &Worker, + function: &str, + args: serde_json::Value, +) -> String { + let mut res = std::str::from_utf8( + &rt.block_on(worker.view( + &contract_id.parse().unwrap(), + function, + args.to_string().into_bytes(), + )) + .unwrap() + .result, + ) + .unwrap() + .to_string(); + remove_quotes(&mut res) +} + +fn get_fee_amount( + amount: u128, + fee_percentage: u128, + lower_bound: u128, + upper_bound: u128, +) -> u128 { + const FEE_DECIMAL_PRECISION: u128 = 100_00_00; + let mut fee_amount = (amount * fee_percentage) / FEE_DECIMAL_PRECISION; + if fee_amount < lower_bound { + fee_amount = lower_bound; + return fee_amount; + } else if fee_amount > upper_bound { + fee_amount = upper_bound; + return fee_amount; + } else { + return fee_amount; + } +} + +#[test] +fn test_token_transfer_with_deposit_and_withdraw_fee() { + let (alice, factory, _, worker) = create_contract(); + let rt = Runtime::new().unwrap(); + const INIT_ALICE_BALANCE: u64 = 1000; + const WITHDRAW_AMOUNT: u64 = 100; + + assert!(&rt + .block_on( + alice + .call(factory.id(), "deploy_bridge_token") + .deposit(35 * ONE_NEAR) + .args( + json!({"address": DAI_ADDRESS.to_string()}) + .to_string() + .into_bytes() + ) + .max_gas() + .transact() + ) + .unwrap() + .is_success()); + + let token_account_id: String = run_view_function( + &rt, + &factory.id().to_string(), + &worker, + "get_bridge_token_account_id", + json!({"address": DAI_ADDRESS.to_string()}), + ); + + assert_eq!( + token_account_id, + format!("{}.{}", DAI_ADDRESS, factory.id()) + ); + + let alice_balance: String = run_view_function( + &rt, + &token_account_id, + &worker, + "ft_balance_of", + json!({ "account_id": ALICE }), + ); + + assert_eq!(alice_balance, "0"); + + let total_supply: String = run_view_function( + &rt, + &token_account_id, + &worker, + "ft_total_supply", + json!({}), + ); + assert_eq!(total_supply, "0"); + + let mut proof = Proof::default(); + proof.log_entry_data = EthLockedEvent { + locker_address: validate_eth_address(LOCKER_ADDRESS.to_string()), + token: DAI_ADDRESS.to_string(), + sender: SENDER_ADDRESS.to_string(), + amount: Balance::from(INIT_ALICE_BALANCE), + recipient: ALICE.parse().unwrap(), + } + .to_log_entry_data(); + + let deposit_fee_setter_call = &rt + .block_on( + alice + .call(factory.id(), "set_deposit_fee") + .args( + json!({"token": DAI_ADDRESS.to_string(), "fee_percentage": "400000", "upper_bound": "500", "lower_bound": "100"}) + .to_string() + .into_bytes() + ) + .max_gas() + .transact() + ) + .unwrap(); + + assert!( + deposit_fee_setter_call.is_success(), + "Fee setter called failed" + ); + + let withdraw_fee_setter_call = &rt + .block_on( + alice + .call(factory.id(), "set_withdraw_fee") + .args( + json!({"token": DAI_ADDRESS.to_string(), "fee_percentage": "350000", "upper_bound": "50", "lower_bound": "10"}) + .to_string() + .into_bytes(), + ) + .max_gas() + .transact(), + ) + .unwrap(); + assert!( + withdraw_fee_setter_call.is_success(), + "Withdraw Fee setter called failed" + ); + + let deposit_call = &rt + .block_on( + alice + .call(factory.id(), "deposit") + .deposit(DEFAULT_DEPOSIT) + .max_gas() + .args(proof.try_to_vec().unwrap()) + .transact(), + ) + .unwrap(); + if deposit_call.is_failure() { + println!("\n\n\n Deposit error {:?}\n\n", deposit_call.failures()); + } + let deposit_fee_amount = + get_fee_amount(u128::from(INIT_ALICE_BALANCE), 400000u128, 100u128, 500u128); + let transfer_amount = INIT_ALICE_BALANCE as u128 - deposit_fee_amount; + + let alice_balance: String = run_view_function( + &rt, + &token_account_id, + &worker, + "ft_balance_of", + json!({ "account_id": ALICE }), + ); + assert_eq!(alice_balance, format!("{}", transfer_amount)); + + let token_factory_balance_after_deposit: String = run_view_function( + &rt, + &token_account_id, + &worker, + "ft_balance_of", + json!({ "account_id": factory.id().to_string() }), + ); + assert_eq!( + token_factory_balance_after_deposit, + format!("{}", deposit_fee_amount) + ); + + assert!(&rt + .block_on( + alice + .call(&token_account_id.parse().unwrap(), "withdraw") + .max_gas() + .deposit(ONE_YOCTO) + .args( + json!({ + "amount" : format!("{}", WITHDRAW_AMOUNT), + "recipient" : SENDER_ADDRESS.to_string() + }) + .to_string() + .into_bytes(), + ) + .transact(), + ) + .unwrap() + .is_success()); + + let alice_balance: String = run_view_function( + &rt, + &token_account_id, + &worker, + "ft_balance_of", + json!({ "account_id": ALICE }), + ); + assert_eq!( + alice_balance, + format!( + "{}", + u128::from(INIT_ALICE_BALANCE) - u128::from(WITHDRAW_AMOUNT) - deposit_fee_amount + ) + ); + let withdraw_fee_amount = + get_fee_amount(u128::from(WITHDRAW_AMOUNT), 350000u128, 10u128, 50u128); + let token_factory_balance_after_withdraw: String = run_view_function( + &rt, + &token_account_id, + &worker, + "ft_balance_of", + json!({ "account_id": factory.id().to_string()}), + ); + assert_eq!( + format!("{}", deposit_fee_amount + withdraw_fee_amount), + token_factory_balance_after_withdraw + ); +} + +#[test] +fn test_token_deposit_without_fee_bound_and_fee_percentage() { + let (alice, factory, _, worker) = create_contract(); + let rt = Runtime::new().unwrap(); + const INIT_ALICE_BALANCE: u64 = 1000; + + assert!(&rt + .block_on( + alice + .call(factory.id(), "deploy_bridge_token") + .deposit(35 * ONE_NEAR) + .args( + json!({"address": DAI_ADDRESS.to_string()}) + .to_string() + .into_bytes() + ) + .max_gas() + .transact() + ) + .unwrap() + .is_success()); + + let token_account_id: String = run_view_function( + &rt, + &factory.id().to_string(), + &worker, + "get_bridge_token_account_id", + json!({"address": DAI_ADDRESS.to_string()}), + ); + + assert_eq!( + token_account_id, + format!("{}.{}", DAI_ADDRESS, factory.id()) + ); + + let alice_balance: String = run_view_function( + &rt, + &token_account_id, + &worker, + "ft_balance_of", + json!({ "account_id": ALICE }), + ); + + assert_eq!(alice_balance, "0"); + + let total_supply: String = run_view_function( + &rt, + &token_account_id, + &worker, + "ft_total_supply", + json!({}), + ); + assert_eq!(total_supply, "0"); + + let mut proof = Proof::default(); + proof.log_entry_data = EthLockedEvent { + locker_address: validate_eth_address(LOCKER_ADDRESS.to_string()), + token: DAI_ADDRESS.to_string(), + sender: SENDER_ADDRESS.to_string(), + amount: Balance::from(INIT_ALICE_BALANCE), + recipient: ALICE.parse().unwrap(), + } + .to_log_entry_data(); + + assert!(&rt + .block_on( + alice + .call(factory.id(), "deposit") + .deposit(DEFAULT_DEPOSIT) + .max_gas() + .args(proof.try_to_vec().unwrap()) + .transact() + ) + .unwrap() + .is_success()); + + let alice_balance: String = run_view_function( + &rt, + &token_account_id, + &worker, + "ft_balance_of", + json!({ "account_id": ALICE }), + ); + assert_eq!(alice_balance, format!("{}", INIT_ALICE_BALANCE)); + + let token_factory_balance_after_deposit: String = run_view_function( + &rt, + &token_account_id, + &worker, + "ft_balance_of", + json!({ "account_id": factory.id().to_string()}), + ); + assert_eq!(token_factory_balance_after_deposit, format!("{}", 0)); +} + +#[test] +fn test_token_deposit_with_fee_less_than_lower_bound() { + let (alice, factory, _, worker) = create_contract(); + let rt = Runtime::new().unwrap(); + const INIT_ALICE_BALANCE: u64 = 1000; + + assert!(&rt + .block_on( + alice + .call(factory.id(), "deploy_bridge_token") + .deposit(35 * ONE_NEAR) + .args( + json!({"address": DAI_ADDRESS.to_string()}) + .to_string() + .into_bytes() + ) + .max_gas() + .transact() + ) + .unwrap() + .is_success()); + + let token_account_id: String = run_view_function( + &rt, + &factory.id().to_string(), + &worker, + "get_bridge_token_account_id", + json!({"address": DAI_ADDRESS.to_string()}), + ); + + assert_eq!( + token_account_id, + format!("{}.{}", DAI_ADDRESS, factory.id()) + ); + + let alice_balance: String = run_view_function( + &rt, + &token_account_id, + &worker, + "ft_balance_of", + json!({ "account_id": ALICE }), + ); + + assert_eq!(alice_balance, "0"); + + let total_supply: String = run_view_function( + &rt, + &token_account_id, + &worker, + "ft_total_supply", + json!({}), + ); + assert_eq!(total_supply, "0"); + + let mut proof = Proof::default(); + proof.log_entry_data = EthLockedEvent { + locker_address: validate_eth_address(LOCKER_ADDRESS.to_string()), + token: DAI_ADDRESS.to_string(), + sender: SENDER_ADDRESS.to_string(), + amount: Balance::from(INIT_ALICE_BALANCE), + recipient: ALICE.parse().unwrap(), + } + .to_log_entry_data(); + + let deposit_fee_setter_call = &rt + .block_on( + alice + .call(factory.id(), "set_deposit_fee") + .args( + json!({"token": DAI_ADDRESS.to_string(), "fee_percentage": "50000", "upper_bound": "200", "lower_bound": "100"}) + .to_string() + .into_bytes() + ) + .max_gas() + .transact() + ) + .unwrap(); + assert!( + deposit_fee_setter_call.is_success(), + "Fee setter called failed" + ); + + let deposit_call = &rt + .block_on( + alice + .call(factory.id(), "deposit") + .deposit(DEFAULT_DEPOSIT) + .max_gas() + .args(proof.try_to_vec().unwrap()) + .transact(), + ) + .unwrap(); + if deposit_call.is_failure() { + println!("\n\n\n Deposit error {:?}\n\n", deposit_call.failures()); + } + let fee_amount = get_fee_amount(u128::from(INIT_ALICE_BALANCE), 50000u128, 100u128, 200u128); + let transfer_amount = INIT_ALICE_BALANCE as u128 - fee_amount; + + let alice_balance: String = run_view_function( + &rt, + &token_account_id, + &worker, + "ft_balance_of", + json!({ "account_id": ALICE }), + ); + assert_eq!(alice_balance, format!("{}", transfer_amount)); + + let token_factory_balance_after_deposit: String = run_view_function( + &rt, + &token_account_id, + &worker, + "ft_balance_of", + json!({ "account_id": factory.id().to_string()}), + ); + assert_eq!( + token_factory_balance_after_deposit, + format!("{}", fee_amount) + ); +} + +#[test] +fn test_token_deposit_with_fee_more_than_upper_bound() { + let (alice, factory, _, worker) = create_contract(); + let rt = Runtime::new().unwrap(); + const INIT_ALICE_BALANCE: u64 = 1000; + + assert!(&rt + .block_on( + alice + .call(factory.id(), "deploy_bridge_token") + .deposit(35 * ONE_NEAR) + .args( + json!({"address": DAI_ADDRESS.to_string()}) + .to_string() + .into_bytes() + ) + .max_gas() + .transact() + ) + .unwrap() + .is_success()); + + let token_account_id: String = run_view_function( + &rt, + &factory.id().to_string(), + &worker, + "get_bridge_token_account_id", + json!({"address": DAI_ADDRESS.to_string()}), + ); + + assert_eq!( + token_account_id, + format!("{}.{}", DAI_ADDRESS, factory.id()) + ); + + let alice_balance: String = run_view_function( + &rt, + &token_account_id, + &worker, + "ft_balance_of", + json!({ "account_id": ALICE }), + ); + + assert_eq!(alice_balance, "0"); + + let total_supply: String = run_view_function( + &rt, + &token_account_id, + &worker, + "ft_total_supply", + json!({}), + ); + assert_eq!(total_supply, "0"); + + let mut proof = Proof::default(); + proof.log_entry_data = EthLockedEvent { + locker_address: validate_eth_address(LOCKER_ADDRESS.to_string()), + token: DAI_ADDRESS.to_string(), + sender: SENDER_ADDRESS.to_string(), + amount: Balance::from(INIT_ALICE_BALANCE), + recipient: ALICE.parse().unwrap(), + } + .to_log_entry_data(); + + let deposit_fee_setter_call = &rt + .block_on( + alice + .call(factory.id(), "set_deposit_fee") + .args( + json!({"token": DAI_ADDRESS.to_string(), "fee_percentage": "400000", "upper_bound": "200", "lower_bound": "100"}) + .to_string() + .into_bytes() + ) + .max_gas() + .transact() + ) + .unwrap(); + assert!( + deposit_fee_setter_call.is_success(), + "Fee setter called failed" + ); + + let deposit_call = &rt + .block_on( + alice + .call(factory.id(), "deposit") + .deposit(DEFAULT_DEPOSIT) + .max_gas() + .args(proof.try_to_vec().unwrap()) + .transact(), + ) + .unwrap(); + if deposit_call.is_failure() { + println!("\n\n\n Deposit error {:?}\n\n", deposit_call.failures()); + } + let fee_amount = get_fee_amount(u128::from(INIT_ALICE_BALANCE), 400000u128, 100u128, 200u128); + let transfer_amount = INIT_ALICE_BALANCE as u128 - fee_amount; + + let alice_balance: String = run_view_function( + &rt, + &token_account_id, + &worker, + "ft_balance_of", + json!({ "account_id": ALICE }), + ); + assert_eq!(alice_balance, format!("{}", transfer_amount)); + + let token_factory_balance: String = run_view_function( + &rt, + &token_account_id, + &worker, + "ft_balance_of", + json!({ "account_id": factory.id().to_string()}), + ); + assert_eq!(token_factory_balance, format!("{}", fee_amount)); +} + +#[test] +fn test_token_deposit_with_fee_in_bound_range() { + let (alice, factory, _, worker) = create_contract(); + let rt = Runtime::new().unwrap(); + const INIT_ALICE_BALANCE: u64 = 1000; + + assert!(&rt + .block_on( + alice + .call(factory.id(), "deploy_bridge_token") + .deposit(35 * ONE_NEAR) + .args( + json!({"address": DAI_ADDRESS.to_string()}) + .to_string() + .into_bytes() + ) + .max_gas() + .transact() + ) + .unwrap() + .is_success()); + + let token_account_id: String = run_view_function( + &rt, + &factory.id().to_string(), + &worker, + "get_bridge_token_account_id", + json!({"address": DAI_ADDRESS.to_string()}), + ); + + assert_eq!( + token_account_id, + format!("{}.{}", DAI_ADDRESS, factory.id()) + ); + + let alice_balance: String = run_view_function( + &rt, + &token_account_id, + &worker, + "ft_balance_of", + json!({ "account_id": ALICE }), + ); + + assert_eq!(alice_balance, "0"); + + let total_supply: String = run_view_function( + &rt, + &token_account_id, + &worker, + "ft_total_supply", + json!({}), + ); + assert_eq!(total_supply, "0"); + + let mut proof = Proof::default(); + proof.log_entry_data = EthLockedEvent { + locker_address: validate_eth_address(LOCKER_ADDRESS.to_string()), + token: DAI_ADDRESS.to_string(), + sender: SENDER_ADDRESS.to_string(), + amount: Balance::from(INIT_ALICE_BALANCE), + recipient: ALICE.parse().unwrap(), + } + .to_log_entry_data(); + + let deposit_fee_setter_call = &rt + .block_on( + alice + .call(factory.id(), "set_deposit_fee") + .args( + json!({"token": DAI_ADDRESS.to_string(), "fee_percentage": "400000", "upper_bound": "500", "lower_bound": "100"}) + .to_string() + .into_bytes() + ) + .max_gas() + .transact() + ) + .unwrap(); + assert!( + deposit_fee_setter_call.is_success(), + "Fee setter called failed" + ); + + let deposit_call = &rt + .block_on( + alice + .call(factory.id(), "deposit") + .deposit(DEFAULT_DEPOSIT) + .max_gas() + .args(proof.try_to_vec().unwrap()) + .transact(), + ) + .unwrap(); + if deposit_call.is_failure() { + println!("\n\n\n Deposit error {:?}\n\n", deposit_call.failures()); + } + let fee_amount = get_fee_amount(u128::from(INIT_ALICE_BALANCE), 400000u128, 100u128, 500u128); + let transfer_amount = INIT_ALICE_BALANCE as u128 - fee_amount; + + let alice_balance: String = run_view_function( + &rt, + &token_account_id, + &worker, + "ft_balance_of", + json!({ "account_id": ALICE }), + ); + assert_eq!(alice_balance, format!("{}", transfer_amount)); + + let token_factory_balance: String = run_view_function( + &rt, + &token_account_id, + &worker, + "ft_balance_of", + json!({ "account_id": factory.id().to_string() }), + ); + assert_eq!(token_factory_balance, format!("{}", fee_amount)); +} + +#[test] +fn test_token_withdraw_without_fee_bound_and_fee_percentage() { + let (alice, factory, _, worker) = create_contract(); + let rt = Runtime::new().unwrap(); + const INIT_ALICE_BALANCE: u64 = 1000; + const WITHDRAW_AMOUNT: u64 = 100; + + assert!(&rt + .block_on( + alice + .call(factory.id(), "deploy_bridge_token") + .deposit(35 * ONE_NEAR) + .args( + json!({"address": DAI_ADDRESS.to_string()}) + .to_string() + .into_bytes() + ) + .max_gas() + .transact() + ) + .unwrap() + .is_success()); + + let token_account_id: String = run_view_function( + &rt, + &factory.id().to_string(), + &worker, + "get_bridge_token_account_id", + json!({"address": DAI_ADDRESS.to_string()}), + ); + + assert_eq!( + token_account_id, + format!("{}.{}", DAI_ADDRESS, factory.id()) + ); + + let alice_balance: String = run_view_function( + &rt, + &token_account_id, + &worker, + "ft_balance_of", + json!({ "account_id": ALICE }), + ); + + assert_eq!(alice_balance, "0"); + + let total_supply: String = run_view_function( + &rt, + &token_account_id, + &worker, + "ft_total_supply", + json!({}), + ); + assert_eq!(total_supply, "0"); + + let mut proof = Proof::default(); + proof.log_entry_data = EthLockedEvent { + locker_address: validate_eth_address(LOCKER_ADDRESS.to_string()), + token: DAI_ADDRESS.to_string(), + sender: SENDER_ADDRESS.to_string(), + amount: Balance::from(INIT_ALICE_BALANCE), + recipient: ALICE.parse().unwrap(), + } + .to_log_entry_data(); + + let deposit_call = &rt + .block_on( + alice + .call(factory.id(), "deposit") + .deposit(DEFAULT_DEPOSIT) + .max_gas() + .args(proof.try_to_vec().unwrap()) + .transact(), + ) + .unwrap(); + if deposit_call.is_failure() { + println!("\n\n\n Deposit error {:?}\n\n", deposit_call.failures()); + } + + let alice_balance: String = run_view_function( + &rt, + &token_account_id, + &worker, + "ft_balance_of", + json!({ "account_id": ALICE }), + ); + assert_eq!(alice_balance, format!("{}", INIT_ALICE_BALANCE)); + + assert!(&rt + .block_on( + alice + .call(&token_account_id.parse().unwrap(), "withdraw") + .max_gas() + .deposit(ONE_YOCTO) + .args( + json!({ + "amount" : format!("{}", WITHDRAW_AMOUNT), + "recipient" : SENDER_ADDRESS.to_string() + }) + .to_string() + .into_bytes(), + ) + .transact(), + ) + .unwrap() + .is_success()); + + let alice_balance: String = run_view_function( + &rt, + &token_account_id, + &worker, + "ft_balance_of", + json!({ "account_id": ALICE }), + ); + assert_eq!( + alice_balance, + format!( + "{}", + u128::from(INIT_ALICE_BALANCE) - u128::from(WITHDRAW_AMOUNT) + ) + ); + let token_factory_balance_after_withdraw: String = run_view_function( + &rt, + &token_account_id, + &worker, + "ft_balance_of", + json!({ "account_id": factory.id().to_string() }), + ); + assert_eq!(format!("{}", 0), token_factory_balance_after_withdraw); +} + +#[test] +fn test_token_withdraw_with_fee_less_than_lower_bound() { + let (alice, factory, _, worker) = create_contract(); + let rt = Runtime::new().unwrap(); + const INIT_ALICE_BALANCE: u64 = 1000; + const WITHDRAW_AMOUNT: u64 = 100; + + assert!(&rt + .block_on( + alice + .call(factory.id(), "deploy_bridge_token") + .deposit(35 * ONE_NEAR) + .args( + json!({"address": DAI_ADDRESS.to_string()}) + .to_string() + .into_bytes() + ) + .max_gas() + .transact() + ) + .unwrap() + .is_success()); + + let token_account_id: String = run_view_function( + &rt, + &factory.id().to_string(), + &worker, + "get_bridge_token_account_id", + json!({"address": DAI_ADDRESS.to_string()}), + ); + + assert_eq!( + token_account_id, + format!("{}.{}", DAI_ADDRESS, factory.id()) + ); + + let alice_balance: String = run_view_function( + &rt, + &token_account_id, + &worker, + "ft_balance_of", + json!({ "account_id": ALICE }), + ); + + assert_eq!(alice_balance, "0"); + + let total_supply: String = run_view_function( + &rt, + &token_account_id, + &worker, + "ft_total_supply", + json!({}), + ); + assert_eq!(total_supply, "0"); + + let mut proof = Proof::default(); + proof.log_entry_data = EthLockedEvent { + locker_address: validate_eth_address(LOCKER_ADDRESS.to_string()), + token: DAI_ADDRESS.to_string(), + sender: SENDER_ADDRESS.to_string(), + amount: Balance::from(INIT_ALICE_BALANCE), + recipient: ALICE.parse().unwrap(), + } + .to_log_entry_data(); + + let withdraw_fee_setter_call = &rt + .block_on( + alice + .call(factory.id(), "set_withdraw_fee") + .args( + json!({"token": DAI_ADDRESS.to_string(), "fee_percentage": "50000", "upper_bound": "20", "lower_bound": "10"}) + .to_string() + .into_bytes(), + ) + .max_gas() + .transact(), + ) + .unwrap(); + assert!( + withdraw_fee_setter_call.is_success(), + "Withdraw Fee setter called failed" + ); + + let deposit_call = &rt + .block_on( + alice + .call(factory.id(), "deposit") + .deposit(DEFAULT_DEPOSIT) + .max_gas() + .args(proof.try_to_vec().unwrap()) + .transact(), + ) + .unwrap(); + if deposit_call.is_failure() { + println!("\n\n\n Deposit error {:?}\n\n", deposit_call.failures()); + } + + let alice_balance: String = run_view_function( + &rt, + &token_account_id, + &worker, + "ft_balance_of", + json!({ "account_id": ALICE }), + ); + assert_eq!(alice_balance, format!("{}", INIT_ALICE_BALANCE)); + + assert!(&rt + .block_on( + alice + .call(&token_account_id.parse().unwrap(), "withdraw") + .max_gas() + .deposit(ONE_YOCTO) + .args( + json!({ + "amount" : format!("{}", WITHDRAW_AMOUNT), + "recipient" : SENDER_ADDRESS.to_string() + }) + .to_string() + .into_bytes(), + ) + .transact(), + ) + .unwrap() + .is_success()); + + let alice_balance: String = run_view_function( + &rt, + &token_account_id, + &worker, + "ft_balance_of", + json!({ "account_id": ALICE }), + ); + assert_eq!( + alice_balance, + format!( + "{}", + u128::from(INIT_ALICE_BALANCE) - u128::from(WITHDRAW_AMOUNT) + ) + ); + let fee_amount = get_fee_amount(u128::from(WITHDRAW_AMOUNT), 50000u128, 10u128, 50u128); + let token_factory_balance_after_withdraw: String = run_view_function( + &rt, + &token_account_id, + &worker, + "ft_balance_of", + json!({ "account_id": factory.id().to_string() }), + ); + assert_eq!( + format!("{}", fee_amount), + token_factory_balance_after_withdraw + ); +} +#[test] +fn test_token_withdraw_with_fee_more_than_upper_bound() { + let (alice, factory, _, worker) = create_contract(); + let rt = Runtime::new().unwrap(); + const INIT_ALICE_BALANCE: u64 = 1000; + const WITHDRAW_AMOUNT: u64 = 100; + + assert!(&rt + .block_on( + alice + .call(factory.id(), "deploy_bridge_token") + .deposit(35 * ONE_NEAR) + .args( + json!({"address": DAI_ADDRESS.to_string()}) + .to_string() + .into_bytes() + ) + .max_gas() + .transact() + ) + .unwrap() + .is_success()); + + let token_account_id: String = run_view_function( + &rt, + &factory.id().to_string(), + &worker, + "get_bridge_token_account_id", + json!({"address": DAI_ADDRESS.to_string()}), + ); + + assert_eq!( + token_account_id, + format!("{}.{}", DAI_ADDRESS, factory.id()) + ); + + let alice_balance: String = run_view_function( + &rt, + &token_account_id, + &worker, + "ft_balance_of", + json!({ "account_id": ALICE }), + ); + + assert_eq!(alice_balance, "0"); + + let total_supply: String = run_view_function( + &rt, + &token_account_id, + &worker, + "ft_total_supply", + json!({}), + ); + assert_eq!(total_supply, "0"); + + let mut proof = Proof::default(); + proof.log_entry_data = EthLockedEvent { + locker_address: validate_eth_address(LOCKER_ADDRESS.to_string()), + token: DAI_ADDRESS.to_string(), + sender: SENDER_ADDRESS.to_string(), + amount: Balance::from(INIT_ALICE_BALANCE), + recipient: ALICE.parse().unwrap(), + } + .to_log_entry_data(); + + let withdraw_fee_setter_call = &rt + .block_on( + alice + .call(factory.id(), "set_withdraw_fee") + .args( + json!({"token": DAI_ADDRESS.to_string(), "fee_percentage": "350000", "upper_bound": "30", "lower_bound": "10"}) + .to_string() + .into_bytes(), + ) + .max_gas() + .transact(), + ) + .unwrap(); + assert!( + withdraw_fee_setter_call.is_success(), + "Withdraw Fee setter called failed" + ); + + let deposit_call = &rt + .block_on( + alice + .call(factory.id(), "deposit") + .deposit(DEFAULT_DEPOSIT) + .max_gas() + .args(proof.try_to_vec().unwrap()) + .transact(), + ) + .unwrap(); + if deposit_call.is_failure() { + println!("\n\n\n Deposit error {:?}\n\n", deposit_call.failures()); + } + + let alice_balance: String = run_view_function( + &rt, + &token_account_id, + &worker, + "ft_balance_of", + json!({ "account_id": ALICE }), + ); + assert_eq!(alice_balance, format!("{}", INIT_ALICE_BALANCE)); + + assert!(&rt + .block_on( + alice + .call(&token_account_id.parse().unwrap(), "withdraw") + .max_gas() + .deposit(ONE_YOCTO) + .args( + json!({ + "amount" : format!("{}", WITHDRAW_AMOUNT), + "recipient" : SENDER_ADDRESS.to_string() + }) + .to_string() + .into_bytes(), + ) + .transact(), + ) + .unwrap() + .is_success()); + + let alice_balance: String = run_view_function( + &rt, + &token_account_id, + &worker, + "ft_balance_of", + json!({ "account_id": ALICE }), + ); + assert_eq!( + alice_balance, + format!( + "{}", + u128::from(INIT_ALICE_BALANCE) - u128::from(WITHDRAW_AMOUNT) + ) + ); + let fee_amount = get_fee_amount(u128::from(WITHDRAW_AMOUNT), 350000u128, 10u128, 30u128); + let token_factory_balance_after_withdraw: String = run_view_function( + &rt, + &token_account_id, + &worker, + "ft_balance_of", + json!({ "account_id": factory.id().to_string() }), + ); + assert_eq!( + format!("{}", fee_amount), + token_factory_balance_after_withdraw + ); +} + +#[test] +fn test_token_withdraw_with_fee_in_bound_range() { + let (alice, factory, _, worker) = create_contract(); + let rt = Runtime::new().unwrap(); + const INIT_ALICE_BALANCE: u64 = 1000; + const WITHDRAW_AMOUNT: u64 = 100; + + assert!(&rt + .block_on( + alice + .call(factory.id(), "deploy_bridge_token") + .deposit(35 * ONE_NEAR) + .args( + json!({"address": DAI_ADDRESS.to_string()}) + .to_string() + .into_bytes() + ) + .max_gas() + .transact() + ) + .unwrap() + .is_success()); + + let token_account_id: String = run_view_function( + &rt, + &factory.id().to_string(), + &worker, + "get_bridge_token_account_id", + json!({"address": DAI_ADDRESS.to_string()}), + ); + + assert_eq!( + token_account_id, + format!("{}.{}", DAI_ADDRESS, factory.id()) + ); + + let alice_balance: String = run_view_function( + &rt, + &token_account_id, + &worker, + "ft_balance_of", + json!({ "account_id": ALICE }), + ); + + assert_eq!(alice_balance, "0"); + + let total_supply: String = run_view_function( + &rt, + &token_account_id, + &worker, + "ft_total_supply", + json!({}), + ); + assert_eq!(total_supply, "0"); + + let mut proof = Proof::default(); + proof.log_entry_data = EthLockedEvent { + locker_address: validate_eth_address(LOCKER_ADDRESS.to_string()), + token: DAI_ADDRESS.to_string(), + sender: SENDER_ADDRESS.to_string(), + amount: Balance::from(INIT_ALICE_BALANCE), + recipient: ALICE.parse().unwrap(), + } + .to_log_entry_data(); + + let withdraw_fee_setter_call = &rt + .block_on( + alice + .call(factory.id(), "set_withdraw_fee") + .args( + json!({"token": DAI_ADDRESS.to_string(), "fee_percentage": "400000", "upper_bound": "50", "lower_bound": "10"}) + .to_string() + .into_bytes(), + ) + .max_gas() + .transact(), + ) + .unwrap(); + assert!( + withdraw_fee_setter_call.is_success(), + "Withdraw Fee setter called failed" + ); + + let deposit_call = &rt + .block_on( + alice + .call(factory.id(), "deposit") + .deposit(DEFAULT_DEPOSIT) + .max_gas() + .args(proof.try_to_vec().unwrap()) + .transact(), + ) + .unwrap(); + if deposit_call.is_failure() { + println!("\n\n\n Deposit error {:?}\n\n", deposit_call.failures()); + } + + let alice_balance: String = run_view_function( + &rt, + &token_account_id, + &worker, + "ft_balance_of", + json!({ "account_id": ALICE }), + ); + assert_eq!(alice_balance, format!("{}", INIT_ALICE_BALANCE)); + + assert!(&rt + .block_on( + alice + .call(&token_account_id.parse().unwrap(), "withdraw") + .max_gas() + .deposit(ONE_YOCTO) + .args( + json!({ + "amount" : format!("{}", WITHDRAW_AMOUNT), + "recipient" : SENDER_ADDRESS.to_string() + }) + .to_string() + .into_bytes(), + ) + .transact(), + ) + .unwrap() + .is_success()); + + let alice_balance: String = run_view_function( + &rt, + &token_account_id, + &worker, + "ft_balance_of", + json!({ "account_id": ALICE }), + ); + assert_eq!( + alice_balance, + format!( + "{}", + u128::from(INIT_ALICE_BALANCE) - u128::from(WITHDRAW_AMOUNT) + ) + ); + let fee_amount = get_fee_amount(u128::from(WITHDRAW_AMOUNT), 400000u128, 10u128, 50u128); + let token_factory_balance_after_withdraw: String = run_view_function( + &rt, + &token_account_id, + &worker, + "ft_balance_of", + json!({ "account_id": factory.id().to_string() }), + ); + assert_eq!( + format!("{}", fee_amount), + token_factory_balance_after_withdraw + ); +} + +#[test] +fn test_fee_deposit_claim() { + let (alice, factory, factory_account, worker) = create_contract(); + let rt = Runtime::new().unwrap(); + const INIT_ALICE_BALANCE: u64 = 1000; + const WITHDRAW_AMOUNT: u64 = 100; + + assert!(&rt + .block_on( + alice + .call(factory.id(), "deploy_bridge_token") + .deposit(35 * ONE_NEAR) + .args( + json!({"address": DAI_ADDRESS.to_string()}) + .to_string() + .into_bytes() + ) + .max_gas() + .transact() + ) + .unwrap() + .is_success()); + + let token_account_id: String = run_view_function( + &rt, + &factory.id().to_string(), + &worker, + "get_bridge_token_account_id", + json!({"address": DAI_ADDRESS.to_string()}), + ); + + assert_eq!( + token_account_id, + format!("{}.{}", DAI_ADDRESS, factory.id()) + ); + + let alice_balance: String = run_view_function( + &rt, + &token_account_id, + &worker, + "ft_balance_of", + json!({ "account_id": ALICE }), + ); + + assert_eq!(alice_balance, "0"); + + let total_supply: String = run_view_function( + &rt, + &token_account_id, + &worker, + "ft_total_supply", + json!({}), + ); + assert_eq!(total_supply, "0"); + + let _grant_fee_claimer_role_call = rt + .block_on( + factory_account + .call(factory.id(), "acl_grant_role") + .args( + json!({"role": "FeeClaimer".to_string(), "account_id": alice.id()}) + .to_string() + .into_bytes(), + ) + .transact(), + ) + .unwrap(); + assert!( + _grant_fee_claimer_role_call.is_success(), + "fee setter grant role failed" + ); + + let mut proof = Proof::default(); + proof.log_entry_data = EthLockedEvent { + locker_address: validate_eth_address(LOCKER_ADDRESS.to_string()), + token: DAI_ADDRESS.to_string(), + sender: SENDER_ADDRESS.to_string(), + amount: Balance::from(INIT_ALICE_BALANCE), + recipient: ALICE.parse().unwrap(), + } + .to_log_entry_data(); + + let deposit_fee_setter_call = &rt + .block_on( + alice + .call(factory.id(), "set_deposit_fee") + .args( + json!({"token": DAI_ADDRESS.to_string(), "fee_percentage": "400000", "upper_bound": "500", "lower_bound": "100"}) + .to_string() + .into_bytes() + ) + .max_gas() + .transact() + ) + .unwrap(); + assert!( + deposit_fee_setter_call.is_success(), + "Fee setter called failed" + ); + + let withdraw_fee_setter_call = &rt + .block_on( + alice + .call(factory.id(), "set_withdraw_fee") + .args( + json!({"token": DAI_ADDRESS.to_string(), "fee_percentage": "350000", "upper_bound": "50", "lower_bound": "10"}) + .to_string() + .into_bytes(), + ) + .max_gas() + .transact(), + ) + .unwrap(); + assert!( + withdraw_fee_setter_call.is_success(), + "Withdraw Fee setter called failed" + ); + + let deposit_call = &rt + .block_on( + alice + .call(factory.id(), "deposit") + .deposit(DEFAULT_DEPOSIT) + .max_gas() + .args(proof.try_to_vec().unwrap()) + .transact(), + ) + .unwrap(); + if deposit_call.is_failure() { + println!("\n\n\n Deposit error {:?}\n\n", deposit_call.failures()); + } + let deposit_fee_amount = + get_fee_amount(u128::from(INIT_ALICE_BALANCE), 400000u128, 100u128, 500u128); + let transfer_amount = INIT_ALICE_BALANCE as u128 - deposit_fee_amount; + + let alice_balance: String = run_view_function( + &rt, + &token_account_id, + &worker, + "ft_balance_of", + json!({ "account_id": ALICE }), + ); + assert_eq!(alice_balance, format!("{}", transfer_amount)); + + let token_factory_balance_after_deposit: String = run_view_function( + &rt, + &token_account_id, + &worker, + "ft_balance_of", + json!({ "account_id": factory.id().to_string() }), + ); + assert_eq!( + token_factory_balance_after_deposit, + format!("{}", deposit_fee_amount) + ); + + assert!(&rt + .block_on( + alice + .call(&token_account_id.parse().unwrap(), "withdraw") + .max_gas() + .deposit(ONE_YOCTO) + .args( + json!({ + "amount" : format!("{}", WITHDRAW_AMOUNT), + "recipient" : SENDER_ADDRESS.to_string() + }) + .to_string() + .into_bytes(), + ) + .transact(), + ) + .unwrap() + .is_success()); + + let alice_balance: String = run_view_function( + &rt, + &token_account_id, + &worker, + "ft_balance_of", + json!({ "account_id": ALICE }), + ); + assert_eq!( + alice_balance, + format!( + "{}", + u128::from(INIT_ALICE_BALANCE) - u128::from(WITHDRAW_AMOUNT) - deposit_fee_amount + ) + ); + let withdraw_fee_amount = + get_fee_amount(u128::from(WITHDRAW_AMOUNT), 350000u128, 10u128, 50u128); + let token_factory_balance_after_withdraw: String = run_view_function( + &rt, + &token_account_id, + &worker, + "ft_balance_of", + json!({ "account_id": factory.id().to_string()}), + ); + assert_eq!( + format!("{}", deposit_fee_amount + withdraw_fee_amount), + token_factory_balance_after_withdraw + ); + + let alice_balance_before_claim: String = run_view_function( + &rt, + &token_account_id, + &worker, + "ft_balance_of", + json!({ "account_id": ALICE }), + ); + + let claim_fee_call = &rt + .block_on( + alice + .call(factory.id(), "claim_fee") + .deposit(ONE_YOCTO) + .args( + json!({"token": token_account_id, "amount": 50}) + .to_string() + .into_bytes(), + ) + .max_gas() + .transact(), + ) + .unwrap(); + assert!(claim_fee_call.is_success(), "Claim Fee call failed"); + + let token_factory_balance_after_claim: String = run_view_function( + &rt, + &token_account_id, + &worker, + "ft_balance_of", + json!({ "account_id": factory.id().to_string()}), + ); + + let alice_balance_after_claim: String = run_view_function( + &rt, + &token_account_id, + &worker, + "ft_balance_of", + json!({ "account_id": ALICE }), + ); + let alice_bal_before_claim: u128 = alice_balance_before_claim.parse().unwrap(); + let token_factory_balance_before_claim: u128 = + token_factory_balance_after_withdraw.parse().unwrap(); + assert_eq!( + format!("{}", token_factory_balance_before_claim - 50), + token_factory_balance_after_claim, + "Token Factory balance didn't matched before and after fee is claimed" + ); + assert_eq!( + format!("{}", alice_bal_before_claim + 50), + alice_balance_after_claim, + "Before-After claim balance of alice not matched" + ); +} diff --git a/bridge-token-factory/tests/token_transfer.rs b/bridge-token-factory/tests/token_transfer.rs index 3ae2d6a2..e7fc82e7 100644 --- a/bridge-token-factory/tests/token_transfer.rs +++ b/bridge-token-factory/tests/token_transfer.rs @@ -2,7 +2,6 @@ use bridge_token_factory::{validate_eth_address, EthLockedEvent, Proof}; use near_sdk::borsh::{self, BorshSerialize}; use near_sdk::{AccountId, Balance, ONE_NEAR, ONE_YOCTO}; use serde_json::json; -use tokio::runtime::Runtime; use workspaces::result::ExecutionFinalResult; use workspaces::{network::Sandbox, Account, Contract, Worker}; @@ -13,58 +12,50 @@ const SENDER_ADDRESS: &str = "00005474e89094c44da98b954eedeac495271d0f"; const ALICE: &str = "alice.test.near"; const FACTORY_WASM_PATH: &str = "../res/bridge_token_factory.wasm"; +const FACTORY_WASM_PATH_V_0_1_6: &str = "res/bridge_token_factory_v0.1.6.wasm"; +const BRIDGE_TOKEN_WASM_PATH: &str = "../res/bridge_token.wasm"; const MOCK_PROVER_WASM_PATH: &str = "../res/mock_prover.wasm"; const DEFAULT_DEPOSIT: u128 = ONE_NEAR; -fn create_contract() -> (Account, Contract, Worker) { - let rt = Runtime::new().unwrap(); - - let worker: Worker = rt.block_on(workspaces::sandbox()).unwrap(); +async fn create_contract(factory_wasm_path: &str) -> (Account, Contract, Worker) { + let worker: Worker = workspaces::sandbox().await.unwrap(); let prover_wasm = std::fs::read(MOCK_PROVER_WASM_PATH).unwrap(); - let prover_contract: Contract = rt.block_on(worker.dev_deploy(&prover_wasm)).unwrap(); + let prover_contract: Contract = worker.dev_deploy(&prover_wasm).await.unwrap(); let prover_id = prover_contract.id(); let owner = worker.root_account().unwrap(); - let factory_account = rt - .block_on( - owner - .create_subaccount(FACTORY) - .initial_balance(200 * ONE_NEAR) - .transact(), - ) + let factory_account = owner + .create_subaccount(FACTORY) + .initial_balance(200 * ONE_NEAR) + .transact() + .await .unwrap() .into_result() .unwrap(); - let factory_wasm = std::fs::read(FACTORY_WASM_PATH).unwrap(); - let factory_contract: Contract = rt - .block_on(factory_account.deploy(&factory_wasm)) - .unwrap() - .result; + let factory_wasm = std::fs::read(factory_wasm_path).unwrap(); + let factory_contract: Contract = factory_account.deploy(&factory_wasm).await.unwrap().result; - let alice = rt - .block_on( - owner - .create_subaccount("alice") - .initial_balance(200 * ONE_NEAR) - .transact(), - ) + let alice = owner + .create_subaccount("alice") + .initial_balance(200 * ONE_NEAR) + .transact() + .await .unwrap() .into_result() .unwrap(); - let _result = rt.block_on( - alice - .call(factory_contract.id(), "new") - .args( - json!({"prover_account": prover_id, "locker_address": LOCKER_ADDRESS.to_string()}) - .to_string() - .into_bytes(), - ) - .transact(), - ) - .unwrap(); + let _result = factory_account + .call(factory_contract.id(), "new") + .args( + json!({"prover_account": prover_id, "locker_address": LOCKER_ADDRESS.to_string()}) + .to_string() + .into_bytes(), + ) + .transact() + .await + .unwrap(); (alice, factory_contract, worker) } @@ -104,57 +95,55 @@ fn remove_quotes(s: &mut String) -> String { s.to_string() } -fn run_view_function( - rt: &Runtime, +async fn run_view_function( contract_id: &String, worker: &Worker, function: &str, args: serde_json::Value, ) -> String { let mut res = std::str::from_utf8( - &rt.block_on(worker.view( - &contract_id.parse().unwrap(), - function, - args.to_string().into_bytes(), - )) - .unwrap() - .result, + &worker + .view( + &contract_id.parse().unwrap(), + function, + args.to_string().into_bytes(), + ) + .await + .unwrap() + .result, ) .unwrap() .to_string(); remove_quotes(&mut res) } -#[test] -fn test_eth_token_transfer() { - let (alice, factory, worker) = create_contract(); - let rt = Runtime::new().unwrap(); +#[tokio::test] +async fn test_eth_token_transfer() { + let (alice, factory, worker) = create_contract(FACTORY_WASM_PATH).await; const INIT_ALICE_BALANCE: u64 = 1000; const WITHDRAW_AMOUNT: u64 = 100; - assert!(&rt - .block_on( - alice - .call(factory.id(), "deploy_bridge_token") - .deposit(35 * ONE_NEAR) - .args( - json!({"address": DAI_ADDRESS.to_string()}) - .to_string() - .into_bytes() - ) - .max_gas() - .transact() + assert!(alice + .call(factory.id(), "deploy_bridge_token") + .deposit(35 * ONE_NEAR) + .args( + json!({"address": DAI_ADDRESS.to_string()}) + .to_string() + .into_bytes() ) + .max_gas() + .transact() + .await .unwrap() .is_success()); let token_account_id: String = run_view_function( - &rt, &factory.id().to_string(), &worker, "get_bridge_token_account_id", json!({"address": DAI_ADDRESS.to_string()}), - ); + ) + .await; assert_eq!( token_account_id, @@ -162,22 +151,17 @@ fn test_eth_token_transfer() { ); let alice_balance: String = run_view_function( - &rt, &token_account_id, &worker, "ft_balance_of", json!({ "account_id": ALICE }), - ); + ) + .await; assert_eq!(alice_balance, "0"); - let total_supply: String = run_view_function( - &rt, - &token_account_id, - &worker, - "ft_total_supply", - json!({}), - ); + let total_supply: String = + run_view_function(&token_account_id, &worker, "ft_total_supply", json!({})).await; assert_eq!(total_supply, "0"); let mut proof = Proof::default(); @@ -190,107 +174,91 @@ fn test_eth_token_transfer() { } .to_log_entry_data(); - assert!(&rt - .block_on( - alice - .call(factory.id(), "deposit") - .deposit(DEFAULT_DEPOSIT) - .max_gas() - .args(proof.try_to_vec().unwrap()) - .transact() - ) + assert!(alice + .call(factory.id(), "deposit") + .deposit(DEFAULT_DEPOSIT) + .max_gas() + .args(proof.try_to_vec().unwrap()) + .transact() + .await .unwrap() .is_success()); let alice_balance: String = run_view_function( - &rt, &token_account_id, &worker, "ft_balance_of", json!({ "account_id": ALICE }), - ); + ) + .await; assert_eq!(alice_balance, format!("{}", INIT_ALICE_BALANCE)); - let total_supply: String = run_view_function( - &rt, - &token_account_id, - &worker, - "ft_total_supply", - json!({}), - ); + let total_supply: String = + run_view_function(&token_account_id, &worker, "ft_total_supply", json!({})).await; assert_eq!(total_supply, format!("{}", INIT_ALICE_BALANCE)); - assert!(&rt - .block_on( - alice - .call(&token_account_id.parse().unwrap(), "withdraw") - .max_gas() - .deposit(ONE_YOCTO) - .args( - json!({ - "amount" : format!("{}", WITHDRAW_AMOUNT), - "recipient" : SENDER_ADDRESS.to_string() - }) - .to_string() - .into_bytes(), - ) - .transact(), + assert!(alice + .call(&token_account_id.parse().unwrap(), "withdraw") + .max_gas() + .deposit(ONE_YOCTO) + .args( + json!({ + "amount" : format!("{}", WITHDRAW_AMOUNT), + "recipient" : SENDER_ADDRESS.to_string() + }) + .to_string() + .into_bytes(), ) + .transact() + .await .unwrap() .is_success()); let alice_balance: String = run_view_function( - &rt, &token_account_id, &worker, "ft_balance_of", json!({ "account_id": ALICE }), - ); + ) + .await; assert_eq!( alice_balance, format!("{}", INIT_ALICE_BALANCE - WITHDRAW_AMOUNT) ); - let total_supply: String = run_view_function( - &rt, - &token_account_id, - &worker, - "ft_total_supply", - json!({}), - ); + let total_supply: String = + run_view_function(&token_account_id, &worker, "ft_total_supply", json!({})).await; assert_eq!( total_supply, format!("{}", INIT_ALICE_BALANCE - WITHDRAW_AMOUNT) ); } -#[test] -fn test_with_invalid_proof() { - let (user, factory, worker) = create_contract(); - let rt = Runtime::new().unwrap(); - - assert!(&rt - .block_on( - user.call(factory.id(), "deploy_bridge_token") - .deposit(35 * ONE_NEAR) - .args( - json!({"address": DAI_ADDRESS.to_string()}) - .to_string() - .into_bytes() - ) - .max_gas() - .transact() +#[tokio::test] +async fn test_with_invalid_proof() { + let (user, factory, worker) = create_contract(FACTORY_WASM_PATH).await; + + assert!(user + .call(factory.id(), "deploy_bridge_token") + .deposit(35 * ONE_NEAR) + .args( + json!({"address": DAI_ADDRESS.to_string()}) + .to_string() + .into_bytes() ) + .max_gas() + .transact() + .await .unwrap() .is_success()); let token_account_id: String = run_view_function( - &rt, &factory.id().to_string(), &worker, "get_bridge_token_account_id", json!({"address": DAI_ADDRESS.to_string()}), - ); + ) + .await; assert_eq!( token_account_id, @@ -313,12 +281,12 @@ fn test_with_invalid_proof() { proof.proof = vec![vec![]]; assert_error( - &rt.block_on( - user.call(factory.id(), "deposit") - .max_gas() - .args(proof.try_to_vec().unwrap()) - .transact(), - ), + &user + .call(factory.id(), "deposit") + .max_gas() + .args(proof.try_to_vec().unwrap()) + .transact() + .await, "Failed to verify the proof", ); @@ -327,47 +295,44 @@ fn test_with_invalid_proof() { // This deposit event must succeed. Notice that previously a similar deposit // was made, but it failed because it had an invalid proof, so this one should succeed. - assert!(&rt - .block_on( - user.call(factory.id(), "deposit") - .max_gas() - .deposit(DEFAULT_DEPOSIT) - .args(proof.try_to_vec().unwrap()) - .transact() - ) + assert!(user + .call(factory.id(), "deposit") + .max_gas() + .deposit(DEFAULT_DEPOSIT) + .args(proof.try_to_vec().unwrap()) + .transact() + .await .unwrap() .is_success()); // This deposit event must fail since same deposit event can't be reused. // Previous call to deposit with the same event was successful. assert_error( - &rt.block_on( - user.call(factory.id(), "deposit") - .max_gas() - .args(proof.try_to_vec().unwrap()) - .transact(), - ), + &user + .call(factory.id(), "deposit") + .max_gas() + .args(proof.try_to_vec().unwrap()) + .transact() + .await, "Event cannot be reused for depositing.", ); } -#[test] -fn test_bridge_token_failures() { - let (user, factory, _worker) = create_contract(); - let rt = Runtime::new().unwrap(); - - assert!(&rt - .block_on( - user.call(factory.id(), "deploy_bridge_token") - .deposit(35 * ONE_NEAR) - .args( - json!({"address": DAI_ADDRESS.to_string()}) - .to_string() - .into_bytes() - ) - .max_gas() - .transact() +#[tokio::test] +async fn test_bridge_token_failures() { + let (user, factory, _worker) = create_contract(FACTORY_WASM_PATH).await; + + assert!(user + .call(factory.id(), "deploy_bridge_token") + .deposit(35 * ONE_NEAR) + .args( + json!({"address": DAI_ADDRESS.to_string()}) + .to_string() + .into_bytes() ) + .max_gas() + .transact() + .await .unwrap() .is_success()); @@ -375,20 +340,20 @@ fn test_bridge_token_failures() { // Fail to withdraw because the account is not registered (and have no coins) assert_error( - &rt.block_on( - user.call(&token_account_id.parse().unwrap(), "withdraw") - .max_gas() - .deposit(ONE_YOCTO) - .args( - json!({ - "amount" : "100", - "recipient" : SENDER_ADDRESS.to_string() - }) - .to_string() - .into_bytes(), - ) - .transact(), - ), + &user + .call(&token_account_id.parse().unwrap(), "withdraw") + .max_gas() + .deposit(ONE_YOCTO) + .args( + json!({ + "amount" : "100", + "recipient" : SENDER_ADDRESS.to_string() + }) + .to_string() + .into_bytes(), + ) + .transact() + .await, " is not registered", ); @@ -396,127 +361,317 @@ fn test_bridge_token_failures() { let account_id: Option = None; let registration_only: Option = None; - assert!(&rt - .block_on( - user.call(&token_account_id.parse().unwrap(), "storage_deposit") - .max_gas() - .args( - json!({ - "account_id": account_id, - "registration_only": registration_only - }) - .to_string() - .into_bytes() - ) - .deposit(DEFAULT_DEPOSIT) - .transact() + assert!(user + .call(&token_account_id.parse().unwrap(), "storage_deposit") + .max_gas() + .args( + json!({ + "account_id": account_id, + "registration_only": registration_only + }) + .to_string() + .into_bytes() ) + .deposit(DEFAULT_DEPOSIT) + .transact() + .await .unwrap() .is_success()); // Fail to withdraw because the account has no enough balance assert_error( - &rt.block_on( - user.call(&token_account_id.parse().unwrap(), "withdraw") - .max_gas() - .args( - json!({ - "amount" : "100", - "recipient" : SENDER_ADDRESS.to_string() - }) - .to_string() - .into_bytes(), - ) - .deposit(ONE_YOCTO) - .transact(), - ), + &user + .call(&token_account_id.parse().unwrap(), "withdraw") + .max_gas() + .args( + json!({ + "amount" : "100", + "recipient" : SENDER_ADDRESS.to_string() + }) + .to_string() + .into_bytes(), + ) + .deposit(ONE_YOCTO) + .transact() + .await, "The account doesn't have enough balance", ); - let other_user = rt - .block_on( - user.create_subaccount("bob") - .initial_balance(50 * ONE_NEAR) - .transact(), - ) + let other_user = user + .create_subaccount("bob") + .initial_balance(50 * ONE_NEAR) + .transact() + .await .unwrap() .into_result() .unwrap(); // Fail to mint because the caller is not the controller assert_error( - &rt.block_on( - other_user - .call(&token_account_id.parse().unwrap(), "mint") - .max_gas() - .args( - json!({ - "account_id" : ALICE.to_string(), - "amount" : "100" - }) - .to_string() - .into_bytes(), - ) - .deposit(ONE_YOCTO) - .transact(), - ), + &other_user + .call(&token_account_id.parse().unwrap(), "mint") + .max_gas() + .args( + json!({ + "account_id" : ALICE.to_string(), + "amount" : "100" + }) + .to_string() + .into_bytes(), + ) + .deposit(ONE_YOCTO) + .transact() + .await, "Only controller can call mint", ); } -#[test] -fn test_deploy_failures() { - let (user, factory, _worker) = create_contract(); - let rt = Runtime::new().unwrap(); +#[tokio::test] +async fn test_deploy_failures() { + let (user, factory, _worker) = create_contract(FACTORY_WASM_PATH).await; // Fails with not enough deposit. assert_error( - &rt.block_on( - user.call(factory.id(), "deploy_bridge_token") - .deposit(0) - .args(json!({ "address": DAI_ADDRESS }).to_string().into_bytes()) - .max_gas() - .transact(), - ), + &user + .call(factory.id(), "deploy_bridge_token") + .deposit(0) + .args(json!({ "address": DAI_ADDRESS }).to_string().into_bytes()) + .max_gas() + .transact() + .await, "Not enough attached deposit to complete bridge token creation", ); // Fails with address is invalid. assert_error( - &rt.block_on( - user.call(factory.id(), "deploy_bridge_token") - .deposit(0) - .args(json!({"address": "not_a_hex"}).to_string().into_bytes()) - .max_gas() - .transact(), - ), - "address should be a valid hex string.: OddLength", + &user + .call(factory.id(), "deploy_bridge_token") + .deposit(0) + .args(json!({"address": "not_a_hex"}).to_string().into_bytes()) + .max_gas() + .transact() + .await, + "Invalid hex character", ); // Fails second time because already exists. - assert!(&rt - .block_on( - user.call(factory.id(), "deploy_bridge_token") - .deposit(35 * ONE_NEAR) - .args( - json!({"address": DAI_ADDRESS.to_string()}) - .to_string() - .into_bytes() - ) - .max_gas() - .transact() + assert!(user + .call(factory.id(), "deploy_bridge_token") + .deposit(35 * ONE_NEAR) + .args( + json!({"address": DAI_ADDRESS.to_string()}) + .to_string() + .into_bytes() ) + .max_gas() + .transact() + .await .unwrap() .is_success()); assert_error( - &rt.block_on( - user.call(factory.id(), "deploy_bridge_token") - .deposit(35 * ONE_NEAR) - .args(json!({ "address": DAI_ADDRESS }).to_string().into_bytes()) - .max_gas() - .transact(), - ), + &user + .call(factory.id(), "deploy_bridge_token") + .deposit(35 * ONE_NEAR) + .args(json!({ "address": DAI_ADDRESS }).to_string().into_bytes()) + .max_gas() + .transact() + .await, "BridgeToken contract already exists.", ); } + +#[tokio::test] +async fn test_upgrade() { + let (alice, factory, worker) = create_contract(FACTORY_WASM_PATH_V_0_1_6).await; + const INIT_ALICE_BALANCE: u64 = 1000; + const WITHDRAW_AMOUNT: u64 = 100; + + let token_account = Account::from_secret_key( + format!("{}.{}", DAI_ADDRESS, factory.id()).parse().unwrap(), + factory.as_account().secret_key().clone(), + &worker, + ); + + assert!(alice + .call(factory.id(), "deploy_bridge_token") + .deposit(35 * ONE_NEAR) + .args( + json!({"address": DAI_ADDRESS.to_string()}) + .to_string() + .into_bytes() + ) + .max_gas() + .transact() + .await + .unwrap() + .is_success()); + + let token_account_id: String = factory + .view( + "get_bridge_token_account_id", + json!({"address": DAI_ADDRESS.to_string()}) + .to_string() + .into_bytes(), + ) + .await + .unwrap() + .json() + .unwrap(); + + assert_eq!(token_account_id, token_account.id().to_string()); + + // Deploy new version + let factory = factory + .as_account() + .deploy(&(std::fs::read(FACTORY_WASM_PATH).unwrap())) + .await + .unwrap() + .result; + + // Migrate factory contract + let result = factory.call("migrate").max_gas().transact().await.unwrap(); + assert!(result.is_success()); + + let token_account_id: String = factory + .view( + "get_bridge_token_account_id", + json!({"address": DAI_ADDRESS.to_string()}) + .to_string() + .into_bytes(), + ) + .await + .unwrap() + .json() + .unwrap(); + + assert_eq!(token_account_id, token_account.id().to_string()); + + // Verify the factory version + let factory_version: String = factory + .view("version", "".as_bytes().to_vec()) + .await + .unwrap() + .json() + .unwrap(); + assert_eq!(factory_version, "0.2.0"); + + // Set alice as super admin + let result = factory + .call("acl_init_super_admin") + .args( + json!({ + "account_id": alice.id().to_string() + }) + .to_string() + .into_bytes(), + ) + .max_gas() + .transact() + .await + .unwrap(); + + let result: bool = result.json().unwrap(); + assert!(result); + + // Grant alice the `UpgradableManager` role + let result = alice + .call(factory.id(), "acl_grant_role") + .args( + json!({ + "role": "UpgradableManager", + "account_id": alice.id().to_string() + }) + .to_string() + .into_bytes(), + ) + .max_gas() + .transact() + .await + .unwrap(); + let result: bool = result.json().unwrap(); + assert!(result); + + // Upgrade the bridge token directly because self-upgrade is not supported in version `0.1.6` + let _token_contract = token_account + .deploy(&std::fs::read(BRIDGE_TOKEN_WASM_PATH).unwrap()) + .await + .unwrap() + .result; + + // Verify the bridge token version + let token_version: String = worker + .view(token_account.id(), "version", vec![]) + .await + .unwrap() + .json() + .unwrap(); + assert_eq!(token_version, "0.2.0"); + + // Upgrade the bridge token over factory (redeploy the same version) + let result = alice + .call(factory.id(), "upgrade_bridge_token") + .args( + json!({"address": DAI_ADDRESS.to_string()}) + .to_string() + .into_bytes(), + ) + .max_gas() + .transact() + .await + .unwrap(); + assert!(result.is_success()); + + // Verify the bridge token version + let token_version: String = worker + .view(token_account.id(), "version", vec![]) + .await + .unwrap() + .json() + .unwrap(); + assert_eq!(token_version, "0.2.0"); + + // Grant alice the `PauseManager` role + let result = alice + .call(factory.id(), "acl_grant_role") + .args( + json!({ + "role": "PauseManager", + "account_id": alice.id().to_string() + }) + .to_string() + .into_bytes(), + ) + .max_gas() + .transact() + .await + .unwrap(); + let result: bool = result.json().unwrap(); + assert!(result); + + // Pause bridge token withdraw + let result = alice + .call(factory.id(), "set_paused_withdraw") + .args( + json!({"address": DAI_ADDRESS.to_string(), "paused": true}) + .to_string() + .into_bytes(), + ) + .max_gas() + .transact() + .await + .unwrap(); + println!("{:?}", result); + assert!(result.is_success()); + + // Verify bridge token is paused + let is_paused: bool = alice + .call(token_account.id(), "is_paused") + .max_gas() + .transact() + .await + .unwrap() + .json() + .unwrap(); + assert!(is_paused); +} diff --git a/bridge-token/Cargo.lock b/bridge-token/Cargo.lock index a1093621..62b93809 100644 --- a/bridge-token/Cargo.lock +++ b/bridge-token/Cargo.lock @@ -8,25 +8,26 @@ version = "0.11.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fe438c63458706e03479442743baae6c88256498e6431708f6dfc520a26515d3" -[[package]] -name = "admin-controlled" -version = "0.1.0" -source = "git+https://github.com/near/rainbow-bridge?rev=67966a151122815039f07b02065ab3a2bba09804#67966a151122815039f07b02065ab3a2bba09804" -dependencies = [ - "near-sdk", -] - [[package]] name = "ahash" version = "0.7.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fcb51a0695d8f838b1ee009b3fbf66bda078cd64590202a864a8f3e8c4315c47" dependencies = [ - "getrandom 0.2.7", + "getrandom 0.2.8", "once_cell", "version_check", ] +[[package]] +name = "android_system_properties" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "819e7219dbd41043ac279b19830f2efc897156490d7fd6ea916720117ee66311" +dependencies = [ + "libc", +] + [[package]] name = "arrayref" version = "0.3.6" @@ -47,9 +48,9 @@ checksum = "8da52d66c7071e2e3fa2a1e5c6d088fec47b593032b254f5e980de8ea54454d6" [[package]] name = "autocfg" -version = "1.0.1" +version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cdb031dd78e28731d87d56cc8ffef4a8f36ca26c38fe2de700543e627f8a464a" +checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" [[package]] name = "base64" @@ -59,9 +60,9 @@ checksum = "b41b7ea54a0c9d92199de89e20e58d49f02f8e699814ef3fdf266f6f748d15c7" [[package]] name = "base64" -version = "0.13.0" +version = "0.13.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "904dfeac50f3cdaba28fc6f57fdcddb75f49ed61346676a78c4ffe55877802fd" +checksum = "9e1b586273c5702936fe7b7d6896644d8be71e6314cfe09d3167c95f712589e8" [[package]] name = "bitvec" @@ -92,25 +93,18 @@ version = "0.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4152116fd6e9dadb291ae18fc1ec3575ed6d84c29642d97890f4b4a3417297e4" dependencies = [ - "block-padding", "generic-array", ] [[package]] name = "block-buffer" -version = "0.10.2" +version = "0.10.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0bf7fe51849ea569fd452f37822f606a5cabb684dc918707a0193fd4664ff324" +checksum = "69cce20737498f97b993470a6e536b8523f0af7892a4f928cceb1ac5e52ebe7e" dependencies = [ "generic-array", ] -[[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.9.3" @@ -158,9 +152,8 @@ dependencies = [ [[package]] name = "bridge-token" -version = "0.1.0" +version = "0.2.0" dependencies = [ - "admin-controlled", "near-contract-standards", "near-sdk", ] @@ -173,21 +166,21 @@ checksum = "771fe0050b883fcc3ea2359b1a96bcfbc090b7116eae7c3c512c7a083fdf23d3" [[package]] name = "bumpalo" -version = "3.10.0" +version = "3.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "37ccbd214614c6783386c1af30caf03192f17891059cecc394b4fb119e363de3" +checksum = "572f695136211188308f16ad2ca5c851a712c464060ae6974944458eb83880ba" [[package]] name = "byte-slice-cast" -version = "1.2.1" +version = "1.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "87c5fdd0166095e1d463fc6cc01aa8ce547ad77a4e84d42eb6762b084e28067e" +checksum = "c3ac9f8b63eca6fd385229b3675f6cc0dc5c8a5c8a54a59d4f52ffd670d87b0c" [[package]] name = "byteorder" -version = "1.4.2" +version = "1.4.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ae44d1a3d5a19df61dd0c8beb138458ac2a53a7ac09eba97d55592540004306b" +checksum = "14c189c53d098945499cdfa7ecc63567cf3886b3332b312a5b4585d8d3a6a610" [[package]] name = "bytesize" @@ -207,9 +200,9 @@ dependencies = [ [[package]] name = "cc" -version = "1.0.73" +version = "1.0.74" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2fff2a6927b3bb87f9595d67196a70493f627687a71d87a0d692242c33f58c11" +checksum = "581f5dba903aac52ea3feb5ec4810848460ee833876f1f9b0fdeab1f19091574" [[package]] name = "cfg-if" @@ -225,10 +218,11 @@ checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" [[package]] name = "chrono" -version = "0.4.20" +version = "0.4.22" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6127248204b9aba09a362f6c930ef6a78f2c1b2215f8a7b398c06e1083f17af0" +checksum = "bfd4d1b31faaa3a89d7934dbded3111da0d2ef28e3ebccdb4f0179f5929d1ef1" dependencies = [ + "iana-time-zone", "js-sys", "num-integer", "num-traits", @@ -248,19 +242,35 @@ dependencies = [ ] [[package]] -name = "cpufeatures" -version = "0.2.2" +name = "codespan-reporting" +version = "0.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "59a6001667ab124aebae2a495118e11d30984c3a653e99d86d58971708cf5e4b" +checksum = "3538270d33cc669650c4b093848450d380def10c331d38c768e34cac80576e6e" dependencies = [ - "libc", + "termcolor", + "unicode-width", ] [[package]] -name = "cpuid-bool" -version = "0.1.2" +name = "convert_case" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6245d59a3e82a7fc217c5828a6692dbc6dfb63a0c8c90495621f7b9d79704a0e" + +[[package]] +name = "core-foundation-sys" +version = "0.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5827cebf4670468b8772dd191856768aedcb1b0278a04f989f7766351917b9dc" + +[[package]] +name = "cpufeatures" +version = "0.2.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8aebca1129a03dc6dc2b127edd729435bbc4a37e1d5f4d7513165089ceb02634" +checksum = "28d997bd5e24a5928dd43e46dc529867e207907fe0b239c3477d924f7f2ca320" +dependencies = [ + "libc", +] [[package]] name = "crunchy" @@ -301,14 +311,60 @@ dependencies = [ "zeroize", ] +[[package]] +name = "cxx" +version = "1.0.80" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6b7d4e43b25d3c994662706a1d4fcfc32aaa6afd287502c111b237093bb23f3a" +dependencies = [ + "cc", + "cxxbridge-flags", + "cxxbridge-macro", + "link-cplusplus", +] + +[[package]] +name = "cxx-build" +version = "1.0.80" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "84f8829ddc213e2c1368e51a2564c552b65a8cb6a28f31e576270ac81d5e5827" +dependencies = [ + "cc", + "codespan-reporting", + "once_cell", + "proc-macro2", + "quote", + "scratch", + "syn", +] + +[[package]] +name = "cxxbridge-flags" +version = "1.0.80" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e72537424b474af1460806647c41d4b6d35d09ef7fe031c5c2fa5766047cc56a" + +[[package]] +name = "cxxbridge-macro" +version = "1.0.80" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "309e4fb93eed90e1e14bea0da16b209f81813ba9fc7830c20ed151dd7bc0a4d7" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "derive_more" -version = "0.99.11" +version = "0.99.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "41cb0e6161ad61ed084a36ba71fbba9e3ac5aee3606fb607fe08da6acbcf3d8c" +checksum = "4fb810d30a7c1953f91334de7244731fc3f3c10d7fe163338a35b9f640960321" dependencies = [ + "convert_case", "proc-macro2", "quote", + "rustc_version", "syn", ] @@ -323,14 +379,20 @@ dependencies = [ [[package]] name = "digest" -version = "0.10.3" +version = "0.10.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f2fb860ca6fafa5552fb6d0e816a69c8e49f0908bf524e30a90d97c85892d506" +checksum = "adfbc57365a37acbd2ebf2b64d7e69bb766e2fea813521ed536f5d0520dcf86c" dependencies = [ - "block-buffer 0.10.2", + "block-buffer 0.10.3", "crypto-common", ] +[[package]] +name = "dyn-clone" +version = "1.0.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "68b0cf012f1230e43cd00ebb729c6bb58707ecfa8ad08b52ef3a4ccd2697fc30" + [[package]] name = "easy-ext" version = "0.2.9" @@ -356,7 +418,7 @@ dependencies = [ "ed25519", "rand 0.7.3", "serde", - "sha2 0.9.3", + "sha2 0.9.9", "zeroize", ] @@ -380,9 +442,9 @@ checksum = "fed34cd105917e91daa4da6b3728c47b068749d6a62c59811f06ed2ac71d9da7" [[package]] name = "generic-array" -version = "0.14.4" +version = "0.14.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "501466ecc8a30d1d3b7fc9229b122b2ce8ed6e9d9223f1138d4babb253e51817" +checksum = "bff49e947297f3312447abdca79f45f4738097cc82b06e72054d2223f601f1b9" dependencies = [ "typenum", "version_check", @@ -401,9 +463,9 @@ dependencies = [ [[package]] name = "getrandom" -version = "0.2.7" +version = "0.2.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4eb1a864a501629691edf6c15a593b7a51eebaa1e8468e9ddc623de7c9b58ec6" +checksum = "c05aeb6a22b8f62540c194aac980f2115af067bfe15a0734d7277a768d396b31" dependencies = [ "cfg-if 1.0.0", "libc", @@ -431,6 +493,30 @@ version = "0.4.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" +[[package]] +name = "iana-time-zone" +version = "0.1.53" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "64c122667b287044802d6ce17ee2ddf13207ed924c712de9a66a5814d5b64765" +dependencies = [ + "android_system_properties", + "core-foundation-sys", + "iana-time-zone-haiku", + "js-sys", + "wasm-bindgen", + "winapi", +] + +[[package]] +name = "iana-time-zone-haiku" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0703ae284fc167426161c2e3f1da3ea71d94b21bedbcc9494e92b28e334e3dca" +dependencies = [ + "cxx", + "cxx-build", +] + [[package]] name = "impl-codec" version = "0.5.1" @@ -453,36 +539,48 @@ dependencies = [ [[package]] name = "itoa" -version = "0.4.7" +version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dd25036021b0de88a0aff6b850051563c6516d0bf53f8638938edbb9de732736" +checksum = "4217ad341ebadf8d8e724e264f13e593e0648f5b3e94b3896a5df283be015ecc" [[package]] name = "js-sys" -version = "0.3.49" +version = "0.3.60" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dc15e39392125075f60c95ba416f5381ff6c3a948ff02ab12464715adf56c821" +checksum = "49409df3e3bf0856b916e2ceaca09ee28e6871cf7d9ce97a692cacfdb2a25a47" dependencies = [ "wasm-bindgen", ] [[package]] name = "keccak" -version = "0.1.0" +version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "67c21572b4949434e4fc1e1978b99c5f77064153c59d998bf13ecd96fb5ecba7" +checksum = "f9b7d56ba4a8344d6be9729995e6b06f928af29998cdf79fe390cbf6b1fee838" [[package]] name = "lazy_static" version = "1.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" +dependencies = [ + "spin", +] [[package]] name = "libc" -version = "0.2.129" +version = "0.2.137" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "64de3cc433455c14174d42e554d4027ee631c4d046d43e3ecc6efc4636cdc7a7" +checksum = "fc7fcc620a3bff7cdd7a365be3376c97191aeaccc2a603e600951e452615bf89" + +[[package]] +name = "link-cplusplus" +version = "1.0.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9272ab7b96c9046fbc5bc56c06c117cb639fe2d509df0c421cad82d2915cf369" +dependencies = [ + "cc", +] [[package]] name = "log" @@ -499,11 +597,23 @@ version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8452105ba047068f40ff7093dd1d9da90898e63dd61736462e9cdda6a90ad3c3" +[[package]] +name = "near-abi" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "885db39b08518fa700b73fa2214e8adbbfba316ba82dd510f50519173eadaf73" +dependencies = [ + "borsh", + "schemars", + "semver", + "serde", +] + [[package]] name = "near-account-id" -version = "0.13.0" +version = "0.14.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "de83d74a9241be8cc4eb3055216966b58bf8c463e8e285c0dc553925acdd19fa" +checksum = "71d258582a1878e6db67400b0504a5099db85718d22c2e07f747fe1706ae7150" dependencies = [ "borsh", "serde", @@ -511,20 +621,21 @@ dependencies = [ [[package]] name = "near-contract-standards" -version = "4.0.0" +version = "4.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6466c6aaad18800ff6a3cd104427dc8fd79e144714135224b8289b1dc43f7167" +checksum = "7bacc932e79b26472797adfb21689294b6f90960d1570daaf1e0b682b59fcb35" dependencies = [ "near-sdk", + "schemars", "serde", "serde_json", ] [[package]] name = "near-crypto" -version = "0.13.0" +version = "0.14.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b8ecf0b8b31aa7f4e60f629f72213a2617ca4a5f45cd1ae9ed2cf7cecfebdbb7" +checksum = "1e75673d69fd7365508f3d32483669fe45b03bfb34e4d9363e90adae9dfb416c" dependencies = [ "arrayref", "blake2", @@ -534,7 +645,6 @@ dependencies = [ "curve25519-dalek", "derive_more", "ed25519-dalek", - "libc", "near-account-id", "once_cell", "parity-secp256k1", @@ -549,9 +659,9 @@ dependencies = [ [[package]] name = "near-primitives" -version = "0.13.0" +version = "0.14.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0a2ba19282e79a4485a77736b679d276b09870bbf8042a18e0f0ae36347489c5" +checksum = "8ad1a9a1640539c81f065425c31bffcfbf6b31ef1aeaade59ce905f5df6ac860" dependencies = [ "borsh", "byteorder", @@ -578,9 +688,9 @@ dependencies = [ [[package]] name = "near-primitives-core" -version = "0.13.0" +version = "0.14.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bb561feb392bb8c4f540256073446e6689af087bf6356e8dddcf75fc279f201f" +checksum = "91d508f0fc340f6461e4e256417685720d3c4c00bb5a939b105160e49137caba" dependencies = [ "base64 0.11.0", "borsh", @@ -589,15 +699,15 @@ dependencies = [ "near-account-id", "num-rational", "serde", - "sha2 0.10.2", + "sha2 0.10.6", "strum", ] [[package]] name = "near-rpc-error-core" -version = "0.13.0" +version = "0.14.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "77fdd7ea8d8f786878651c37691515d5053f827ae60894aa40c16882b78f77c9" +checksum = "93ee0b41c75ef859c193a8ff1dadfa0c8207bc0ac447cc22259721ad769a1408" dependencies = [ "quote", "serde", @@ -606,9 +716,9 @@ dependencies = [ [[package]] name = "near-rpc-error-macro" -version = "0.13.0" +version = "0.14.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e521842b6ae864dfe5391afbbe2df9e9d8427c26e9333b2e0b65cd42094f7607" +checksum = "8e837bd4bacd807073ec5ceb85708da7f721b46a4c2a978de86027fb0034ce31" dependencies = [ "near-rpc-error-core", "serde", @@ -617,19 +727,22 @@ dependencies = [ [[package]] name = "near-sdk" -version = "4.0.0" +version = "4.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bda34e06e28fb9a09ac54efbdc49f0c9308780fc932aaa81c49c493fde974045" +checksum = "15eb3de2defe3626260cc209a6cdb985c6b27b0bd4619fad97dcfae002c3c5bd" dependencies = [ - "base64 0.13.0", + "base64 0.13.1", "borsh", "bs58", + "near-abi", "near-crypto", "near-primitives", "near-primitives-core", "near-sdk-macros", "near-sys", "near-vm-logic", + "once_cell", + "schemars", "serde", "serde_json", "wee_alloc", @@ -637,9 +750,9 @@ dependencies = [ [[package]] name = "near-sdk-macros" -version = "4.0.0" +version = "4.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "72064fcc15a623a0d40a6c199ea5cbdc30a83cae4816889d46f218acf31bfba8" +checksum = "4907affc9f5ed559456509188ff0024f1f2099c0830e6bdb66eb61d5b75912c0" dependencies = [ "Inflector", "proc-macro2", @@ -655,9 +768,9 @@ checksum = "e307313276eaeced2ca95740b5639e1f3125b7c97f0a1151809d105f1aa8c6d3" [[package]] name = "near-vm-errors" -version = "0.13.0" +version = "0.14.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0e02faf2bc1f6ef82b965cfe44389808fb5594f7aca4b596766117f4ce74df20" +checksum = "d0da466a30f0446639cbd788c30865086fac3e8dcb07a79e51d2b0775ed4261e" dependencies = [ "borsh", "near-account-id", @@ -667,11 +780,11 @@ dependencies = [ [[package]] name = "near-vm-logic" -version = "0.13.0" +version = "0.14.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f024d90451cd3c24d7a0a5cabf3636b192a60eb8e3ff0456f6c18b91152c346d" +checksum = "81b534828419bacbf1f7b11ef7b00420f248c548c485d3f0cfda8bb6931152f2" dependencies = [ - "base64 0.13.0", + "base64 0.13.1", "borsh", "bs58", "byteorder", @@ -682,15 +795,16 @@ dependencies = [ "near-vm-errors", "ripemd", "serde", - "sha2 0.10.2", + "sha2 0.10.6", "sha3", + "zeropool-bn", ] [[package]] name = "num-bigint" -version = "0.3.1" +version = "0.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5e9a41747ae4633fce5adffb4d2e81ffc5e89593cb19917f8fb2cc5ff76507bf" +checksum = "5f6f7833f2cbf2360a6cfd58cd41a53aa7a90bd4c202f5b1c7dd2ed73c57b2c3" dependencies = [ "autocfg", "num-integer", @@ -699,9 +813,9 @@ dependencies = [ [[package]] name = "num-integer" -version = "0.1.44" +version = "0.1.45" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d2cc698a63b549a70bc047073d2949cce27cd1c7b0a4a862d08a8031bc2801db" +checksum = "225d3389fb3509a24c93f5c29eb6bde2586b98d9f016636dff58d7c6f7569cd9" dependencies = [ "autocfg", "num-traits", @@ -722,18 +836,18 @@ dependencies = [ [[package]] name = "num-traits" -version = "0.2.14" +version = "0.2.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9a64b1ec5cda2586e284722486d802acf1f7dbdc623e2bfc57e65ca1cd099290" +checksum = "578ede34cf02f8924ab9447f50c28075b4d3e5b269972345e7e0372b38c6cdcd" dependencies = [ "autocfg", ] [[package]] name = "once_cell" -version = "1.13.0" +version = "1.16.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "18a6dbe30758c9f83eb00cbea4ac95966305f5a7772f3f42ebfc7fc7eddbd8e1" +checksum = "86f0b0d4bf799edbc74508c1e8bf170ff5f41238e5f8225603ca7caaae2b7860" [[package]] name = "opaque-debug" @@ -781,9 +895,9 @@ dependencies = [ [[package]] name = "ppv-lite86" -version = "0.2.16" +version = "0.2.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eb9f9e6e233e5c4a35559a617bf40a4ec447db2e84c20b55a6f83167b7e57872" +checksum = "5b40af805b3121feab8a3c29f04d8ad262fa8e0561883e7653e024ae4479e6de" [[package]] name = "primitive-types" @@ -818,18 +932,18 @@ dependencies = [ [[package]] name = "proc-macro2" -version = "1.0.24" +version = "1.0.47" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1e0704ee1a7e00d7bb417d0770ea303c1bccbabf0ef1667dae92b5967f5f8a71" +checksum = "5ea3d908b0e36316caf9e9e2c4625cdde190a7e6f440d794667ed17a1855e725" dependencies = [ - "unicode-xid", + "unicode-ident", ] [[package]] name = "quote" -version = "1.0.9" +version = "1.0.21" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c3d0b9745dc2debf507c8422de05d7226cc1f0644216dfdfead988f9b1ab32a7" +checksum = "bbe448f377a7d6961e30f5955f9b8d106c3f5e449d493ee1b125c1d43c2b5179" dependencies = [ "proc-macro2", ] @@ -861,7 +975,7 @@ checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" dependencies = [ "libc", "rand_chacha 0.3.1", - "rand_core 0.6.3", + "rand_core 0.6.4", ] [[package]] @@ -881,7 +995,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" dependencies = [ "ppv-lite86", - "rand_core 0.6.3", + "rand_core 0.6.4", ] [[package]] @@ -895,11 +1009,11 @@ dependencies = [ [[package]] name = "rand_core" -version = "0.6.3" +version = "0.6.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d34f1408f55294453790c48b2f1ebbb1c5b4b7563eb1f418bcfcfdbb06ebb4e7" +checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" dependencies = [ - "getrandom 0.2.7", + "getrandom 0.2.8", ] [[package]] @@ -922,11 +1036,11 @@ dependencies = [ [[package]] name = "ripemd" -version = "0.1.1" +version = "0.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1facec54cb5e0dc08553501fa740091086d0259ad0067e0d4103448e4cb22ed3" +checksum = "bd124222d17ad93a644ed9d011a40f4fb64aa54275c08cc216524a9ea82fb09f" dependencies = [ - "digest 0.10.3", + "digest 0.10.5", ] [[package]] @@ -935,6 +1049,15 @@ version = "2.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3e75f6a532d0fd9f7f13144f392b6ad56a32696bfcd9c78f797f16bbb6f072d6" +[[package]] +name = "rustc_version" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bfa0f585226d2e68097d4f95d113b15b83a82e819ab25717ec0590d9584ef366" +dependencies = [ + "semver", +] + [[package]] name = "rustversion" version = "1.0.9" @@ -943,24 +1066,71 @@ checksum = "97477e48b4cf8603ad5f7aaf897467cf42ab4218a38ef76fb14c2d6773a6d6a8" [[package]] name = "ryu" -version = "1.0.5" +version = "1.0.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "71d301d4193d031abdd79ff7e3dd721168a9572ef3fe51a1517aba235bd8f86e" +checksum = "4501abdff3ae82a1c1b477a17252eb69cee9e66eb915c1abaa4f44d873df9f09" + +[[package]] +name = "schemars" +version = "0.8.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "02c613288622e5f0c3fdc5dbd4db1c5fbe752746b1d1a56a0630b78fd00de44f" +dependencies = [ + "dyn-clone", + "schemars_derive", + "serde", + "serde_json", +] + +[[package]] +name = "schemars_derive" +version = "0.8.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "109da1e6b197438deb6db99952990c7f959572794b80ff93707d55a232545e7c" +dependencies = [ + "proc-macro2", + "quote", + "serde_derive_internals", + "syn", +] + +[[package]] +name = "scratch" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c8132065adcfd6e02db789d9285a0deb2f3fcb04002865ab67d5fb103533898" + +[[package]] +name = "semver" +version = "1.0.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e25dfac463d778e353db5be2449d1cce89bd6fd23c9f1ea21310ce6e5a1b29c4" [[package]] name = "serde" -version = "1.0.118" +version = "1.0.147" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "06c64263859d87aa2eb554587e2d23183398d617427327cf2b3d0ed8c69e4800" +checksum = "d193d69bae983fc11a79df82342761dfbf28a99fc8d203dca4c3c1b590948965" dependencies = [ "serde_derive", ] [[package]] name = "serde_derive" -version = "1.0.118" +version = "1.0.147" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c84d3526699cd55261af4b941e4e725444df67aa4f9e6a3564f18030d12672df" +checksum = "4f1d362ca8fc9c3e3a7484440752472d68a6caa98f1ab81d99b5dfe517cec852" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "serde_derive_internals" +version = "0.26.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85bf8229e7920a9f636479437026331ce11aa132b4dde37d121944a44d6e5f3c" dependencies = [ "proc-macro2", "quote", @@ -969,9 +1139,9 @@ dependencies = [ [[package]] name = "serde_json" -version = "1.0.64" +version = "1.0.87" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "799e97dc9fdae36a5c8b8f2cae9ce2ee9fdce2058c57a93e6099d919fd982f79" +checksum = "6ce777b7b150d76b9cf60d28b55f5847135a003f7d7350c6be7a773508ce7d45" dependencies = [ "itoa", "ryu", @@ -980,51 +1150,49 @@ dependencies = [ [[package]] name = "sha2" -version = "0.9.3" +version = "0.9.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fa827a14b29ab7f44778d14a88d3cb76e949c45083f7dbfa507d0cb699dc12de" +checksum = "4d58a1e1bf39749807d89cf2d98ac2dfa0ff1cb3faa38fbb64dd88ac8013d800" dependencies = [ "block-buffer 0.9.0", "cfg-if 1.0.0", - "cpuid-bool", + "cpufeatures", "digest 0.9.0", "opaque-debug", ] [[package]] name = "sha2" -version = "0.10.2" +version = "0.10.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "55deaec60f81eefe3cce0dc50bda92d6d8e88f2a27df7c5033b42afeb1ed2676" +checksum = "82e6b795fe2e3b1e845bafcb27aa35405c4d47cdfc92af5fc8d3002f76cebdc0" dependencies = [ "cfg-if 1.0.0", "cpufeatures", - "digest 0.10.3", + "digest 0.10.5", ] [[package]] name = "sha3" -version = "0.9.1" +version = "0.10.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f81199417d4e5de3f04b1e871023acea7389672c4135918f05aa9cbf2f2fa809" +checksum = "bdf0c33fae925bdc080598b84bc15c55e7b9a4a43b3c704da051f977469691c9" dependencies = [ - "block-buffer 0.9.0", - "digest 0.9.0", + "digest 0.10.5", "keccak", - "opaque-debug", ] [[package]] name = "signature" -version = "1.5.0" +version = "1.6.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f054c6c1a6e95179d6f23ed974060dcefb2d9388bb7256900badad682c499de4" +checksum = "74233d3b3b2f6d4b006dc19dee745e73e2a6bfb6f93607cd3b02bd5b00797d7c" [[package]] name = "smallvec" -version = "1.9.0" +version = "1.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2fd0db749597d91ff862fd1d55ea87f7855a744a8425a64695b6fca237d1dad1" +checksum = "a507befe795404456341dfab10cef66ead4c041f62b8b11bbb92bffe5d0953e0" [[package]] name = "smart-default" @@ -1037,6 +1205,12 @@ dependencies = [ "syn", ] +[[package]] +name = "spin" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6e63cff320ae2c57904679ba7cb63280a3dc4613885beafb148ee7bf9aa9042d" + [[package]] name = "static_assertions" version = "1.1.0" @@ -1073,13 +1247,13 @@ checksum = "6bdef32e8150c2a081110b42772ffe7d7c9032b606bc226c8260fd97e0976601" [[package]] name = "syn" -version = "1.0.57" +version = "1.0.103" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4211ce9909eb971f111059df92c45640aad50a619cf55cd76476be803c4c68e6" +checksum = "a864042229133ada95abf3b54fdc62ef5ccabe9515b64717bcb9a1919e59445d" dependencies = [ "proc-macro2", "quote", - "unicode-xid", + "unicode-ident", ] [[package]] @@ -1100,20 +1274,29 @@ version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "55937e1799185b12863d447f42597ed69d9928686b8d88a1df17376a097d8369" +[[package]] +name = "termcolor" +version = "1.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bab24d30b911b2376f3a13cc2cd443142f0c81dda04c118693e35b3835757755" +dependencies = [ + "winapi-util", +] + [[package]] name = "thiserror" -version = "1.0.32" +version = "1.0.37" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f5f6586b7f764adc0231f4c79be7b920e766bb2f3e51b3661cdb263828f19994" +checksum = "10deb33631e3c9018b9baf9dcbbc4f737320d2b576bac10f6aefa048fa407e3e" dependencies = [ "thiserror-impl", ] [[package]] name = "thiserror-impl" -version = "1.0.32" +version = "1.0.37" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "12bafc5b54507e0149cdf1b145a5d80ab80a90bcd9275df43d4fff68460f6c21" +checksum = "982d17546b47146b28f7c22e3d08465f6b8903d0ea13c1660d9d84a6e7adcdbb" dependencies = [ "proc-macro2", "quote", @@ -1133,9 +1316,9 @@ dependencies = [ [[package]] name = "toml" -version = "0.5.8" +version = "0.5.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a31142970826733df8241ef35dc040ef98c679ab14d7c3e54d827099b3acecaa" +checksum = "8d82e1a7758622a465f8cee077614c73484dac5b836c02ff6a40d5d1010324d7" dependencies = [ "serde", ] @@ -1148,9 +1331,9 @@ checksum = "dcf81ac59edc17cc8697ff311e8f5ef2d99fcbd9817b34cec66f90b6c3dfd987" [[package]] name = "uint" -version = "0.9.3" +version = "0.9.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "12f03af7ccf01dd611cc450a0d10dbc9b745770d096473e2faf0ca6e2d66d1e0" +checksum = "a45526d29728d135c2900b0d30573fe3ee79fceb12ef534c7bb30e810a91b601" dependencies = [ "byteorder", "crunchy", @@ -1158,17 +1341,29 @@ dependencies = [ "static_assertions", ] +[[package]] +name = "unicode-ident" +version = "1.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6ceab39d59e4c9499d4e5a8ee0e2735b891bb7308ac83dfb4e80cad195c9f6f3" + +[[package]] +name = "unicode-width" +version = "0.1.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c0edd1e5b14653f783770bce4a4dabb4a5108a5370a5f5d8cfe8710c361f6c8b" + [[package]] name = "unicode-xid" -version = "0.2.1" +version = "0.2.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f7fe0bb3479651439c9112f72b6c505038574c9fbb575ed1bf3b797fa39dd564" +checksum = "f962df74c8c05a667b5ee8bcf162993134c104e96440b663c8daa176dc772d8c" [[package]] name = "version_check" -version = "0.9.2" +version = "0.9.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b5a972e5669d67ba988ce3dc826706fb0a8b01471c088cb0b6110b805cc36aed" +checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f" [[package]] name = "wasi" @@ -1190,9 +1385,9 @@ checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" [[package]] name = "wasm-bindgen" -version = "0.2.72" +version = "0.2.83" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8fe8f61dba8e5d645a4d8132dc7a0a66861ed5e1045d2c0ed940fab33bac0fbe" +checksum = "eaf9f5aceeec8be17c128b2e93e031fb8a4d469bb9c4ae2d7dc1888b26887268" dependencies = [ "cfg-if 1.0.0", "wasm-bindgen-macro", @@ -1200,13 +1395,13 @@ dependencies = [ [[package]] name = "wasm-bindgen-backend" -version = "0.2.72" +version = "0.2.83" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "046ceba58ff062da072c7cb4ba5b22a37f00a302483f7e2a6cdc18fedbdc1fd3" +checksum = "4c8ffb332579b0557b52d268b91feab8df3615f265d5270fec2a8c95b17c1142" dependencies = [ "bumpalo", - "lazy_static", "log", + "once_cell", "proc-macro2", "quote", "syn", @@ -1215,9 +1410,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro" -version = "0.2.72" +version = "0.2.83" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0ef9aa01d36cda046f797c57959ff5f3c615c9cc63997a8d545831ec7976819b" +checksum = "052be0f94026e6cbc75cdefc9bae13fd6052cdcaf532fa6c45e7ae33a1e6c810" dependencies = [ "quote", "wasm-bindgen-macro-support", @@ -1225,9 +1420,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro-support" -version = "0.2.72" +version = "0.2.83" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "96eb45c1b2ee33545a813a92dbb53856418bf7eb54ab34f7f7ff1448a5b3735d" +checksum = "07bc0c051dc5f23e307b13285f9d75df86bfdf816c5721e573dec1f9b8aa193c" dependencies = [ "proc-macro2", "quote", @@ -1238,9 +1433,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-shared" -version = "0.2.72" +version = "0.2.83" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b7148f4696fb4960a346eaa60bbfb42a1ac4ebba21f750f75fc1375b098d5ffa" +checksum = "1c38c045535d93ec4f0b4defec448e4291638ee608530863b1e2ba115d4fff7f" [[package]] name = "wee_alloc" @@ -1270,6 +1465,15 @@ version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" +[[package]] +name = "winapi-util" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "70ec6ce85bb158151cae5e5c87f95a8e97d2c0c4b001223f33a334e3ce5de178" +dependencies = [ + "winapi", +] + [[package]] name = "winapi-x86_64-pc-windows-gnu" version = "0.4.0" @@ -1302,3 +1506,17 @@ dependencies = [ "syn", "synstructure", ] + +[[package]] +name = "zeropool-bn" +version = "0.5.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "71e61de68ede9ffdd69c01664f65a178c5188b73f78faa21f0936016a888ff7c" +dependencies = [ + "borsh", + "byteorder", + "crunchy", + "lazy_static", + "rand 0.8.5", + "rustc-hex", +] diff --git a/bridge-token/Cargo.toml b/bridge-token/Cargo.toml index dadf440d..0b9753ea 100644 --- a/bridge-token/Cargo.toml +++ b/bridge-token/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "bridge-token" -version = "0.1.0" +version = "0.2.0" authors = ["Near Inc "] edition = "2021" publish = false @@ -20,7 +20,6 @@ overflow-checks = true [dependencies] near-sdk = "4.0.0" near-contract-standards = "4.0.0" -admin-controlled = { git = "https://github.com/near/rainbow-bridge", rev = "67966a151122815039f07b02065ab3a2bba09804" } [features] default = ["migrate_icon"] diff --git a/bridge-token/rust-toolchain b/bridge-token/rust-toolchain index d5a27893..973115b2 100644 --- a/bridge-token/rust-toolchain +++ b/bridge-token/rust-toolchain @@ -1 +1,3 @@ -stable-2022-07-19 +[toolchain] +channel = "1.64.0" +components = ["clippy", "rustfmt"] diff --git a/bridge-token/src/lib.rs b/bridge-token/src/lib.rs index 0874da25..0b5c6efd 100644 --- a/bridge-token/src/lib.rs +++ b/bridge-token/src/lib.rs @@ -1,17 +1,20 @@ -use admin_controlled::Mask; use near_contract_standards::fungible_token::metadata::{ FungibleTokenMetadata, FungibleTokenMetadataProvider, FT_METADATA_SPEC, }; use near_contract_standards::fungible_token::FungibleToken; use near_sdk::borsh::{self, BorshDeserialize, BorshSerialize}; use near_sdk::json_types::{Base64VecU8, U128}; +use near_sdk::serde_json::json; use near_sdk::{ - assert_one_yocto, env, ext_contract, near_bindgen, AccountId, Balance, Gas, PanicOnDefault, - Promise, PromiseOrValue, StorageUsage, + assert_one_yocto, env, ext_contract, near_bindgen, require, AccountId, Balance, Gas, + PanicOnDefault, Promise, PromiseOrValue, StorageUsage, }; /// Gas to call finish withdraw method on factory. const FINISH_WITHDRAW_GAS: Gas = Gas(Gas::ONE_TERA.0 * 50); +const MIGRATE_GAS: Gas = Gas(Gas::ONE_TERA.0 * 100); +const NO_DEPOSIT: Balance = 0; +const CURRENT_STATE_VERSION: u32 = 1; #[near_bindgen] #[derive(BorshDeserialize, BorshSerialize, PanicOnDefault)] @@ -28,13 +31,14 @@ pub struct BridgeToken { icon: Option, } -const PAUSE_WITHDRAW: Mask = 1 << 0; +pub type Mask = u128; #[ext_contract(ext_bridge_token_factory)] pub trait ExtBridgeTokenFactory { #[result_serializer(borsh)] fn finish_withdraw( &self, + #[serializer(borsh)] withdrawer: AccountId, #[serializer(borsh)] amount: Balance, #[serializer(borsh)] recipient: AccountId, ) -> Promise; @@ -44,10 +48,23 @@ pub trait ExtBridgeTokenFactory { impl BridgeToken { #[init] pub fn new() -> Self { - assert!(!env::state_exists(), "Already initialized"); + let current_account_id = env::current_account_id(); + let (_eth_address, factory_account) = current_account_id + .as_str() + .split_once(".") + .unwrap_or_else(|| env::panic_str("Invalid token address")); + + require!( + env::predecessor_account_id().as_str() == factory_account, + "Only the factory account can init this contract" + ); + + let mut token = FungibleToken::new(b"t".to_vec()); + token.internal_register_account(&env::predecessor_account_id()); + Self { controller: env::predecessor_account_id(), - token: FungibleToken::new(b"t".to_vec()), + token, name: String::default(), symbol: String::default(), reference: String::default(), @@ -69,7 +86,7 @@ impl BridgeToken { icon: Option, ) { // Only owner can change the metadata - assert!(self.controller_or_self()); + require!(self.controller_or_self()); name.map(|name| self.name = name); symbol.map(|symbol| self.symbol = symbol); @@ -98,8 +115,7 @@ impl BridgeToken { #[payable] pub fn withdraw(&mut self, amount: U128, recipient: String) -> Promise { - self.check_not_paused(PAUSE_WITHDRAW); - + require!(!self.is_paused()); assert_one_yocto(); Promise::new(env::predecessor_account_id()).transfer(1); @@ -108,7 +124,11 @@ impl BridgeToken { ext_bridge_token_factory::ext(self.controller.clone()) .with_static_gas(FINISH_WITHDRAW_GAS) - .finish_withdraw(amount.into(), recipient.parse().unwrap()) + .finish_withdraw( + env::predecessor_account_id(), + amount.into(), + recipient.parse().unwrap(), + ) } pub fn account_storage_usage(&self) -> StorageUsage { @@ -120,6 +140,43 @@ impl BridgeToken { let caller = env::predecessor_account_id(); caller == self.controller || caller == env::current_account_id() } + + pub fn is_paused(&self) -> bool { + self.paused != 0 && !self.controller_or_self() + } + + pub fn set_paused(&mut self, paused: bool) { + require!(self.controller_or_self()); + self.paused = if paused { 1 } else { 0 }; + } + + pub fn upgrade_and_migrate(&self) -> Promise { + require!( + self.controller_or_self(), + "Only the controller or self can update the code" + ); + + // Receive the code directly from the input to avoid the + // GAS overhead of deserializing parameters + let code = env::input().expect("Error: No input").to_vec(); + + // Deploy the contract on self + Promise::new(env::current_account_id()) + .deploy_contract(code) + .function_call( + "migrate".to_string(), + json!({ "from_version": CURRENT_STATE_VERSION }) + .to_string() + .into_bytes(), + NO_DEPOSIT, + MIGRATE_GAS, + ) + .as_return() + } + + pub fn version(&self) -> String { + env!("CARGO_PKG_VERSION").to_owned() + } } near_contract_standards::impl_fungible_token_core!(BridgeToken, token); @@ -143,8 +200,6 @@ impl FungibleTokenMetadataProvider for BridgeToken { } } -admin_controlled::impl_admin_controlled!(BridgeToken, paused); - // Migration #[cfg(feature = "migrate_icon")] @@ -163,6 +218,7 @@ pub struct BridgeTokenV0 { #[cfg(feature = "migrate_icon")] impl From for BridgeToken { fn from(obj: BridgeTokenV0) -> Self { + #[allow(deprecated)] Self { controller: obj.controller, token: obj.token, @@ -183,11 +239,14 @@ impl BridgeToken { /// Adding icon as suggested here: https://nomicon.io/Standards/FungibleToken/Metadata.html /// This function can only be called from the factory or from the contract itself. #[init(ignore_state)] - pub fn migrate_nep_148_add_icon() -> Self { - let old_state: BridgeTokenV0 = env::state_read() - .expect("State is not compatible with BridgeTokenV0. Migration has not been applied."); - let new_state: BridgeToken = old_state.into(); - assert!(new_state.controller_or_self()); - new_state + pub fn migrate(from_version: u32) -> Self { + if from_version == 0 { + let old_state: BridgeTokenV0 = env::state_read().expect("Contract isn't initialized"); + let new_state: BridgeToken = old_state.into(); + assert!(new_state.controller_or_self()); + new_state + } else { + env::state_read().unwrap() + } } } diff --git a/res/bridge_token.wasm b/res/bridge_token.wasm index e4cfa080..d88b47a1 100755 Binary files a/res/bridge_token.wasm and b/res/bridge_token.wasm differ diff --git a/res/bridge_token_factory.wasm b/res/bridge_token_factory.wasm index 1f820c66..5f8835b4 100755 Binary files a/res/bridge_token_factory.wasm and b/res/bridge_token_factory.wasm differ diff --git a/res/rainbow_bridge_near_token_locker.wasm b/res/rainbow_bridge_near_token_locker.wasm index 4e34ea64..0f6240bb 100755 Binary files a/res/rainbow_bridge_near_token_locker.wasm and b/res/rainbow_bridge_near_token_locker.wasm differ diff --git a/token-locker/Cargo.lock b/token-locker/Cargo.lock index 27258495..ed973db1 100644 --- a/token-locker/Cargo.lock +++ b/token-locker/Cargo.lock @@ -8,14 +8,6 @@ version = "0.11.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fe438c63458706e03479442743baae6c88256498e6431708f6dfc520a26515d3" -[[package]] -name = "admin-controlled" -version = "0.1.0" -source = "git+https://github.com/near/rainbow-bridge?rev=67966a151122815039f07b02065ab3a2bba09804#67966a151122815039f07b02065ab3a2bba09804" -dependencies = [ - "near-sdk", -] - [[package]] name = "ahash" version = "0.7.6" @@ -63,6 +55,12 @@ version = "0.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "904dfeac50f3cdaba28fc6f57fdcddb75f49ed61346676a78c4ffe55877802fd" +[[package]] +name = "bitflags" +version = "1.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" + [[package]] name = "bitvec" version = "0.17.4" @@ -170,8 +168,7 @@ dependencies = [ name = "bridge-common" version = "0.1.0" dependencies = [ - "admin-controlled", - "eth-types", + "eth-types 0.1.0 (git+https://github.com/near/rainbow-bridge?rev=4b24cbd5a6e098fd8a9cd24fbb194fa8e0fc51e4)", "ethabi", "hex", "near-sdk", @@ -382,6 +379,12 @@ dependencies = [ "crypto-common", ] +[[package]] +name = "dyn-clone" +version = "1.0.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "68b0cf012f1230e43cd00ebb729c6bb58707ecfa8ad08b52ef3a4ccd2697fc30" + [[package]] name = "easy-ext" version = "0.2.9" @@ -417,6 +420,22 @@ version = "1.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e78d4f1cc4ae33bbfc157ed5d5a5ef3bc29227303d595861deb238fcec4e9457" +[[package]] +name = "eth-types" +version = "0.1.0" +source = "git+https://github.com/near/rainbow-bridge?rev=4b24cbd5a6e098fd8a9cd24fbb194fa8e0fc51e4#4b24cbd5a6e098fd8a9cd24fbb194fa8e0fc51e4" +dependencies = [ + "borsh", + "derive_more", + "ethereum-types", + "hex", + "near-sdk", + "rlp", + "rlp-derive", + "serde", + "serde-big-array", +] + [[package]] name = "eth-types" version = "0.1.0" @@ -679,6 +698,9 @@ name = "lazy_static" version = "1.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" +dependencies = [ + "spin", +] [[package]] name = "libc" @@ -692,11 +714,23 @@ version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8452105ba047068f40ff7093dd1d9da90898e63dd61736462e9cdda6a90ad3c3" +[[package]] +name = "near-abi" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "885db39b08518fa700b73fa2214e8adbbfba316ba82dd510f50519173eadaf73" +dependencies = [ + "borsh", + "schemars", + "semver", + "serde", +] + [[package]] name = "near-account-id" -version = "0.13.0" +version = "0.14.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "de83d74a9241be8cc4eb3055216966b58bf8c463e8e285c0dc553925acdd19fa" +checksum = "71d258582a1878e6db67400b0504a5099db85718d22c2e07f747fe1706ae7150" dependencies = [ "borsh", "serde", @@ -704,20 +738,21 @@ dependencies = [ [[package]] name = "near-contract-standards" -version = "4.0.0" +version = "4.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6466c6aaad18800ff6a3cd104427dc8fd79e144714135224b8289b1dc43f7167" +checksum = "7bacc932e79b26472797adfb21689294b6f90960d1570daaf1e0b682b59fcb35" dependencies = [ "near-sdk", + "schemars", "serde", "serde_json", ] [[package]] name = "near-crypto" -version = "0.13.0" +version = "0.14.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b8ecf0b8b31aa7f4e60f629f72213a2617ca4a5f45cd1ae9ed2cf7cecfebdbb7" +checksum = "1e75673d69fd7365508f3d32483669fe45b03bfb34e4d9363e90adae9dfb416c" dependencies = [ "arrayref", "blake2", @@ -727,7 +762,6 @@ dependencies = [ "curve25519-dalek", "derive_more", "ed25519-dalek", - "libc", "near-account-id", "once_cell", "parity-secp256k1", @@ -740,11 +774,34 @@ dependencies = [ "thiserror", ] +[[package]] +name = "near-plugins" +version = "0.1.0" +source = "git+https://github.com/aurora-is-near/near-plugins?rev=93e1a30d79e72d51c4349ba71d454865c76ac690#93e1a30d79e72d51c4349ba71d454865c76ac690" +dependencies = [ + "bitflags", + "near-plugins-derive", + "near-sdk", + "serde", +] + +[[package]] +name = "near-plugins-derive" +version = "0.1.0" +source = "git+https://github.com/aurora-is-near/near-plugins?rev=93e1a30d79e72d51c4349ba71d454865c76ac690#93e1a30d79e72d51c4349ba71d454865c76ac690" +dependencies = [ + "darling", + "proc-macro-crate 0.1.5", + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "near-primitives" -version = "0.13.0" +version = "0.14.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0a2ba19282e79a4485a77736b679d276b09870bbf8042a18e0f0ae36347489c5" +checksum = "8ad1a9a1640539c81f065425c31bffcfbf6b31ef1aeaade59ce905f5df6ac860" dependencies = [ "borsh", "byteorder", @@ -771,9 +828,9 @@ dependencies = [ [[package]] name = "near-primitives-core" -version = "0.13.0" +version = "0.14.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bb561feb392bb8c4f540256073446e6689af087bf6356e8dddcf75fc279f201f" +checksum = "91d508f0fc340f6461e4e256417685720d3c4c00bb5a939b105160e49137caba" dependencies = [ "base64 0.11.0", "borsh", @@ -788,9 +845,9 @@ dependencies = [ [[package]] name = "near-rpc-error-core" -version = "0.13.0" +version = "0.14.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "77fdd7ea8d8f786878651c37691515d5053f827ae60894aa40c16882b78f77c9" +checksum = "93ee0b41c75ef859c193a8ff1dadfa0c8207bc0ac447cc22259721ad769a1408" dependencies = [ "quote", "serde", @@ -799,9 +856,9 @@ dependencies = [ [[package]] name = "near-rpc-error-macro" -version = "0.13.0" +version = "0.14.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e521842b6ae864dfe5391afbbe2df9e9d8427c26e9333b2e0b65cd42094f7607" +checksum = "8e837bd4bacd807073ec5ceb85708da7f721b46a4c2a978de86027fb0034ce31" dependencies = [ "near-rpc-error-core", "serde", @@ -810,19 +867,22 @@ dependencies = [ [[package]] name = "near-sdk" -version = "4.0.0" +version = "4.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bda34e06e28fb9a09ac54efbdc49f0c9308780fc932aaa81c49c493fde974045" +checksum = "15eb3de2defe3626260cc209a6cdb985c6b27b0bd4619fad97dcfae002c3c5bd" dependencies = [ "base64 0.13.0", "borsh", "bs58", + "near-abi", "near-crypto", "near-primitives", "near-primitives-core", "near-sdk-macros", "near-sys", "near-vm-logic", + "once_cell", + "schemars", "serde", "serde_json", "wee_alloc", @@ -830,9 +890,9 @@ dependencies = [ [[package]] name = "near-sdk-macros" -version = "4.0.0" +version = "4.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "72064fcc15a623a0d40a6c199ea5cbdc30a83cae4816889d46f218acf31bfba8" +checksum = "4907affc9f5ed559456509188ff0024f1f2099c0830e6bdb66eb61d5b75912c0" dependencies = [ "Inflector", "proc-macro2", @@ -848,9 +908,9 @@ checksum = "e307313276eaeced2ca95740b5639e1f3125b7c97f0a1151809d105f1aa8c6d3" [[package]] name = "near-vm-errors" -version = "0.13.0" +version = "0.14.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0e02faf2bc1f6ef82b965cfe44389808fb5594f7aca4b596766117f4ce74df20" +checksum = "d0da466a30f0446639cbd788c30865086fac3e8dcb07a79e51d2b0775ed4261e" dependencies = [ "borsh", "near-account-id", @@ -860,9 +920,9 @@ dependencies = [ [[package]] name = "near-vm-logic" -version = "0.13.0" +version = "0.14.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f024d90451cd3c24d7a0a5cabf3636b192a60eb8e3ff0456f6c18b91152c346d" +checksum = "81b534828419bacbf1f7b11ef7b00420f248c548c485d3f0cfda8bb6931152f2" dependencies = [ "base64 0.13.0", "borsh", @@ -877,6 +937,7 @@ dependencies = [ "serde", "sha2 0.10.2", "sha3", + "zeropool-bn", ] [[package]] @@ -1065,14 +1126,14 @@ checksum = "643f8f41a8ebc4c5dc4515c82bb8abd397b527fc20fd681b7c011c2aee5d44fb" [[package]] name = "rainbow-bridge-near-token-locker" -version = "0.1.0" +version = "0.2.0" dependencies = [ - "admin-controlled", "bridge-common", - "eth-types", + "eth-types 0.1.0 (git+https://github.com/near/rainbow-bridge?rev=67966a151122815039f07b02065ab3a2bba09804)", "ethabi", "hex", "near-contract-standards", + "near-plugins", "near-sdk", "rand 0.7.3", "serde_json", @@ -1216,6 +1277,30 @@ version = "1.0.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "73b4b750c782965c211b42f022f59af1fbceabdd026623714f104152f1ec149f" +[[package]] +name = "schemars" +version = "0.8.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "02c613288622e5f0c3fdc5dbd4db1c5fbe752746b1d1a56a0630b78fd00de44f" +dependencies = [ + "dyn-clone", + "schemars_derive", + "serde", + "serde_json", +] + +[[package]] +name = "schemars_derive" +version = "0.8.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "109da1e6b197438deb6db99952990c7f959572794b80ff93707d55a232545e7c" +dependencies = [ + "proc-macro2", + "quote", + "serde_derive_internals", + "syn", +] + [[package]] name = "semver" version = "1.0.6" @@ -1251,6 +1336,17 @@ dependencies = [ "syn", ] +[[package]] +name = "serde_derive_internals" +version = "0.26.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85bf8229e7920a9f636479437026331ce11aa132b4dde37d121944a44d6e5f3c" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "serde_json" version = "1.0.79" @@ -1321,6 +1417,12 @@ dependencies = [ "syn", ] +[[package]] +name = "spin" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6e63cff320ae2c57904679ba7cb63280a3dc4613885beafb148ee7bf9aa9042d" + [[package]] name = "static_assertions" version = "1.1.0" @@ -1581,3 +1683,17 @@ dependencies = [ "syn", "synstructure", ] + +[[package]] +name = "zeropool-bn" +version = "0.5.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "71e61de68ede9ffdd69c01664f65a178c5188b73f78faa21f0936016a888ff7c" +dependencies = [ + "borsh", + "byteorder", + "crunchy", + "lazy_static", + "rand 0.8.5", + "rustc-hex", +] diff --git a/token-locker/Cargo.toml b/token-locker/Cargo.toml index 48be6495..12ced27e 100644 --- a/token-locker/Cargo.toml +++ b/token-locker/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "rainbow-bridge-near-token-locker" -version = "0.1.0" +version = "0.2.0" authors = ["Pagoda "] edition = "2021" publish = false @@ -18,14 +18,14 @@ panic = "abort" overflow-checks = true [dependencies] -near-sdk = "4.0.0" +near-sdk = "4.1.0" near-contract-standards = "4.0.0" eth-types = { git = "https://github.com/near/rainbow-bridge", rev = "67966a151122815039f07b02065ab3a2bba09804" } -admin-controlled = { git = "https://github.com/near/rainbow-bridge", rev = "67966a151122815039f07b02065ab3a2bba09804" } tiny-keccak = "1.4.0" ethabi = "12.0.0" hex = "0.4.2" bridge-common = { path = "../bridge-common" } +near-plugins = { git = "https://github.com/aurora-is-near/near-plugins", rev = "93e1a30d79e72d51c4349ba71d454865c76ac690" } [dev-dependencies] uint = { version = "0.8.3", default-features = false } diff --git a/token-locker/rust-toolchain b/token-locker/rust-toolchain index d5a27893..973115b2 100644 --- a/token-locker/rust-toolchain +++ b/token-locker/rust-toolchain @@ -1 +1,3 @@ -stable-2022-07-19 +[toolchain] +channel = "1.64.0" +components = ["clippy", "rustfmt"] diff --git a/token-locker/src/lib.rs b/token-locker/src/lib.rs index 6c979c6d..3f7bed52 100644 --- a/token-locker/src/lib.rs +++ b/token-locker/src/lib.rs @@ -1,23 +1,25 @@ +use near_plugins::{ + access_control, access_control_any, pause, AccessControlRole, AccessControllable, Pausable, + Upgradable, +}; use std::convert::TryInto; -use admin_controlled::{AdminControlled, Mask}; use near_contract_standards::fungible_token::metadata::FungibleTokenMetadata; use near_contract_standards::storage_management::StorageBalance; use near_sdk::borsh::{self, BorshDeserialize, BorshSerialize}; -use near_sdk::collections::UnorderedSet; +use near_sdk::collections::{UnorderedMap, UnorderedSet}; use near_sdk::json_types::U128; +use near_sdk::serde::{Deserialize, Serialize}; use near_sdk::{ - env, ext_contract, near_bindgen, AccountId, Balance, Gas, PanicOnDefault, Promise, - PromiseOrValue, + env, ext_contract, near_bindgen, AccountId, Balance, BorshStorageKey, Gas, PanicOnDefault, + Promise, PromiseOrValue, }; use bridge_common::prover::{ ext_prover, validate_eth_address, EthAddress, Proof, FT_TRANSFER_CALL_GAS, FT_TRANSFER_GAS, - NO_DEPOSIT, PAUSE_DEPOSIT, STORAGE_BALANCE_CALL_GAS, VERIFY_LOG_ENTRY_GAS, + NO_DEPOSIT, STORAGE_BALANCE_CALL_GAS, VERIFY_LOG_ENTRY_GAS, }; use bridge_common::{parse_recipient, result_types, Recipient}; -use near_sdk_inner::collections::UnorderedMap; -use near_sdk_inner::BorshStorageKey; use whitelist::WhitelistMode; use crate::unlock_event::EthUnlockedEvent; @@ -42,6 +44,8 @@ const FT_FINISH_LOG_METADATA_GAS: Gas = Gas(Gas::ONE_TERA.0 * 30); /// Gas to call storage balance callback method. const FT_STORAGE_BALANCE_CALLBACK_GAS: Gas = Gas(Gas::ONE_TERA.0 * 10); +pub type Mask = u128; + #[derive(BorshSerialize, BorshStorageKey)] enum StorageKey { UsedEvents, @@ -49,8 +53,30 @@ enum StorageKey { WhitelistAccounts, } +#[derive(AccessControlRole, Deserialize, Serialize, Copy, Clone)] +#[serde(crate = "near_sdk::serde")] +pub enum Role { + PauseManager, + UpgradableManager, + UpgradableCodeStager, + UpgradableCodeDeployer, + UpgradableDurationManager, + ConfigManager, + UnrestrictedDeposit, + UnrestrictedWithdraw, +} + #[near_bindgen] -#[derive(BorshDeserialize, BorshSerialize, PanicOnDefault)] +#[derive(BorshDeserialize, BorshSerialize, PanicOnDefault, Pausable, Upgradable)] +#[access_control(role_type(Role))] +#[pausable(manager_roles(Role::PauseManager))] +#[upgradable(access_control_roles( + code_stagers(Role::UpgradableCodeStager, Role::UpgradableManager), + code_deployers(Role::UpgradableCodeDeployer, Role::UpgradableManager), + duration_initializers(Role::UpgradableDurationManager, Role::UpgradableManager), + duration_update_stagers(Role::UpgradableDurationManager, Role::UpgradableManager), + duration_update_appliers(Role::UpgradableDurationManager, Role::UpgradableManager), +))] pub struct Contract { /// The account of the prover that we can use to prove. pub prover_account: AccountId, @@ -59,6 +85,7 @@ pub struct Contract { /// Hashes of the events that were already used. pub used_events: UnorderedSet>, /// Mask determining all paused functions + #[deprecated] paused: Mask, /// Mapping whitelisted tokens to their mode pub whitelist_tokens: UnorderedMap, @@ -135,7 +162,8 @@ impl Contract { /// `prover_account`: NEAR account of the Near Prover contract; /// `factory_address`: Ethereum address of the token factory contract, in hex. pub fn new(prover_account: AccountId, factory_address: String) -> Self { - Self { + #[allow(deprecated)] + let mut contract = Self { prover_account, used_events: UnorderedSet::new(StorageKey::UsedEvents), eth_factory_address: validate_eth_address(factory_address), @@ -143,7 +171,10 @@ impl Contract { whitelist_tokens: UnorderedMap::new(StorageKey::WhitelistTokens), whitelist_accounts: UnorderedSet::new(StorageKey::WhitelistAccounts), is_whitelist_mode_enabled: true, - } + }; + + contract.acl_init_super_admin(near_sdk::env::predecessor_account_id()); + contract } /// Logs into the result of this transaction a Metadata for given token. @@ -178,8 +209,8 @@ impl Contract { /// Withdraw funds from NEAR Token Locker. /// Receives proof of burning tokens on the other side. Validates it and releases funds. #[payable] + #[pause(except(roles(Role::UnrestrictedWithdraw)))] pub fn withdraw(&mut self, #[serializer(borsh)] proof: Proof) -> Promise { - self.check_not_paused(PAUSE_DEPOSIT); let event = EthUnlockedEvent::from_log_entry_data(&proof.log_entry_data); assert_eq!( event.eth_factory_address, @@ -326,14 +357,12 @@ impl Contract { required_deposit } - #[private] + #[access_control_any(roles(Role::ConfigManager))] pub fn update_factory_address(&mut self, factory_address: String) { self.eth_factory_address = validate_eth_address(factory_address); } } -admin_controlled::impl_admin_controlled!(Contract, paused); - #[cfg(not(target_arch = "wasm32"))] #[cfg(test)] mod tests { @@ -342,13 +371,15 @@ mod tests { use near_contract_standards::fungible_token::receiver::FungibleTokenReceiver; use near_sdk::env::sha256; - use near_sdk::test_utils::{accounts, VMContextBuilder}; + use near_sdk::test_utils::VMContextBuilder; use near_sdk::testing_env; use uint::rustc_hex::{FromHex, ToHex}; use super::*; - const UNPAUSE_ALL: Mask = 0; + pub fn accounts(id: usize) -> AccountId { + AccountId::new_unchecked(near_sdk::test_utils::accounts(id).to_string() + ".near") + } macro_rules! inner_set_env { ($builder:ident) => { @@ -373,17 +404,21 @@ mod tests { } fn prover() -> AccountId { - "prover".parse().unwrap() + "prover.near".parse().unwrap() } fn bridge_token_factory() -> AccountId { - "bridge".parse().unwrap() + "bridge.near".parse().unwrap() } fn token_locker() -> String { "6b175474e89094c44da98b954eedeac495271d0f".to_string() } + fn pause_manager() -> AccountId { + "pause_manager".parse().unwrap() + } + /// Generate a valid ethereum address. fn ethereum_address_from_id(id: u8) -> String { let mut buffer = vec![id]; @@ -477,23 +512,26 @@ mod tests { } #[test] - fn test_only_admin_can_pause() { - set_env!(predecessor_account_id: accounts(0)); - let mut contract = Contract::new(prover(), token_locker()); - - // Admin can pause + fn test_only_pause_manager_can_pause() { set_env!( current_account_id: bridge_token_factory(), predecessor_account_id: bridge_token_factory(), ); - contract.set_paused(0b1111); + let mut contract = Contract::new(prover(), token_locker()); + assert!(contract + .acl_grant_role("PauseManager".to_owned(), pause_manager()) + .unwrap()); - // Admin can unpause. set_env!( current_account_id: bridge_token_factory(), - predecessor_account_id: bridge_token_factory(), + predecessor_account_id: pause_manager(), ); - contract.set_paused(UNPAUSE_ALL); + + // Pause manager can pause + contract.pa_pause_feature("deposit".to_string()); + + // Pause manager can unpause. + contract.pa_unpause_feature("deposit".to_string()); // Alice can't pause set_env!( @@ -502,7 +540,7 @@ mod tests { ); panic::catch_unwind(move || { - contract.set_paused(0); + contract.pa_pause_feature("deposit".to_string()); }) .unwrap_err(); } diff --git a/token-locker/src/token_receiver.rs b/token-locker/src/token_receiver.rs index 97671314..0fae3a77 100644 --- a/token-locker/src/token_receiver.rs +++ b/token-locker/src/token_receiver.rs @@ -6,13 +6,13 @@ use crate::*; impl FungibleTokenReceiver for Contract { /// Callback on receiving tokens by this contract. /// msg: `Ethereum` address to receive the tokens on. + #[pause(except(roles(Role::UnrestrictedDeposit)))] fn ft_on_transfer( &mut self, sender_id: AccountId, amount: U128, msg: String, ) -> PromiseOrValue { - self.check_not_paused(PAUSE_DEPOSIT); self.check_whitelist_token(env::predecessor_account_id(), sender_id); // Fails if msg is not a valid Ethereum address. let eth_address = validate_eth_address(msg); diff --git a/token-locker/src/unlock_event.rs b/token-locker/src/unlock_event.rs index a89bbeb1..57b16ca6 100644 --- a/token-locker/src/unlock_event.rs +++ b/token-locker/src/unlock_event.rs @@ -50,7 +50,7 @@ impl EthUnlockedEvent { } } - #[warn(dead_code)] + #[allow(dead_code)] pub fn to_log_entry_data(&self) -> Vec { EthEvent::to_log_entry_data( "Withdraw",