diff --git a/.cargo/config b/.cargo/config new file mode 100644 index 0000000..af616d8 --- /dev/null +++ b/.cargo/config @@ -0,0 +1,12 @@ +[profile.contract] +inherits = "release" +strip = true +codegen-units = 1 +opt-level = "z" + +#helloo + +lto = true +debug = false +panic = "abort" +overflow-checks = true diff --git a/.env b/.env new file mode 100644 index 0000000..a39a8fe --- /dev/null +++ b/.env @@ -0,0 +1,2 @@ + +export CONTRACT_NAME=hodl_lockup diff --git a/.github/workflows/push.yml b/.github/workflows/push.yml new file mode 100644 index 0000000..5579353 --- /dev/null +++ b/.github/workflows/push.yml @@ -0,0 +1,52 @@ +name: Push + +on: + push: + branches: [ main ] + +env: + CARGO_TERM_COLOR: always + +jobs: + + build: + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v4 + with: + submodules: recursive + + - name: Build + run: make build-in-docker + + lint: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + with: + submodules: recursive + + - name: Lint + run: make lint + + unit-tests: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + with: + submodules: recursive + + - name: Unit tests + run: make test + + integration-tests: + runs-on: macos-latest + steps: + - name: Checkout + uses: actions/checkout@v4 + with: + submodules: recursive + + - name: Integration tests + run: make integration diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml new file mode 100644 index 0000000..1f6b0ef --- /dev/null +++ b/.github/workflows/test.yml @@ -0,0 +1,52 @@ +name: Test + +on: + pull_request: + branches: [ main ] + +env: + CARGO_TERM_COLOR: always + +jobs: + + build: + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v4 + with: + submodules: recursive + + - name: Build + run: make build + + lint: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + with: + submodules: recursive + + - name: Lint + run: make lint + + unit-tests: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + with: + submodules: recursive + + - name: Unit tests + run: make test + + integration-tests: + runs-on: macos-latest + steps: + - name: Checkout + uses: actions/checkout@v4 + with: + submodules: recursive + + - name: Integration tests + run: make integration diff --git a/.gitignore b/.gitignore index 7dfd0f1..c5d676f 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,20 @@ +# Generated by Cargo +# will have compiled files and executables +debug/ target/ -neardev/ +# Remove Cargo.lock from gitignore if creating an executable, leave it for libraries +# More information here https://doc.rust-lang.org/cargo/guide/cargo-toml-vs-cargo-lock.html +Cargo.lock +# These are backup files generated by rustfmt +**/*.rs.bk + +# MSVC Windows builds of rustc generate these, which store debugging information +*.pdb + +res/contract_name.wasm + +.idea + +.DS_Store diff --git a/.rustfmt.toml b/.rustfmt.toml new file mode 100644 index 0000000..1a25d9c --- /dev/null +++ b/.rustfmt.toml @@ -0,0 +1,3 @@ +group_imports = "StdExternalCrate" +imports_granularity = "Crate" +max_width = 120 diff --git a/Cargo.lock b/Cargo.lock deleted file mode 100644 index e40c631..0000000 --- a/Cargo.lock +++ /dev/null @@ -1,3329 +0,0 @@ -# This file is automatically @generated by Cargo. -# It is not intended for manual editing. -version = 3 - -[[package]] -name = "Inflector" -version = "0.11.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fe438c63458706e03479442743baae6c88256498e6431708f6dfc520a26515d3" - -[[package]] -name = "addr2line" -version = "0.14.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a55f82cfe485775d02112886f4169bde0c5894d75e79ead7eafe7e40a25e45f7" -dependencies = [ - "gimli 0.23.0", -] - -[[package]] -name = "adler" -version = "1.0.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe" - -[[package]] -name = "ahash" -version = "0.4.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "739f4a8db6605981345c5654f3a85b056ce52f37a39d34da03f25bf2151ea16e" - -[[package]] -name = "aho-corasick" -version = "0.7.15" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7404febffaa47dac81aa44dba71523c9d069b1bdc50a77db41195149e17f68e5" -dependencies = [ - "memchr", -] - -[[package]] -name = "anyhow" -version = "1.0.40" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "28b2cd92db5cbd74e8e5028f7e27dd7aa3090e89e4f2a197cc7c8dfb69c7063b" - -[[package]] -name = "arrayref" -version = "0.3.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a4c527152e37cf757a3f78aae5a06fbeefdb07ccc535c980a3208ee3060dd544" - -[[package]] -name = "arrayvec" -version = "0.5.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "23b62fc65de8e4e7f52534fb52b0f3ed04746ae267519eef2a83941e8085068b" - -[[package]] -name = "arrayvec" -version = "0.7.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5a2f58b0bb10c380af2b26e57212856b8c9a59e0925b4c20f4a174a49734eaf7" - -[[package]] -name = "async-mutex" -version = "1.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "479db852db25d9dbf6204e6cb6253698f175c15726470f78af0d918e99d6156e" -dependencies = [ - "event-listener", -] - -[[package]] -name = "async-trait" -version = "0.1.42" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8d3a45e77e34375a7923b1e8febb049bb011f064714a8e17a1a616fef01da13d" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] - -[[package]] -name = "autocfg" -version = "1.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cdb031dd78e28731d87d56cc8ffef4a8f36ca26c38fe2de700543e627f8a464a" - -[[package]] -name = "backtrace" -version = "0.3.58" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "88fb5a785d6b44fd9d6700935608639af1b8356de1e55d5f7c2740f4faa15d82" -dependencies = [ - "addr2line", - "cc", - "cfg-if 1.0.0", - "libc", - "miniz_oxide", - "object 0.23.0", - "rustc-demangle", -] - -[[package]] -name = "base64" -version = "0.13.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "904dfeac50f3cdaba28fc6f57fdcddb75f49ed61346676a78c4ffe55877802fd" - -[[package]] -name = "bincode" -version = "1.3.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b1f45e9417d87227c7a56d22e471c6206462cba514c7590c09aff4cf6d1ddcad" -dependencies = [ - "serde", -] - -[[package]] -name = "bindgen" -version = "0.57.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fd4865004a46a0aafb2a0a5eb19d3c9fc46ee5f063a6cfc605c69ac9ecf5263d" -dependencies = [ - "bitflags", - "cexpr", - "clang-sys", - "lazy_static", - "lazycell", - "peeking_take_while", - "proc-macro2", - "quote", - "regex", - "rustc-hash", - "shlex", -] - -[[package]] -name = "bitflags" -version = "1.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cf1de2fe8c75bc145a2f577add951f8134889b4795d47466a54a5c846d691693" - -[[package]] -name = "bitvec" -version = "0.20.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7774144344a4faa177370406a7ff5f1da24303817368584c6206c8303eb07848" -dependencies = [ - "funty", - "radium", - "tap", - "wyz", -] - -[[package]] -name = "blake2" -version = "0.9.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "10a5720225ef5daecf08657f23791354e1685a8c91a4c60c7f3d3b2892f978f4" -dependencies = [ - "crypto-mac", - "digest 0.9.0", - "opaque-debug", -] - -[[package]] -name = "blake3" -version = "0.3.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e9ff35b701f3914bdb8fad3368d822c766ef2858b2583198e41639b936f09d3f" -dependencies = [ - "arrayref", - "arrayvec 0.5.2", - "cc", - "cfg-if 0.1.10", - "constant_time_eq", - "crypto-mac", - "digest 0.9.0", -] - -[[package]] -name = "block-buffer" -version = "0.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4152116fd6e9dadb291ae18fc1ec3575ed6d84c29642d97890f4b4a3417297e4" -dependencies = [ - "block-padding", - "generic-array 0.14.4", -] - -[[package]] -name = "block-padding" -version = "0.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8d696c370c750c948ada61c69a0ee2cbbb9c50b1019ddb86d9317157a99c2cae" - -[[package]] -name = "borsh" -version = "0.8.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "09a7111f797cc721407885a323fb071636aee57f750b1a4ddc27397eba168a74" -dependencies = [ - "borsh-derive", - "hashbrown", -] - -[[package]] -name = "borsh-derive" -version = "0.8.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "307f3740906bac2c118a8122fe22681232b244f1369273e45f1156b45c43d2dd" -dependencies = [ - "borsh-derive-internal", - "borsh-schema-derive-internal", - "proc-macro-crate", - "proc-macro2", - "syn", -] - -[[package]] -name = "borsh-derive-internal" -version = "0.8.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d2104c73179359431cc98e016998f2f23bc7a05bc53e79741bcba705f30047bc" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] - -[[package]] -name = "borsh-schema-derive-internal" -version = "0.8.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ae29eb8418fcd46f723f8691a2ac06857d31179d33d2f2d91eb13967de97c728" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] - -[[package]] -name = "bs58" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "771fe0050b883fcc3ea2359b1a96bcfbc090b7116eae7c3c512c7a083fdf23d3" - -[[package]] -name = "byte-slice-cast" -version = "1.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "65c1bf4a04a88c54f589125563643d773f3254b5c38571395e2b591c693bbc81" - -[[package]] -name = "byteorder" -version = "1.4.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "14c189c53d098945499cdfa7ecc63567cf3886b3332b312a5b4585d8d3a6a610" - -[[package]] -name = "bytes" -version = "1.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b700ce4376041dcd0a327fd0097c41095743c4c8af8887265942faf1100bd040" - -[[package]] -name = "c2-chacha" -version = "0.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fb6b83fa00a7c53f420893670940c8fdfaa89f9dd9adb52062cca39482a31ab6" -dependencies = [ - "cipher", - "ppv-lite86", -] - -[[package]] -name = "cached" -version = "0.23.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5e2afe73808fbaac302e39c9754bfc3c4b4d0f99c9c240b9f4e4efc841ad1b74" -dependencies = [ - "async-mutex", - "async-trait", - "cached_proc_macro", - "cached_proc_macro_types", - "futures", - "hashbrown", - "once_cell", -] - -[[package]] -name = "cached_proc_macro" -version = "0.6.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bf857ae42d910aede5c5186e62684b0d7a597ce2fe3bd14448ab8f7ef439848c" -dependencies = [ - "async-mutex", - "cached_proc_macro_types", - "darling 0.10.2", - "quote", - "syn", -] - -[[package]] -name = "cached_proc_macro_types" -version = "0.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3a4f925191b4367301851c6d99b09890311d74b0d43f274c0b34c86d308a3663" - -[[package]] -name = "cc" -version = "1.0.67" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e3c69b077ad434294d3ce9f1f6143a2a4b89a8a2d54ef813d85003a4fd1137fd" -dependencies = [ - "jobserver", -] - -[[package]] -name = "cexpr" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f4aedb84272dbe89af497cf81375129abda4fc0a9e7c5d317498c15cc30c0d27" -dependencies = [ - "nom", -] - -[[package]] -name = "cfg-if" -version = "0.1.10" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4785bdd1c96b2a846b2bd7cc02e86b6b3dbf14e7e53446c4f54c92a361040822" - -[[package]] -name = "cfg-if" -version = "1.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" - -[[package]] -name = "chrono" -version = "0.4.19" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "670ad68c9088c2a963aaa298cb369688cf3f9465ce5e2d4ca10e6e0098a1ce73" -dependencies = [ - "libc", - "num-integer", - "num-traits", - "serde", - "time", - "winapi", -] - -[[package]] -name = "cipher" -version = "0.2.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "12f8e7987cbd042a63249497f41aed09f8e65add917ea6566effbc56578d6801" -dependencies = [ - "generic-array 0.14.4", -] - -[[package]] -name = "clang-sys" -version = "1.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "853eda514c284c2287f4bf20ae614f8781f40a81d32ecda6e91449304dfe077c" -dependencies = [ - "glob", - "libc", - "libloading 0.7.0", -] - -[[package]] -name = "cloudabi" -version = "0.0.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ddfc5b9aa5d4507acaf872de71051dfd0e309860e88966e1051e462a077aac4f" -dependencies = [ - "bitflags", -] - -[[package]] -name = "concat-with" -version = "0.2.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "81e05b2e0bc9f34da5b073abb9fab94dd47d2a79479244c99145893a5ae28fed" - -[[package]] -name = "constant_time_eq" -version = "0.1.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "245097e9a4535ee1e3e3931fcfcd55a796a44c643e8596ff6566d68f09b87bbc" - -[[package]] -name = "convert_case" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6245d59a3e82a7fc217c5828a6692dbc6dfb63a0c8c90495621f7b9d79704a0e" - -[[package]] -name = "cpuid-bool" -version = "0.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8aebca1129a03dc6dc2b127edd729435bbc4a37e1d5f4d7513165089ceb02634" - -[[package]] -name = "cranelift-bforest" -version = "0.67.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0f065f6889758f817f61a230220d1811ba99a9762af2fb69ae23048314f75ff2" -dependencies = [ - "cranelift-entity 0.67.0", -] - -[[package]] -name = "cranelift-bforest" -version = "0.68.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9221545c0507dc08a62b2d8b5ffe8e17ac580b0a74d1813b496b8d70b070fbd0" -dependencies = [ - "cranelift-entity 0.68.0", -] - -[[package]] -name = "cranelift-codegen" -version = "0.67.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "510aa2ab4307644100682b94e449940a0ea15c5887f1d4b9678b8dd5ef31e736" -dependencies = [ - "byteorder", - "cranelift-bforest 0.67.0", - "cranelift-codegen-meta 0.67.0", - "cranelift-codegen-shared 0.67.0", - "cranelift-entity 0.67.0", - "gimli 0.21.0", - "log", - "regalloc 0.0.30", - "serde", - "smallvec", - "target-lexicon 0.11.2", - "thiserror", -] - -[[package]] -name = "cranelift-codegen" -version = "0.68.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7e9936ea608b6cd176f107037f6adbb4deac933466fc7231154f96598b2d3ab1" -dependencies = [ - "byteorder", - "cranelift-bforest 0.68.0", - "cranelift-codegen-meta 0.68.0", - "cranelift-codegen-shared 0.68.0", - "cranelift-entity 0.68.0", - "gimli 0.22.0", - "log", - "regalloc 0.0.31", - "smallvec", - "target-lexicon 0.11.2", - "thiserror", -] - -[[package]] -name = "cranelift-codegen-meta" -version = "0.67.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b4cb0c7e87c60d63b35f9670c15479ee4a5e557dd127efab88b2f9b2ca83c9a0" -dependencies = [ - "cranelift-codegen-shared 0.67.0", - "cranelift-entity 0.67.0", -] - -[[package]] -name = "cranelift-codegen-meta" -version = "0.68.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4ef2b2768568306540f4c8db3acce9105534d34c4a1e440529c1e702d7f8c8d7" -dependencies = [ - "cranelift-codegen-shared 0.68.0", - "cranelift-entity 0.68.0", -] - -[[package]] -name = "cranelift-codegen-shared" -version = "0.67.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "60636227098693e06de8d6d88beea2a7d32ecf8a8030dacdb57c68e06f381826" - -[[package]] -name = "cranelift-codegen-shared" -version = "0.68.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6759012d6d19c4caec95793f052613e9d4113e925e7f14154defbac0f1d4c938" - -[[package]] -name = "cranelift-entity" -version = "0.67.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6156db73e0c9f65f80c512988d63ec736be0dee3dd66bf951e3e28aed9dc02d3" -dependencies = [ - "serde", -] - -[[package]] -name = "cranelift-entity" -version = "0.68.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "86badbce14e15f52a45b666b38abe47b204969dd7f8fb7488cb55dd46b361fa6" -dependencies = [ - "serde", -] - -[[package]] -name = "cranelift-frontend" -version = "0.67.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e09cd158c9a820a4cc14a34076811da225cce1d31dc6d03c5ef85b91aef560b9" -dependencies = [ - "cranelift-codegen 0.67.0", - "log", - "smallvec", - "target-lexicon 0.11.2", -] - -[[package]] -name = "cranelift-frontend" -version = "0.68.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b608bb7656c554d0a4cf8f50c7a10b857e80306f6ff829ad6d468a7e2323c8d8" -dependencies = [ - "cranelift-codegen 0.68.0", - "log", - "smallvec", - "target-lexicon 0.11.2", -] - -[[package]] -name = "cranelift-native" -version = "0.67.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7054533ae1fc2048c1a6110bdf8f4314b77c60329ec6a7df79d2cfb84e3dcc1c" -dependencies = [ - "cranelift-codegen 0.67.0", - "raw-cpuid", - "target-lexicon 0.11.2", -] - -[[package]] -name = "cranelift-wasm" -version = "0.67.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7aee0e0b68eba99f99a4923212d97aca9e44655ca8246f07fffe11236109b0d0" -dependencies = [ - "cranelift-codegen 0.67.0", - "cranelift-entity 0.67.0", - "cranelift-frontend 0.67.0", - "log", - "serde", - "thiserror", - "wasmparser 0.59.0", -] - -[[package]] -name = "crc32fast" -version = "1.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "81156fece84ab6a9f2afdb109ce3ae577e42b1228441eded99bd77f627953b1a" -dependencies = [ - "cfg-if 1.0.0", -] - -[[package]] -name = "crossbeam-channel" -version = "0.5.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "06ed27e177f16d65f0f0c22a213e17c696ace5dd64b14258b52f9417ccb52db4" -dependencies = [ - "cfg-if 1.0.0", - "crossbeam-utils", -] - -[[package]] -name = "crossbeam-deque" -version = "0.8.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "94af6efb46fef72616855b036a624cf27ba656ffc9be1b9a3c931cfc7749a9a9" -dependencies = [ - "cfg-if 1.0.0", - "crossbeam-epoch", - "crossbeam-utils", -] - -[[package]] -name = "crossbeam-epoch" -version = "0.9.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2584f639eb95fea8c798496315b297cf81b9b58b6d30ab066a75455333cf4b12" -dependencies = [ - "cfg-if 1.0.0", - "crossbeam-utils", - "lazy_static", - "memoffset 0.6.3", - "scopeguard", -] - -[[package]] -name = "crossbeam-utils" -version = "0.8.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e7e9d99fa91428effe99c5c6d4634cdeba32b8cf784fc428a2a687f61a952c49" -dependencies = [ - "autocfg", - "cfg-if 1.0.0", - "lazy_static", -] - -[[package]] -name = "crunchy" -version = "0.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7a81dae078cea95a014a339291cec439d2f232ebe854a9d672b796c6afafa9b7" - -[[package]] -name = "crypto-mac" -version = "0.8.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b584a330336237c1eecd3e94266efb216c56ed91225d634cb2991c5f3fd1aeab" -dependencies = [ - "generic-array 0.14.4", - "subtle", -] - -[[package]] -name = "curve25519-dalek" -version = "3.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "639891fde0dbea823fc3d798a0fdf9d2f9440a42d64a78ab3488b0ca025117b3" -dependencies = [ - "byteorder", - "digest 0.9.0", - "rand_core 0.5.1", - "subtle", - "zeroize", -] - -[[package]] -name = "darling" -version = "0.10.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0d706e75d87e35569db781a9b5e2416cff1236a47ed380831f959382ccd5f858" -dependencies = [ - "darling_core 0.10.2", - "darling_macro 0.10.2", -] - -[[package]] -name = "darling" -version = "0.12.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5f2c43f534ea4b0b049015d00269734195e6d3f0f6635cb692251aca6f9f8b3c" -dependencies = [ - "darling_core 0.12.4", - "darling_macro 0.12.4", -] - -[[package]] -name = "darling_core" -version = "0.10.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f0c960ae2da4de88a91b2d920c2a7233b400bc33cb28453a2987822d8392519b" -dependencies = [ - "fnv", - "ident_case", - "proc-macro2", - "quote", - "strsim 0.9.3", - "syn", -] - -[[package]] -name = "darling_core" -version = "0.12.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8e91455b86830a1c21799d94524df0845183fa55bafd9aa137b01c7d1065fa36" -dependencies = [ - "fnv", - "ident_case", - "proc-macro2", - "quote", - "strsim 0.10.0", - "syn", -] - -[[package]] -name = "darling_macro" -version = "0.10.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d9b5a2f4ac4969822c62224815d069952656cadc7084fdca9751e6d959189b72" -dependencies = [ - "darling_core 0.10.2", - "quote", - "syn", -] - -[[package]] -name = "darling_macro" -version = "0.12.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "29b5acf0dea37a7f66f7b25d2c5e93fd46f8f6968b1a5d7a3e02e97768afc95a" -dependencies = [ - "darling_core 0.12.4", - "quote", - "syn", -] - -[[package]] -name = "derive_more" -version = "0.99.13" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f82b1b72f1263f214c0f823371768776c4f5841b942c9883aa8e5ec584fd0ba6" -dependencies = [ - "convert_case", - "proc-macro2", - "quote", - "syn", -] - -[[package]] -name = "digest" -version = "0.8.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f3d0c8c8752312f9713efd397ff63acb9f85585afbf179282e720e7704954dd5" -dependencies = [ - "generic-array 0.12.4", -] - -[[package]] -name = "digest" -version = "0.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d3dd60d1080a57a05ab032377049e0591415d2b31afd7028356dbf3cc6dcb066" -dependencies = [ - "generic-array 0.14.4", -] - -[[package]] -name = "dynasm" -version = "1.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3d7d1242462849390bb2ad38aeed769499f1afc7383affa2ab0c1baa894c0200" -dependencies = [ - "bitflags", - "byteorder", - "lazy_static", - "proc-macro-error", - "proc-macro2", - "quote", - "syn", -] - -[[package]] -name = "dynasmrt" -version = "1.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c1dd4d1d5ca12258cef339a57a7643e8b233a42dea9bb849630ddd9dd7726aa9" -dependencies = [ - "byteorder", - "dynasm", - "memmap2", -] - -[[package]] -name = "easy-ext" -version = "0.2.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "62473f23f4a15690bd64ea37269266ba7b273e7c26b302aef68d3968ae9262a0" -dependencies = [ - "quote", - "syn", -] - -[[package]] -name = "ed25519" -version = "1.0.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "37c66a534cbb46ab4ea03477eae19d5c22c01da8258030280b7bd9d8433fb6ef" -dependencies = [ - "signature", -] - -[[package]] -name = "ed25519-dalek" -version = "1.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c762bae6dcaf24c4c84667b8579785430908723d5c889f469d76a41d59cc7a9d" -dependencies = [ - "curve25519-dalek", - "ed25519", - "rand 0.7.3", - "serde", - "sha2", - "zeroize", -] - -[[package]] -name = "either" -version = "1.6.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e78d4f1cc4ae33bbfc157ed5d5a5ef3bc29227303d595861deb238fcec4e9457" - -[[package]] -name = "elastic-array" -version = "0.11.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0d63720ea2bc2e1b79f7aa044d9dc0b825f9ccb6930b32120f8fb9e873aa84bc" -dependencies = [ - "heapsize", -] - -[[package]] -name = "enumset" -version = "1.0.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fbd795df6708a599abf1ee10eacc72efd052b7a5f70fdf0715e4d5151a6db9c3" -dependencies = [ - "enumset_derive", -] - -[[package]] -name = "enumset_derive" -version = "0.5.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e19c52f9ec503c8a68dc04daf71a04b07e690c32ab1a8b68e33897f255269d47" -dependencies = [ - "darling 0.12.4", - "proc-macro2", - "quote", - "syn", -] - -[[package]] -name = "errno" -version = "0.2.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fa68f2fb9cae9d37c9b2b3584aba698a2e97f72d7aef7b9f7aa71d8b54ce46fe" -dependencies = [ - "errno-dragonfly", - "libc", - "winapi", -] - -[[package]] -name = "errno-dragonfly" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "14ca354e36190500e1e1fb267c647932382b54053c50b14970856c0b00a35067" -dependencies = [ - "gcc", - "libc", -] - -[[package]] -name = "ethbloom" -version = "0.11.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "779864b9c7f7ead1f092972c3257496c6a84b46dba2ce131dd8a282cb2cc5972" -dependencies = [ - "crunchy", - "fixed-hash", - "impl-rlp", - "impl-serde", - "tiny-keccak", -] - -[[package]] -name = "ethereum-types" -version = "0.11.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f64b5df66a228d85e4b17e5d6c6aa43b0310898ffe8a85988c4c032357aaabfd" -dependencies = [ - "ethbloom", - "fixed-hash", - "impl-rlp", - "impl-serde", - "primitive-types", - "uint", -] - -[[package]] -name = "event-listener" -version = "2.5.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f7531096570974c3a9dcf9e4b8e1cede1ec26cf5046219fb3b9d897503b9be59" - -[[package]] -name = "fallible-iterator" -version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4443176a9f2c162692bd3d352d745ef9413eec5782a80d8fd6f8a1ac692a07f7" - -[[package]] -name = "fixed-hash" -version = "0.7.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cfcf0ed7fe52a17a03854ec54a9f76d6d84508d1c0e66bc1793301c73fc8493c" -dependencies = [ - "byteorder", - "rand 0.8.3", - "rustc-hex", - "static_assertions", -] - -[[package]] -name = "fnv" -version = "1.0.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" - -[[package]] -name = "form_urlencoded" -version = "1.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5fc25a87fa4fd2094bffb06925852034d90a17f0d1e05197d4956d3555752191" -dependencies = [ - "matches", - "percent-encoding", -] - -[[package]] -name = "fs_extra" -version = "1.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2022715d62ab30faffd124d40b76f4134a550a87792276512b18d63272333394" - -[[package]] -name = "ft-lockup" -version = "1.0.0" -dependencies = [ - "near-contract-standards", - "near-sdk", - "near-sdk-sim", - "uint", -] - -[[package]] -name = "funty" -version = "1.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fed34cd105917e91daa4da6b3728c47b068749d6a62c59811f06ed2ac71d9da7" - -[[package]] -name = "futures" -version = "0.3.14" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a9d5813545e459ad3ca1bff9915e9ad7f1a47dc6a91b627ce321d5863b7dd253" -dependencies = [ - "futures-channel", - "futures-core", - "futures-executor", - "futures-io", - "futures-sink", - "futures-task", - "futures-util", -] - -[[package]] -name = "futures-channel" -version = "0.3.14" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ce79c6a52a299137a6013061e0cf0e688fce5d7f1bc60125f520912fdb29ec25" -dependencies = [ - "futures-core", - "futures-sink", -] - -[[package]] -name = "futures-core" -version = "0.3.14" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "098cd1c6dda6ca01650f1a37a794245eb73181d0d4d4e955e2f3c37db7af1815" - -[[package]] -name = "futures-executor" -version = "0.3.14" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "10f6cb7042eda00f0049b1d2080aa4b93442997ee507eb3828e8bd7577f94c9d" -dependencies = [ - "futures-core", - "futures-task", - "futures-util", -] - -[[package]] -name = "futures-io" -version = "0.3.14" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "365a1a1fb30ea1c03a830fdb2158f5236833ac81fa0ad12fe35b29cddc35cb04" - -[[package]] -name = "futures-macro" -version = "0.3.14" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "668c6733a182cd7deb4f1de7ba3bf2120823835b3bcfbeacf7d2c4a773c1bb8b" -dependencies = [ - "proc-macro-hack", - "proc-macro2", - "quote", - "syn", -] - -[[package]] -name = "futures-sink" -version = "0.3.14" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5c5629433c555de3d82861a7a4e3794a4c40040390907cfbfd7143a92a426c23" - -[[package]] -name = "futures-task" -version = "0.3.14" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ba7aa51095076f3ba6d9a1f702f74bd05ec65f555d70d2033d55ba8d69f581bc" - -[[package]] -name = "futures-util" -version = "0.3.14" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3c144ad54d60f23927f0a6b6d816e4271278b64f005ad65e4e35291d2de9c025" -dependencies = [ - "futures-channel", - "futures-core", - "futures-io", - "futures-macro", - "futures-sink", - "futures-task", - "memchr", - "pin-project-lite", - "pin-utils", - "proc-macro-hack", - "proc-macro-nested", - "slab", -] - -[[package]] -name = "gcc" -version = "0.3.55" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8f5f3913fa0bfe7ee1fd8248b6b9f42a5af4b9d65ec2dd2c3c26132b950ecfc2" - -[[package]] -name = "generic-array" -version = "0.12.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ffdf9f34f1447443d37393cc6c2b8313aebddcd96906caf34e54c68d8e57d7bd" -dependencies = [ - "typenum", -] - -[[package]] -name = "generic-array" -version = "0.14.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "501466ecc8a30d1d3b7fc9229b122b2ce8ed6e9d9223f1138d4babb253e51817" -dependencies = [ - "typenum", - "version_check", -] - -[[package]] -name = "getrandom" -version = "0.1.16" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8fc3cb4d91f53b50155bdcfd23f6a4c39ae1969c2ae85982b135750cccaf5fce" -dependencies = [ - "cfg-if 1.0.0", - "libc", - "wasi 0.9.0+wasi-snapshot-preview1", -] - -[[package]] -name = "getrandom" -version = "0.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c9495705279e7140bf035dde1f6e750c162df8b625267cd52cc44e0b156732c8" -dependencies = [ - "cfg-if 1.0.0", - "libc", - "wasi 0.10.2+wasi-snapshot-preview1", -] - -[[package]] -name = "gimli" -version = "0.21.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bcc8e0c9bce37868955864dbecd2b1ab2bdf967e6f28066d65aaac620444b65c" -dependencies = [ - "fallible-iterator", - "indexmap", - "stable_deref_trait", -] - -[[package]] -name = "gimli" -version = "0.22.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "aaf91faf136cb47367fa430cd46e37a788775e7fa104f8b4bcb3861dc389b724" -dependencies = [ - "fallible-iterator", - "indexmap", - "stable_deref_trait", -] - -[[package]] -name = "gimli" -version = "0.23.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f6503fe142514ca4799d4c26297c4248239fe8838d827db6bd6065c6ed29a6ce" - -[[package]] -name = "glob" -version = "0.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9b919933a397b79c37e33b77bb2aa3dc8eb6e165ad809e58ff75bc7db2e34574" - -[[package]] -name = "hashbrown" -version = "0.9.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d7afe4a420e3fe79967a00898cc1f4db7c8a49a9333a29f8a4bd76a253d5cd04" -dependencies = [ - "ahash", -] - -[[package]] -name = "heapsize" -version = "0.4.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1679e6ea370dee694f91f1dc469bf94cf8f52051d147aec3e1f9497c6fc22461" -dependencies = [ - "winapi", -] - -[[package]] -name = "heck" -version = "0.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "87cbf45460356b7deeb5e3415b5563308c0a9b057c85e12b06ad551f98d0a6ac" -dependencies = [ - "unicode-segmentation", -] - -[[package]] -name = "hermit-abi" -version = "0.1.18" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "322f4de77956e22ed0e5032c359a0f1273f1f7f0d79bfa3b8ffbc730d7fbcc5c" -dependencies = [ - "libc", -] - -[[package]] -name = "hex" -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 = "idna" -version = "0.2.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "418a0a6fab821475f634efe3ccc45c013f742efe03d853e8d3355d5cb850ecf8" -dependencies = [ - "matches", - "unicode-bidi", - "unicode-normalization", -] - -[[package]] -name = "impl-codec" -version = "0.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "df170efa359aebdd5cb7fe78edcc67107748e4737bdca8a8fb40d15ea7a877ed" -dependencies = [ - "parity-scale-codec", -] - -[[package]] -name = "impl-rlp" -version = "0.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f28220f89297a075ddc7245cd538076ee98b01f2a9c23a53a4f1105d5a322808" -dependencies = [ - "rlp", -] - -[[package]] -name = "impl-serde" -version = "0.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b47ca4d2b6931707a55fce5cf66aff80e2178c8b63bbb4ecb5695cbc870ddf6f" -dependencies = [ - "serde", -] - -[[package]] -name = "indexmap" -version = "1.6.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "824845a0bf897a9042383849b02c1bc219c2383772efcd5c6f9766fa4b81aef3" -dependencies = [ - "autocfg", - "hashbrown", - "serde", -] - -[[package]] -name = "instant" -version = "0.1.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "61124eeebbd69b8190558df225adf7e4caafce0d743919e5d6b19652314ec5ec" -dependencies = [ - "cfg-if 1.0.0", -] - -[[package]] -name = "itoa" -version = "0.4.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dd25036021b0de88a0aff6b850051563c6516d0bf53f8638938edbb9de732736" - -[[package]] -name = "jemalloc-sys" -version = "0.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0d3b9f3f5c9b31aa0f5ed3260385ac205db665baa41d49bb8338008ae94ede45" -dependencies = [ - "cc", - "fs_extra", - "libc", -] - -[[package]] -name = "jemallocator" -version = "0.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "43ae63fcfc45e99ab3d1b29a46782ad679e98436c3169d15a167a1108a724b69" -dependencies = [ - "jemalloc-sys", - "libc", -] - -[[package]] -name = "jobserver" -version = "0.1.22" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "972f5ae5d1cb9c6ae417789196c803205313edde988685da5e3aae0827b9e7fd" -dependencies = [ - "libc", -] - -[[package]] -name = "keccak" -version = "0.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "67c21572b4949434e4fc1e1978b99c5f77064153c59d998bf13ecd96fb5ecba7" - -[[package]] -name = "lazy-static-include" -version = "3.0.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "72c8cc0286c6d5fc53b3726f9763cc6468b4694f1ea8d78bd01e6600d117f73c" -dependencies = [ - "lazy_static", - "slash-formatter", - "syn", -] - -[[package]] -name = "lazy_static" -version = "1.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" - -[[package]] -name = "lazycell" -version = "1.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "830d08ce1d1d941e6b30645f1a0eb5643013d835ce3779a5fc208261dbe10f55" - -[[package]] -name = "leb128" -version = "0.2.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3576a87f2ba00f6f106fdfcd16db1d698d648a26ad8e0573cad8537c3c362d2a" - -[[package]] -name = "libc" -version = "0.2.94" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "18794a8ad5b29321f790b55d93dfba91e125cb1a9edbd4f8e3150acc771c1a5e" - -[[package]] -name = "libloading" -version = "0.6.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "351a32417a12d5f7e82c368a66781e307834dae04c6ce0cd4456d52989229883" -dependencies = [ - "cfg-if 1.0.0", - "winapi", -] - -[[package]] -name = "libloading" -version = "0.7.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6f84d96438c15fcd6c3f244c8fce01d1e2b9c6b5623e9c711dc9286d8fc92d6a" -dependencies = [ - "cfg-if 1.0.0", - "winapi", -] - -[[package]] -name = "librocksdb-sys" -version = "6.17.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5da125e1c0f22c7cae785982115523a0738728498547f415c9054cb17c7e89f9" -dependencies = [ - "bindgen", - "cc", - "glob", - "libc", -] - -[[package]] -name = "lock_api" -version = "0.3.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c4da24a77a3d8a6d4862d95f72e6fdb9c09a643ecdb402d754004a557f2bec75" -dependencies = [ - "scopeguard", -] - -[[package]] -name = "lock_api" -version = "0.4.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5a3c91c24eae6777794bb1997ad98bbb87daf92890acab859f7eaa4320333176" -dependencies = [ - "scopeguard", -] - -[[package]] -name = "log" -version = "0.4.14" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "51b9bbe6c47d51fc3e1a9b945965946b4c44142ab8792c50835a980d362c2710" -dependencies = [ - "cfg-if 1.0.0", -] - -[[package]] -name = "mach" -version = "0.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b823e83b2affd8f40a9ee8c29dbc56404c1e34cd2710921f2801e2cf29527afa" -dependencies = [ - "libc", -] - -[[package]] -name = "matches" -version = "0.1.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7ffc5c5338469d4d3ea17d269fa8ea3512ad247247c30bd2df69e68309ed0a08" - -[[package]] -name = "memchr" -version = "2.3.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0ee1c47aaa256ecabcaea351eae4a9b01ef39ed810004e298d2511ed284b1525" - -[[package]] -name = "memmap" -version = "0.7.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6585fd95e7bb50d6cc31e20d4cf9afb4e2ba16c5846fc76793f11218da9c475b" -dependencies = [ - "libc", - "winapi", -] - -[[package]] -name = "memmap2" -version = "0.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "397d1a6d6d0563c0f5462bbdae662cf6c784edf5e828e40c7257f85d82bf56dd" -dependencies = [ - "libc", -] - -[[package]] -name = "memoffset" -version = "0.5.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "043175f069eda7b85febe4a74abbaeff828d9f8b448515d3151a14a3542811aa" -dependencies = [ - "autocfg", -] - -[[package]] -name = "memoffset" -version = "0.6.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f83fb6581e8ed1f85fd45c116db8405483899489e38406156c25eb743554361d" -dependencies = [ - "autocfg", -] - -[[package]] -name = "memory_units" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8452105ba047068f40ff7093dd1d9da90898e63dd61736462e9cdda6a90ad3c3" - -[[package]] -name = "miniz_oxide" -version = "0.4.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a92518e98c078586bc6c934028adcca4c92a53d6a958196de835170a01d84e4b" -dependencies = [ - "adler", - "autocfg", -] - -[[package]] -name = "more-asserts" -version = "0.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0debeb9fcf88823ea64d64e4a815ab1643f33127d995978e099942ce38f25238" - -[[package]] -name = "near-contract-standards" -version = "3.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d5837ffd278eeedc4f97104586b3851ed9e7c32449de8b54e8d752f22498588e" -dependencies = [ - "near-sdk", -] - -[[package]] -name = "near-crypto" -version = "0.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bb14bec070cfd808438712cda5d54703001b9cf1196c8afaeadc9514e06d00a3" -dependencies = [ - "arrayref", - "blake2", - "borsh", - "bs58", - "c2-chacha", - "curve25519-dalek", - "derive_more", - "ed25519-dalek", - "lazy_static", - "libc", - "parity-secp256k1", - "rand 0.7.3", - "rand_core 0.5.1", - "serde", - "serde_json", - "subtle", - "thiserror", -] - -[[package]] -name = "near-metrics" -version = "0.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d7b9dcbc960db15fcec85bcb29a78b57e4005b7b1a0afc70a26a97453862528e" -dependencies = [ - "lazy_static", - "log", - "prometheus", -] - -[[package]] -name = "near-pool" -version = "0.1.0-pre.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bde79472f7cfc0675733b65f79f9e50c20bfbb9806298ab2872916869a45dccd" -dependencies = [ - "borsh", - "near-crypto", - "near-primitives", - "rand 0.7.3", -] - -[[package]] -name = "near-primitives" -version = "0.1.0-pre.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "75ed2263518ca67a3c158c144813832fd96f48ab239494bb9d7793d315f31417" -dependencies = [ - "base64", - "borsh", - "bs58", - "byteorder", - "chrono", - "derive_more", - "easy-ext", - "hex", - "jemallocator", - "lazy_static", - "near-crypto", - "near-primitives-core", - "near-rpc-error-macro", - "near-vm-errors", - "num-rational", - "primitive-types", - "rand 0.7.3", - "reed-solomon-erasure", - "regex", - "serde", - "serde_json", - "sha2", - "smart-default", - "validator", -] - -[[package]] -name = "near-primitives-core" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c2b3fb5acf3a494aed4e848446ef2d6ebb47dbe91c681105d4d1786c2ee63e52" -dependencies = [ - "base64", - "borsh", - "bs58", - "derive_more", - "hex", - "lazy_static", - "num-rational", - "serde", - "serde_json", - "sha2", -] - -[[package]] -name = "near-rpc-error-core" -version = "0.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ffa8dbf8437a28ac40fcb85859ab0d0b8385013935b000c7a51ae79631dd74d9" -dependencies = [ - "proc-macro2", - "quote", - "serde", - "serde_json", - "syn", -] - -[[package]] -name = "near-rpc-error-macro" -version = "0.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0c6111d713e90c7c551dee937f4a06cb9ea2672243455a4454cc7566387ba2d9" -dependencies = [ - "near-rpc-error-core", - "proc-macro2", - "quote", - "serde", - "serde_json", - "syn", -] - -[[package]] -name = "near-runtime" -version = "4.0.0-pre.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2e4c0a4cd2ee5ccbc1fd5d492180ebf33ac1159d721b2e0c58c11953131fb449" -dependencies = [ - "borsh", - "byteorder", - "ethereum-types", - "hex", - "lazy_static", - "log", - "near-crypto", - "near-metrics", - "near-primitives", - "near-runtime-utils", - "near-store", - "near-vm-errors", - "near-vm-logic", - "near-vm-runner", - "num-bigint", - "num-rational", - "num-traits", - "rand 0.7.3", - "serde", - "serde_json", -] - -[[package]] -name = "near-runtime-utils" -version = "4.0.0-pre.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a48d80c4ca1d4cf99bc16490e1e3d49826c150dfc4410ac498918e45c7d98e07" -dependencies = [ - "lazy_static", - "regex", -] - -[[package]] -name = "near-sdk" -version = "3.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c7383e242d3e07bf0951e8589d6eebd7f18bb1c1fc5fbec3fad796041a6aebd1" -dependencies = [ - "base64", - "borsh", - "bs58", - "near-primitives-core", - "near-sdk-macros", - "near-vm-logic", - "serde", - "serde_json", - "wee_alloc", -] - -[[package]] -name = "near-sdk-core" -version = "3.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "284a78d9eb8eda58330462fa0023a6d7014c941df1f0387095e7dfd1dc0f2bce" -dependencies = [ - "Inflector", - "proc-macro2", - "quote", - "syn", -] - -[[package]] -name = "near-sdk-macros" -version = "3.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2037337438f97d1ce5f7c896cf229dc56dacd5c01142d1ef95a7d778cde6ce7d" -dependencies = [ - "near-sdk-core", - "proc-macro2", - "quote", - "syn", -] - -[[package]] -name = "near-sdk-sim" -version = "3.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6f9be539455733f5cff63febaee07dcdc48c0a9940856257a1989e84a5552d7a" -dependencies = [ - "funty", - "lazy-static-include", - "near-crypto", - "near-pool", - "near-primitives", - "near-runtime", - "near-sdk", - "near-store", - "near-vm-logic", -] - -[[package]] -name = "near-store" -version = "0.1.0-pre.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "07e880d1d9a4ca5a1ca1ea0366fd80e295465f90cd0c1209f45d6d0b7a443ed4" -dependencies = [ - "borsh", - "byteorder", - "cached", - "derive_more", - "elastic-array", - "lazy_static", - "near-crypto", - "near-primitives", - "num_cpus", - "rand 0.7.3", - "rocksdb", - "serde", - "serde_json", - "strum", -] - -[[package]] -name = "near-vm-errors" -version = "4.0.0-pre.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e281d8730ed8cb0e3e69fb689acee6b93cdb43824cd69a8ffd7e1bfcbd1177d7" -dependencies = [ - "borsh", - "hex", - "near-rpc-error-macro", - "serde", -] - -[[package]] -name = "near-vm-logic" -version = "4.0.0-pre.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e11cb28a2d07f37680efdaf860f4c9802828c44fc50c08009e7884de75d982c5" -dependencies = [ - "base64", - "borsh", - "bs58", - "byteorder", - "near-primitives-core", - "near-runtime-utils", - "near-vm-errors", - "serde", - "sha2", - "sha3", -] - -[[package]] -name = "near-vm-runner" -version = "4.0.0-pre.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "93a66e94e12ec66a29674cc4efa975c280415aa0c944d7294cedbdb0c3858b48" -dependencies = [ - "anyhow", - "borsh", - "cached", - "log", - "near-primitives", - "near-vm-errors", - "near-vm-logic", - "parity-wasm", - "pwasm-utils", - "tracing", - "wasmer", - "wasmer-compiler-singlepass", - "wasmer-runtime-core-near", - "wasmer-runtime-near", - "wasmer-types", - "wasmtime", -] - -[[package]] -name = "nix" -version = "0.15.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3b2e0b4f3320ed72aaedb9a5ac838690a8047c7b275da22711fddff4f8a14229" -dependencies = [ - "bitflags", - "cc", - "cfg-if 0.1.10", - "libc", - "void", -] - -[[package]] -name = "nom" -version = "5.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ffb4262d26ed83a1c0a33a38fe2bb15797329c85770da05e6b828ddb782627af" -dependencies = [ - "memchr", - "version_check", -] - -[[package]] -name = "num-bigint" -version = "0.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7d0a3d5e207573f948a9e5376662aa743a2ea13f7c50a554d7af443a73fbfeba" -dependencies = [ - "autocfg", - "num-integer", - "num-traits", -] - -[[package]] -name = "num-integer" -version = "0.1.44" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d2cc698a63b549a70bc047073d2949cce27cd1c7b0a4a862d08a8031bc2801db" -dependencies = [ - "autocfg", - "num-traits", -] - -[[package]] -name = "num-rational" -version = "0.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "12ac428b1cb17fce6f731001d307d351ec70a6d202fc2e60f7d4c5e42d8f4f07" -dependencies = [ - "autocfg", - "num-bigint", - "num-integer", - "num-traits", - "serde", -] - -[[package]] -name = "num-traits" -version = "0.2.14" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9a64b1ec5cda2586e284722486d802acf1f7dbdc623e2bfc57e65ca1cd099290" -dependencies = [ - "autocfg", -] - -[[package]] -name = "num_cpus" -version = "1.13.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "05499f3756671c15885fee9034446956fff3f243d6077b91e5767df161f766b3" -dependencies = [ - "hermit-abi", - "libc", -] - -[[package]] -name = "object" -version = "0.21.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "37fd5004feb2ce328a52b0b3d01dbf4ffff72583493900ed15f22d4111c51693" -dependencies = [ - "crc32fast", - "indexmap", - "wasmparser 0.57.0", -] - -[[package]] -name = "object" -version = "0.22.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8d3b63360ec3cb337817c2dbd47ab4a0f170d285d8e5a2064600f3def1402397" -dependencies = [ - "crc32fast", - "indexmap", -] - -[[package]] -name = "object" -version = "0.23.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a9a7ab5d64814df0fe4a4b5ead45ed6c5f181ee3ff04ba344313a6c80446c5d4" - -[[package]] -name = "once_cell" -version = "1.7.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "af8b08b04175473088b46763e51ee54da5f9a164bc162f615b91bc179dbf15a3" - -[[package]] -name = "opaque-debug" -version = "0.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "624a8340c38c1b80fd549087862da4ba43e08858af025b236e509b6649fc13d5" - -[[package]] -name = "page_size" -version = "0.4.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eebde548fbbf1ea81a99b128872779c437752fb99f217c45245e1a61dcd9edcd" -dependencies = [ - "libc", - "winapi", -] - -[[package]] -name = "parity-scale-codec" -version = "2.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e0f518afaa5a47d0d6386229b0a6e01e86427291d643aa4cabb4992219f504f8" -dependencies = [ - "arrayvec 0.7.0", - "bitvec", - "byte-slice-cast", - "serde", -] - -[[package]] -name = "parity-secp256k1" -version = "0.7.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4fca4f82fccae37e8bbdaeb949a4a218a1bbc485d11598f193d2a908042e5fc1" -dependencies = [ - "arrayvec 0.5.2", - "cc", - "cfg-if 0.1.10", - "rand 0.7.3", -] - -[[package]] -name = "parity-wasm" -version = "0.41.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ddfc878dac00da22f8f61e7af3157988424567ab01d9920b962ef7dcbd7cd865" - -[[package]] -name = "parking_lot" -version = "0.10.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d3a704eb390aafdc107b0e392f56a82b668e3a71366993b5340f5833fd62505e" -dependencies = [ - "lock_api 0.3.4", - "parking_lot_core 0.7.2", -] - -[[package]] -name = "parking_lot" -version = "0.11.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6d7744ac029df22dca6284efe4e898991d28e3085c706c972bcd7da4a27a15eb" -dependencies = [ - "instant", - "lock_api 0.4.3", - "parking_lot_core 0.8.3", -] - -[[package]] -name = "parking_lot_core" -version = "0.7.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d58c7c768d4ba344e3e8d72518ac13e259d7c7ade24167003b8488e10b6740a3" -dependencies = [ - "cfg-if 0.1.10", - "cloudabi", - "libc", - "redox_syscall 0.1.57", - "smallvec", - "winapi", -] - -[[package]] -name = "parking_lot_core" -version = "0.8.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fa7a782938e745763fe6907fc6ba86946d72f49fe7e21de074e08128a99fb018" -dependencies = [ - "cfg-if 1.0.0", - "instant", - "libc", - "redox_syscall 0.2.7", - "smallvec", - "winapi", -] - -[[package]] -name = "peeking_take_while" -version = "0.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "19b17cddbe7ec3f8bc800887bab5e717348c95ea2ca0b1bf0837fb964dc67099" - -[[package]] -name = "percent-encoding" -version = "2.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d4fd5641d01c8f18a23da7b6fe29298ff4b55afcccdf78973b24cf3175fee32e" - -[[package]] -name = "pin-project-lite" -version = "0.2.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dc0e1f259c92177c30a4c9d177246edd0a3568b25756a977d0632cf8fa37e905" - -[[package]] -name = "pin-utils" -version = "0.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" - -[[package]] -name = "ppv-lite86" -version = "0.2.10" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ac74c624d6b2d21f425f752262f42188365d7b8ff1aff74c82e45136510a4857" - -[[package]] -name = "primitive-types" -version = "0.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2415937401cb030a2a0a4d922483f945fa068f52a7dbb22ce0fe5f2b6f6adace" -dependencies = [ - "fixed-hash", - "impl-codec", - "impl-rlp", - "impl-serde", - "uint", -] - -[[package]] -name = "proc-macro-crate" -version = "0.1.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1d6ea3c4595b96363c13943497db34af4460fb474a95c43f4446ad341b8c9785" -dependencies = [ - "toml", -] - -[[package]] -name = "proc-macro-error" -version = "1.0.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "da25490ff9892aab3fcf7c36f08cfb902dd3e71ca0f9f9517bea02a73a5ce38c" -dependencies = [ - "proc-macro-error-attr", - "proc-macro2", - "quote", - "syn", - "version_check", -] - -[[package]] -name = "proc-macro-error-attr" -version = "1.0.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a1be40180e52ecc98ad80b184934baf3d0d29f979574e439af5a55274b35f869" -dependencies = [ - "proc-macro2", - "quote", - "version_check", -] - -[[package]] -name = "proc-macro-hack" -version = "0.5.19" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dbf0c48bc1d91375ae5c3cd81e3722dff1abcf81a30960240640d223f59fe0e5" - -[[package]] -name = "proc-macro-nested" -version = "0.1.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bc881b2c22681370c6a780e47af9840ef841837bc98118431d4e1868bd0c1086" - -[[package]] -name = "proc-macro2" -version = "1.0.26" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a152013215dca273577e18d2bf00fa862b89b24169fb78c4c95aeb07992c9cec" -dependencies = [ - "unicode-xid", -] - -[[package]] -name = "prometheus" -version = "0.11.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c8425533e7122f0c3cc7a37e6244b16ad3a2cc32ae7ac6276e2a75da0d9c200d" -dependencies = [ - "cfg-if 1.0.0", - "fnv", - "lazy_static", - "parking_lot 0.11.1", - "protobuf", - "regex", - "thiserror", -] - -[[package]] -name = "protobuf" -version = "2.23.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "45604fc7a88158e7d514d8e22e14ac746081e7a70d7690074dd0029ee37458d6" - -[[package]] -name = "pwasm-utils" -version = "0.12.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4f7a12f176deee919f4ba55326ee17491c8b707d0987aed822682c821b660192" -dependencies = [ - "byteorder", - "log", - "parity-wasm", -] - -[[package]] -name = "quote" -version = "1.0.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c3d0b9745dc2debf507c8422de05d7226cc1f0644216dfdfead988f9b1ab32a7" -dependencies = [ - "proc-macro2", -] - -[[package]] -name = "radium" -version = "0.6.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "643f8f41a8ebc4c5dc4515c82bb8abd397b527fc20fd681b7c011c2aee5d44fb" - -[[package]] -name = "rand" -version = "0.7.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6a6b1679d49b24bbfe0c803429aa1874472f50d9b363131f0e89fc356b544d03" -dependencies = [ - "getrandom 0.1.16", - "libc", - "rand_chacha 0.2.2", - "rand_core 0.5.1", - "rand_hc 0.2.0", -] - -[[package]] -name = "rand" -version = "0.8.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0ef9e7e66b4468674bfcb0c81af8b7fa0bb154fa9f28eb840da5c447baeb8d7e" -dependencies = [ - "libc", - "rand_chacha 0.3.0", - "rand_core 0.6.2", - "rand_hc 0.3.0", -] - -[[package]] -name = "rand_chacha" -version = "0.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f4c8ed856279c9737206bf725bf36935d8666ead7aa69b52be55af369d193402" -dependencies = [ - "ppv-lite86", - "rand_core 0.5.1", -] - -[[package]] -name = "rand_chacha" -version = "0.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e12735cf05c9e10bf21534da50a147b924d555dc7a547c42e6bb2d5b6017ae0d" -dependencies = [ - "ppv-lite86", - "rand_core 0.6.2", -] - -[[package]] -name = "rand_core" -version = "0.5.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "90bde5296fc891b0cef12a6d03ddccc162ce7b2aff54160af9338f8d40df6d19" -dependencies = [ - "getrandom 0.1.16", -] - -[[package]] -name = "rand_core" -version = "0.6.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "34cf66eb183df1c5876e2dcf6b13d57340741e8dc255b48e40a26de954d06ae7" -dependencies = [ - "getrandom 0.2.2", -] - -[[package]] -name = "rand_hc" -version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ca3129af7b92a17112d59ad498c6f81eaf463253766b90396d39ea7a39d6613c" -dependencies = [ - "rand_core 0.5.1", -] - -[[package]] -name = "rand_hc" -version = "0.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3190ef7066a446f2e7f42e239d161e905420ccab01eb967c9eb27d21b2322a73" -dependencies = [ - "rand_core 0.6.2", -] - -[[package]] -name = "raw-cpuid" -version = "7.0.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "beb71f708fe39b2c5e98076204c3cc094ee5a4c12c4cdb119a2b72dc34164f41" -dependencies = [ - "bitflags", - "cc", - "rustc_version", -] - -[[package]] -name = "rayon" -version = "1.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8b0d8e0819fadc20c74ea8373106ead0600e3a67ef1fe8da56e39b9ae7275674" -dependencies = [ - "autocfg", - "crossbeam-deque", - "either", - "rayon-core", -] - -[[package]] -name = "rayon-core" -version = "1.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9ab346ac5921dc62ffa9f89b7a773907511cdfa5490c572ae9be1be33e8afa4a" -dependencies = [ - "crossbeam-channel", - "crossbeam-deque", - "crossbeam-utils", - "lazy_static", - "num_cpus", -] - -[[package]] -name = "redox_syscall" -version = "0.1.57" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "41cc0f7e4d5d4544e8861606a285bb08d3e70712ccc7d2b84d7c0ccfaf4b05ce" - -[[package]] -name = "redox_syscall" -version = "0.2.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "85dd92e586f7355c633911e11f77f3d12f04b1b1bd76a198bd34ae3af8341ef2" -dependencies = [ - "bitflags", -] - -[[package]] -name = "reed-solomon-erasure" -version = "4.0.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a415a013dd7c5d4221382329a5a3482566da675737494935cbbbcdec04662f9d" -dependencies = [ - "smallvec", -] - -[[package]] -name = "regalloc" -version = "0.0.30" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2041c2d34f6ff346d6f428974f03d8bf12679b0c816bb640dc5eb1d48848d8d1" -dependencies = [ - "log", - "rustc-hash", - "smallvec", -] - -[[package]] -name = "regalloc" -version = "0.0.31" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "571f7f397d61c4755285cd37853fe8e03271c243424a907415909379659381c5" -dependencies = [ - "log", - "rustc-hash", - "smallvec", -] - -[[package]] -name = "regex" -version = "1.4.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "957056ecddbeba1b26965114e191d2e8589ce74db242b6ea25fc4062427a5c19" -dependencies = [ - "aho-corasick", - "memchr", - "regex-syntax", -] - -[[package]] -name = "regex-syntax" -version = "0.6.23" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "24d5f089152e60f62d28b835fbff2cd2e8dc0baf1ac13343bef92ab7eed84548" - -[[package]] -name = "region" -version = "2.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "877e54ea2adcd70d80e9179344c97f93ef0dffd6b03e1f4529e6e83ab2fa9ae0" -dependencies = [ - "bitflags", - "libc", - "mach", - "winapi", -] - -[[package]] -name = "remove_dir_all" -version = "0.5.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3acd125665422973a33ac9d3dd2df85edad0f4ae9b00dafb1a05e43a9f5ef8e7" -dependencies = [ - "winapi", -] - -[[package]] -name = "rlp" -version = "0.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e54369147e3e7796c9b885c7304db87ca3d09a0a98f72843d532868675bbfba8" -dependencies = [ - "bytes", - "rustc-hex", -] - -[[package]] -name = "rocksdb" -version = "0.15.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "23d83c02c429044d58474eaf5ae31e062d0de894e21125b47437ec0edc1397e6" -dependencies = [ - "libc", - "librocksdb-sys", -] - -[[package]] -name = "rustc-demangle" -version = "0.1.18" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6e3bad0ee36814ca07d7968269dd4b7ec89ec2da10c4bb613928d3077083c232" - -[[package]] -name = "rustc-hash" -version = "1.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "08d43f7aa6b08d49f382cde6a7982047c3426db949b1424bc4b7ec9ae12c6ce2" - -[[package]] -name = "rustc-hex" -version = "2.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3e75f6a532d0fd9f7f13144f392b6ad56a32696bfcd9c78f797f16bbb6f072d6" - -[[package]] -name = "rustc_version" -version = "0.2.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "138e3e0acb6c9fb258b19b67cb8abd63c00679d2851805ea151465464fe9030a" -dependencies = [ - "semver", -] - -[[package]] -name = "ryu" -version = "1.0.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "71d301d4193d031abdd79ff7e3dd721168a9572ef3fe51a1517aba235bd8f86e" - -[[package]] -name = "scopeguard" -version = "1.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d29ab0c6d3fc0ee92fe66e2d99f700eab17a8d57d1c1d3b748380fb20baa78cd" - -[[package]] -name = "semver" -version = "0.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1d7eb9ef2c18661902cc47e535f9bc51b78acd254da71d375c2f6720d9a40403" -dependencies = [ - "semver-parser", -] - -[[package]] -name = "semver-parser" -version = "0.7.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "388a1df253eca08550bef6c72392cfe7c30914bf41df5269b68cbd6ff8f570a3" - -[[package]] -name = "serde" -version = "1.0.118" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "06c64263859d87aa2eb554587e2d23183398d617427327cf2b3d0ed8c69e4800" -dependencies = [ - "serde_derive", -] - -[[package]] -name = "serde-bench" -version = "0.0.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d733da87e79faaac25616e33d26299a41143fd4cd42746cbb0e91d8feea243fd" -dependencies = [ - "byteorder", - "serde", -] - -[[package]] -name = "serde_bytes" -version = "0.11.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "16ae07dd2f88a366f15bd0632ba725227018c69a1c8550a927324f8eb8368bb9" -dependencies = [ - "serde", -] - -[[package]] -name = "serde_derive" -version = "1.0.118" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c84d3526699cd55261af4b941e4e725444df67aa4f9e6a3564f18030d12672df" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] - -[[package]] -name = "serde_json" -version = "1.0.64" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "799e97dc9fdae36a5c8b8f2cae9ce2ee9fdce2058c57a93e6099d919fd982f79" -dependencies = [ - "indexmap", - "itoa", - "ryu", - "serde", -] - -[[package]] -name = "sha2" -version = "0.9.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fa827a14b29ab7f44778d14a88d3cb76e949c45083f7dbfa507d0cb699dc12de" -dependencies = [ - "block-buffer", - "cfg-if 1.0.0", - "cpuid-bool", - "digest 0.9.0", - "opaque-debug", -] - -[[package]] -name = "sha3" -version = "0.9.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f81199417d4e5de3f04b1e871023acea7389672c4135918f05aa9cbf2f2fa809" -dependencies = [ - "block-buffer", - "digest 0.9.0", - "keccak", - "opaque-debug", -] - -[[package]] -name = "shlex" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7fdf1b9db47230893d76faad238fd6097fd6d6a9245cd7a4d90dbd639536bbd2" - -[[package]] -name = "signature" -version = "1.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0f0242b8e50dd9accdd56170e94ca1ebd223b098eb9c83539a6e367d0f36ae68" - -[[package]] -name = "slab" -version = "0.4.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f173ac3d1a7e3b28003f40de0b5ce7fe2710f9b9dc3fc38664cebee46b3b6527" - -[[package]] -name = "slash-formatter" -version = "3.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8be5f0374e00ae49a7b6883830d98b24df8d5df6f217d546637a3a159c29a201" -dependencies = [ - "concat-with", -] - -[[package]] -name = "smallvec" -version = "1.6.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fe0f37c9e8f3c5a4a66ad655a93c74daac4ad00c441533bf5c6e7990bb42604e" - -[[package]] -name = "smart-default" -version = "0.6.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "133659a15339456eeeb07572eb02a91c91e9815e9cbc89566944d2c8d3efdbf6" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] - -[[package]] -name = "stable_deref_trait" -version = "1.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a8f112729512f8e442d81f95a8a7ddf2b7c6b8a1a6f509a95864142b30cab2d3" - -[[package]] -name = "static_assertions" -version = "1.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f" - -[[package]] -name = "strsim" -version = "0.9.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6446ced80d6c486436db5c078dde11a9f73d42b57fb273121e160b84f63d894c" - -[[package]] -name = "strsim" -version = "0.10.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623" - -[[package]] -name = "strum" -version = "0.20.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7318c509b5ba57f18533982607f24070a55d353e90d4cae30c467cdb2ad5ac5c" -dependencies = [ - "strum_macros", -] - -[[package]] -name = "strum_macros" -version = "0.20.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ee8bc6b87a5112aeeab1f4a9f7ab634fe6cbefc4850006df31267f4cfb9e3149" -dependencies = [ - "heck", - "proc-macro2", - "quote", - "syn", -] - -[[package]] -name = "subtle" -version = "2.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1e81da0851ada1f3e9d4312c704aa4f8806f0f9d69faaf8df2f3464b4a9437c2" - -[[package]] -name = "syn" -version = "1.0.57" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4211ce9909eb971f111059df92c45640aad50a619cf55cd76476be803c4c68e6" -dependencies = [ - "proc-macro2", - "quote", - "unicode-xid", -] - -[[package]] -name = "synstructure" -version = "0.12.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b834f2d66f734cb897113e34aaff2f1ab4719ca946f9a7358dba8f8064148701" -dependencies = [ - "proc-macro2", - "quote", - "syn", - "unicode-xid", -] - -[[package]] -name = "tap" -version = "1.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "55937e1799185b12863d447f42597ed69d9928686b8d88a1df17376a097d8369" - -[[package]] -name = "target-lexicon" -version = "0.10.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ab0e7238dcc7b40a7be719a25365910f6807bd864f4cce6b2e6b873658e2b19d" - -[[package]] -name = "target-lexicon" -version = "0.11.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "422045212ea98508ae3d28025bc5aaa2bd4a9cdaecd442a08da2ee620ee9ea95" - -[[package]] -name = "tempfile" -version = "3.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dac1c663cfc93810f88aed9b8941d48cabf856a1b111c29a40439018d870eb22" -dependencies = [ - "cfg-if 1.0.0", - "libc", - "rand 0.8.3", - "redox_syscall 0.2.7", - "remove_dir_all", - "winapi", -] - -[[package]] -name = "thiserror" -version = "1.0.24" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e0f4a65597094d4483ddaed134f409b2cb7c1beccf25201a9f73c719254fa98e" -dependencies = [ - "thiserror-impl", -] - -[[package]] -name = "thiserror-impl" -version = "1.0.24" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7765189610d8241a44529806d6fd1f2e0a08734313a35d5b3a556f92b381f3c0" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] - -[[package]] -name = "time" -version = "0.1.43" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ca8a50ef2360fbd1eeb0ecd46795a87a19024eb4b53c5dc916ca1fd95fe62438" -dependencies = [ - "libc", - "winapi", -] - -[[package]] -name = "tiny-keccak" -version = "2.0.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2c9d3793400a45f954c52e73d068316d76b6f4e36977e3fcebb13a2721e80237" -dependencies = [ - "crunchy", -] - -[[package]] -name = "tinyvec" -version = "1.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5b5220f05bb7de7f3f53c7c065e1199b3172696fe2db9f9c4d8ad9b4ee74c342" -dependencies = [ - "tinyvec_macros", -] - -[[package]] -name = "tinyvec_macros" -version = "0.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cda74da7e1a664f795bb1f8a87ec406fb89a02522cf6e50620d016add6dbbf5c" - -[[package]] -name = "toml" -version = "0.5.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a31142970826733df8241ef35dc040ef98c679ab14d7c3e54d827099b3acecaa" -dependencies = [ - "serde", -] - -[[package]] -name = "tracing" -version = "0.1.25" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "01ebdc2bb4498ab1ab5f5b73c5803825e60199229ccba0698170e3be0e7f959f" -dependencies = [ - "cfg-if 1.0.0", - "pin-project-lite", - "tracing-attributes", - "tracing-core", -] - -[[package]] -name = "tracing-attributes" -version = "0.1.15" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c42e6fa53307c8a17e4ccd4dc81cf5ec38db9209f59b222210375b54ee40d1e2" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] - -[[package]] -name = "tracing-core" -version = "0.1.17" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f50de3927f93d202783f4513cda820ab47ef17f624b03c096e86ef00c67e6b5f" -dependencies = [ - "lazy_static", -] - -[[package]] -name = "typenum" -version = "1.13.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "879f6906492a7cd215bfa4cf595b600146ccfac0c79bcbd1f3000162af5e8b06" - -[[package]] -name = "uint" -version = "0.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e11fe9a9348741cf134085ad57c249508345fe16411b3d7fb4ff2da2f1d6382e" -dependencies = [ - "byteorder", - "crunchy", - "hex", - "static_assertions", -] - -[[package]] -name = "unicode-bidi" -version = "0.3.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eeb8be209bb1c96b7c177c7420d26e04eccacb0eeae6b980e35fcb74678107e0" -dependencies = [ - "matches", -] - -[[package]] -name = "unicode-normalization" -version = "0.1.17" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "07fbfce1c8a97d547e8b5334978438d9d6ec8c20e38f56d4a4374d181493eaef" -dependencies = [ - "tinyvec", -] - -[[package]] -name = "unicode-segmentation" -version = "1.7.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bb0d2e7be6ae3a5fa87eed5fb451aff96f2573d2694942e40543ae0bbe19c796" - -[[package]] -name = "unicode-xid" -version = "0.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f7fe0bb3479651439c9112f72b6c505038574c9fbb575ed1bf3b797fa39dd564" - -[[package]] -name = "url" -version = "2.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9ccd964113622c8e9322cfac19eb1004a07e636c545f325da085d5cdde6f1f8b" -dependencies = [ - "form_urlencoded", - "idna", - "matches", - "percent-encoding", -] - -[[package]] -name = "validator" -version = "0.12.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "841d6937c33ec6039d8071bcf72933146b5bbe378d645d8fa59bdadabfc2a249" -dependencies = [ - "idna", - "lazy_static", - "regex", - "serde", - "serde_derive", - "serde_json", - "url", - "validator_types", -] - -[[package]] -name = "validator_types" -version = "0.12.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ad9680608df133af2c1ddd5eaf1ddce91d60d61b6bc51494ef326458365a470a" - -[[package]] -name = "version_check" -version = "0.9.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5fecdca9a5291cc2b8dcf7dc02453fee791a280f3743cb0905f8822ae463b3fe" - -[[package]] -name = "void" -version = "1.0.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6a02e4885ed3bc0f2de90ea6dd45ebcbb66dacffe03547fadbb0eeae2770887d" - -[[package]] -name = "wasi" -version = "0.9.0+wasi-snapshot-preview1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cccddf32554fecc6acb585f82a32a72e28b48f8c4c1883ddfeeeaa96f7d8e519" - -[[package]] -name = "wasi" -version = "0.10.2+wasi-snapshot-preview1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fd6fbd9a79829dd1ad0cc20627bf1ed606756a7f77edff7b66b7064f9cb327c6" - -[[package]] -name = "wasmer" -version = "1.0.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a70cfae554988d904d64ca17ab0e7cd652ee5c8a0807094819c1ea93eb9d6866" -dependencies = [ - "cfg-if 0.1.10", - "indexmap", - "more-asserts", - "target-lexicon 0.11.2", - "thiserror", - "wasmer-compiler", - "wasmer-compiler-cranelift", - "wasmer-derive", - "wasmer-engine", - "wasmer-engine-jit", - "wasmer-engine-native", - "wasmer-types", - "wasmer-vm", - "wat", - "winapi", -] - -[[package]] -name = "wasmer-compiler" -version = "1.0.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6b7732a9cab472bd921d5a0c422f45b3d03f62fa2c40a89e0770cef6d47e383e" -dependencies = [ - "enumset", - "serde", - "serde_bytes", - "smallvec", - "target-lexicon 0.11.2", - "thiserror", - "wasmer-types", - "wasmer-vm", - "wasmparser 0.65.0", -] - -[[package]] -name = "wasmer-compiler-cranelift" -version = "1.0.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "48cb9395f094e1d81534f4c5e330ed4cdb424e8df870d29ad585620284f5fddb" -dependencies = [ - "cranelift-codegen 0.68.0", - "cranelift-frontend 0.68.0", - "gimli 0.22.0", - "more-asserts", - "rayon", - "serde", - "smallvec", - "tracing", - "wasmer-compiler", - "wasmer-types", - "wasmer-vm", -] - -[[package]] -name = "wasmer-compiler-singlepass" -version = "1.0.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "426ae6ef0f606ca815510f3e2ef6f520e217514bfb7a664defe180b9a9e75d07" -dependencies = [ - "byteorder", - "dynasm", - "dynasmrt", - "lazy_static", - "more-asserts", - "rayon", - "serde", - "smallvec", - "wasmer-compiler", - "wasmer-types", - "wasmer-vm", -] - -[[package]] -name = "wasmer-derive" -version = "1.0.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d8b86dcd2c3efdb8390728a2b56f762db07789aaa5aa872a9dc776ba3a7912ed" -dependencies = [ - "proc-macro-error", - "proc-macro2", - "quote", - "syn", -] - -[[package]] -name = "wasmer-engine" -version = "1.0.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "efe4667d6bd888f26ae8062a63a9379fa697415b4b4e380f33832e8418fd71b5" -dependencies = [ - "backtrace", - "bincode", - "lazy_static", - "memmap2", - "more-asserts", - "rustc-demangle", - "serde", - "serde_bytes", - "target-lexicon 0.11.2", - "thiserror", - "wasmer-compiler", - "wasmer-types", - "wasmer-vm", -] - -[[package]] -name = "wasmer-engine-jit" -version = "1.0.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "26770be802888011b4a3072f2a282fc2faa68aa48c71b3db6252a3937a85f3da" -dependencies = [ - "bincode", - "cfg-if 0.1.10", - "region", - "serde", - "serde_bytes", - "wasmer-compiler", - "wasmer-engine", - "wasmer-types", - "wasmer-vm", - "winapi", -] - -[[package]] -name = "wasmer-engine-native" -version = "1.0.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2bb4083a6c69f2cd4b000b82a80717f37c6cc2e536aee3a8ffe9af3edc276a8b" -dependencies = [ - "bincode", - "cfg-if 0.1.10", - "leb128", - "libloading 0.6.7", - "serde", - "tempfile", - "tracing", - "wasmer-compiler", - "wasmer-engine", - "wasmer-object", - "wasmer-types", - "wasmer-vm", - "which", -] - -[[package]] -name = "wasmer-object" -version = "1.0.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "abf8e0c12b82ff81ebecd30d7e118be5fec871d6de885a90eeb105df0a769a7b" -dependencies = [ - "object 0.22.0", - "thiserror", - "wasmer-compiler", - "wasmer-types", -] - -[[package]] -name = "wasmer-runtime-core-near" -version = "0.17.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2390b0b8cf58615c2f8b454d9535e1aed1d14c510cc1ce1af806a1992ad53300" -dependencies = [ - "bincode", - "blake3", - "cc", - "digest 0.8.1", - "errno", - "hex", - "indexmap", - "lazy_static", - "libc", - "nix", - "page_size", - "parking_lot 0.10.2", - "rustc_version", - "serde", - "serde-bench", - "serde_bytes", - "serde_derive", - "smallvec", - "target-lexicon 0.10.0", - "wasmparser 0.51.4", - "winapi", -] - -[[package]] -name = "wasmer-runtime-near" -version = "0.17.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e6660e86bc7697fa29bab902214d5b33d394a990826c401b10816bcd285f938f" -dependencies = [ - "lazy_static", - "memmap", - "serde", - "serde_derive", - "wasmer-runtime-core-near", - "wasmer-singlepass-backend-near", -] - -[[package]] -name = "wasmer-singlepass-backend-near" -version = "0.17.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d3f23543ef8f59667be4945c22eb4b1a50a79ff340555f6f23354223d2695541" -dependencies = [ - "bincode", - "byteorder", - "dynasm", - "dynasmrt", - "lazy_static", - "libc", - "nix", - "serde", - "serde_derive", - "smallvec", - "wasmer-runtime-core-near", -] - -[[package]] -name = "wasmer-types" -version = "1.0.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c7f4ac28c2951cd792c18332f03da523ed06b170f5cf6bb5b1bdd7e36c2a8218" -dependencies = [ - "cranelift-entity 0.68.0", - "serde", - "thiserror", -] - -[[package]] -name = "wasmer-vm" -version = "1.0.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a7635ba0b6d2fd325f588d69a950ad9fa04dddbf6ad08b6b2a183146319bf6ae" -dependencies = [ - "backtrace", - "cc", - "cfg-if 0.1.10", - "indexmap", - "libc", - "memoffset 0.6.3", - "more-asserts", - "region", - "serde", - "thiserror", - "wasmer-types", - "winapi", -] - -[[package]] -name = "wasmparser" -version = "0.51.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "aeb1956b19469d1c5e63e459d29e7b5aa0f558d9f16fcef09736f8a265e6c10a" - -[[package]] -name = "wasmparser" -version = "0.57.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "32fddd575d477c6e9702484139cf9f23dcd554b06d185ed0f56c857dd3a47aa6" - -[[package]] -name = "wasmparser" -version = "0.59.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a950e6a618f62147fd514ff445b2a0b53120d382751960797f85f058c7eda9b9" - -[[package]] -name = "wasmparser" -version = "0.65.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "87cc2fe6350834b4e528ba0901e7aa405d78b89dc1fa3145359eb4de0e323fcf" - -[[package]] -name = "wasmtime" -version = "0.20.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "373b87ebd6721f4121e28eeaaa41943548c48bcca04ac9bb063004207e5e7d70" -dependencies = [ - "anyhow", - "backtrace", - "bincode", - "cfg-if 0.1.10", - "lazy_static", - "libc", - "log", - "region", - "rustc-demangle", - "serde", - "smallvec", - "target-lexicon 0.11.2", - "wasmparser 0.59.0", - "wasmtime-environ", - "wasmtime-jit", - "wasmtime-profiling", - "wasmtime-runtime", - "winapi", -] - -[[package]] -name = "wasmtime-cranelift" -version = "0.20.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "40c01df908e54d40bed80326ade122825d464888991beafd950d186f1be309c2" -dependencies = [ - "cranelift-codegen 0.67.0", - "cranelift-entity 0.67.0", - "cranelift-frontend 0.67.0", - "cranelift-wasm", - "wasmtime-environ", -] - -[[package]] -name = "wasmtime-debug" -version = "0.20.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "28962772f96fadb79dc7be5ade135ca55d2b0017a012f4869e2476a03abbe733" -dependencies = [ - "anyhow", - "gimli 0.21.0", - "more-asserts", - "object 0.21.1", - "target-lexicon 0.11.2", - "thiserror", - "wasmparser 0.59.0", - "wasmtime-environ", -] - -[[package]] -name = "wasmtime-environ" -version = "0.20.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9c0d7401bf253b7b1f426afd70d285bb23ea9a1c7605d6af788c95db5084edf5" -dependencies = [ - "anyhow", - "cfg-if 0.1.10", - "cranelift-codegen 0.67.0", - "cranelift-entity 0.67.0", - "cranelift-wasm", - "gimli 0.21.0", - "indexmap", - "log", - "more-asserts", - "serde", - "thiserror", - "wasmparser 0.59.0", -] - -[[package]] -name = "wasmtime-jit" -version = "0.20.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8c838a108318e7c5a2201addb3d3b27a6ef3d142f0eb0addc815b9c2541e5db5" -dependencies = [ - "anyhow", - "cfg-if 0.1.10", - "cranelift-codegen 0.67.0", - "cranelift-entity 0.67.0", - "cranelift-frontend 0.67.0", - "cranelift-native", - "cranelift-wasm", - "gimli 0.21.0", - "log", - "more-asserts", - "object 0.21.1", - "region", - "serde", - "target-lexicon 0.11.2", - "thiserror", - "wasmparser 0.59.0", - "wasmtime-cranelift", - "wasmtime-debug", - "wasmtime-environ", - "wasmtime-obj", - "wasmtime-profiling", - "wasmtime-runtime", - "winapi", -] - -[[package]] -name = "wasmtime-obj" -version = "0.20.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cc8422b0acce519b74c3ae5db59167c611ea92220e5c334ad406e977277e0f23" -dependencies = [ - "anyhow", - "more-asserts", - "object 0.21.1", - "target-lexicon 0.11.2", - "wasmtime-debug", - "wasmtime-environ", -] - -[[package]] -name = "wasmtime-profiling" -version = "0.20.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "33f2689bf523f843555e57e24d59abf0c5013007366b866081d73a15e510b4b2" -dependencies = [ - "anyhow", - "cfg-if 0.1.10", - "lazy_static", - "libc", - "serde", - "target-lexicon 0.11.2", - "wasmtime-environ", - "wasmtime-runtime", -] - -[[package]] -name = "wasmtime-runtime" -version = "0.20.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d7353f5e79390048128e44b5ceda7255723b2066de4026df9a168b0b2593df71" -dependencies = [ - "backtrace", - "cc", - "cfg-if 0.1.10", - "indexmap", - "lazy_static", - "libc", - "log", - "memoffset 0.5.6", - "more-asserts", - "region", - "thiserror", - "wasmtime-environ", - "winapi", -] - -[[package]] -name = "wast" -version = "35.0.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2ef140f1b49946586078353a453a1d28ba90adfc54dde75710bc1931de204d68" -dependencies = [ - "leb128", -] - -[[package]] -name = "wat" -version = "1.0.37" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8ec280a739b69173e0ffd12c1658507996836ba4e992ed9bc1e5385a0bd72a02" -dependencies = [ - "wast", -] - -[[package]] -name = "wee_alloc" -version = "0.4.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dbb3b5a6b2bb17cb6ad44a2e68a43e8d2722c997da10e928665c72ec6c0a0b8e" -dependencies = [ - "cfg-if 0.1.10", - "libc", - "memory_units", - "winapi", -] - -[[package]] -name = "which" -version = "4.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b55551e42cbdf2ce2bedd2203d0cc08dba002c27510f86dab6d0ce304cba3dfe" -dependencies = [ - "either", - "libc", -] - -[[package]] -name = "winapi" -version = "0.3.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" -dependencies = [ - "winapi-i686-pc-windows-gnu", - "winapi-x86_64-pc-windows-gnu", -] - -[[package]] -name = "winapi-i686-pc-windows-gnu" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" - -[[package]] -name = "winapi-x86_64-pc-windows-gnu" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" - -[[package]] -name = "wyz" -version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "85e60b0d1b5f99db2556934e21937020776a5d31520bf169e851ac44e6420214" - -[[package]] -name = "zeroize" -version = "1.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4756f7db3f7b5574938c3eb1c117038b8e07f95ee6718c0efad4ac21508f1efd" -dependencies = [ - "zeroize_derive", -] - -[[package]] -name = "zeroize_derive" -version = "1.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a2c1e130bebaeab2f23886bf9acbaca14b092408c452543c857f66399cd6dab1" -dependencies = [ - "proc-macro2", - "quote", - "syn", - "synstructure", -] diff --git a/Cargo.toml b/Cargo.toml index e5fe05d..f04aba4 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,24 +1,35 @@ -[package] -name = "ft-lockup" -version = "1.0.0" -authors = ["Eugene The Dream "] -edition = "2018" - -[lib] -crate-type = ["cdylib", "rlib"] - -[dependencies] -near-sdk = "3.1.0" -near-contract-standards = "3.1.0" -uint = { version = "0.9.0", default-features = false } - -[dev-dependencies] -near-sdk-sim = "3.2.0" - -[profile.release] -codegen-units=1 -opt-level = "z" -lto = true -debug = false -panic = "abort" -overflow-checks = true +[workspace] + +resolver = "2" + +default-members = ["contract"] +members = ["model", "contract", "integration-tests"] + +[workspace.dependencies] +anyhow = "1.0.75" +async-trait = "0.1.73" +fake = "2.8.0" +rand = "0.8.5" +futures = "0.3.28" +itertools = "0.12.0" +base64 = "0.21.3" +sha256 = "1.3.0" +tokio = { version = "1.28" } +uint = "0.9.5" +ed25519-dalek = { version = "2.1.0", features = ["rand_core"] } + +near-workspaces = "0.9.0" + +model = { path = "model" } + +near-sdk = { git = "https://github.com/sweatco/near-sdk-rs", rev = "8c48b26cc48d969c1e5f3162141fe9c824fccecd" } +near-contract-standards = { git = "https://github.com/sweatco/near-sdk-rs", rev = "8c48b26cc48d969c1e5f3162141fe9c824fccecd" } + +integration-trait = { git = "https://github.com/sweatco/integration-trait.git", rev = "8dd8b63cef5e60448629a8903ea43b642bbf9f45" } +integration-utils = { git = "https://github.com/sweatco/integration-utils.git", rev = "19a94420eeb9404f906890fc3e6d06111a841e39" } + +sweat-model = { git = "https://github.com/sweatco/sweat-near", rev = "5e4566ba7e2a46bbd4a979574c4ccd9c6f4fc9db" } +sweat-integration = { git = "https://github.com/sweatco/sweat-near", rev = "5e4566ba7e2a46bbd4a979574c4ccd9c6f4fc9db" } + +multisig-model = { git = "https://github.com/sweatco/multisig", rev = "0e2b6523177571d659d2a36bf5a5195e3dffae9e" } +multisig-integration = { git = "https://github.com/sweatco/multisig", rev = "0e2b6523177571d659d2a36bf5a5195e3dffae9e" } diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..c75a006 --- /dev/null +++ b/Makefile @@ -0,0 +1,48 @@ +help: ##@Miscellaneous Show this help + @echo "Usage: make [target] ...\n" + @perl -e '$(HELP_FUN)' $(MAKEFILE_LIST) + +install: ##@Miscellaneous Install dependencies + @npm i near-cli + @cargo build + +build: ##@Build Build the contract locally. + ./scripts/build.sh + +build-in-docker: ##@Build Build reproducible artifact in Docker. + ./scripts/build-in-docker.sh + +build-integration: ##@Build Build the contract for integration tests. + ./scripts/build-integration.sh + +build-utils: ##@Build utils contract for integration tests. + ./scripts/build-utils.sh + +dock: build-in-docker ##@Build Shorthand for `build-in-docker` + +deploy: ##@Deploy Deploy the contract to dev account on Testnet. + ./scripts/deploy.sh + +cov: ##@Testing Run unit tests with coverage. + ./scripts/coverage.sh + +test: ##@Testing Run unit tests. + cargo test --package model && \ + cargo test --package hodl-lockup + +integration: ##@Testing Run integration tests. + cargo test --package integration-tests + +int: integration ##@Testing Shorthand for `integration` + +fmt: ##@Chores Format the code using rustfmt nightly. + cargo +nightly fmt --all + +lint: ##@Chores Run lint checks with Clippy. + ./scripts/lint.sh + +HELP_FUN = \ + %help; while(<>){push@{$$help{$$2//'options'}},[$$1,$$3] \ + if/^([\w-_]+)\s*:.*\#\#(?:@(\w+))?\s(.*)$$/}; \ + print"$$_:\n", map" $$_->[0]".(" "x(20-length($$_->[0])))."$$_->[1]\n",\ + @{$$help{$$_}},"\n" for keys %help; \ diff --git a/README.md b/README.md index e90a9a9..bcbbcf1 100644 --- a/README.md +++ b/README.md @@ -6,9 +6,9 @@ - Lockup schedule can be set as a list of checkpoints with time and balance. - Supports multiple lockups per account ID. - Ability to create a lockup that can be terminated - - A single lockup can be only terminated by a specific account ID. - - Supports custom vesting schedule that should be ahead of the lockup schedule - - The vesting schedule can be hidden behind a hash, so it only needs to be revealed in case of termnation. + - A single lockup can be only terminated by a specific account ID. + - Supports custom vesting schedule that should be ahead of the lockup schedule + - The vesting schedule can be hidden behind a hash, so it only needs to be revealed in case of termnation. - Automatic rollbacks if a FT transfer fails. - Claiming all account's lockups in a single transaction. - Ability to add new lockups. diff --git a/contract/Cargo.toml b/contract/Cargo.toml new file mode 100644 index 0000000..ff20ef6 --- /dev/null +++ b/contract/Cargo.toml @@ -0,0 +1,26 @@ +[package] +name = "hodl-lockup" +version = "1.0.0" +authors = ["Sweat Economy"] +edition = "2021" + +[lib] +crate-type = ["cdylib"] + +[features] +default = [] +integration-test = [] + +[dependencies] + +near-sdk = { workspace = true } +near-contract-standards = { workspace = true } + +model = { workspace = true } + +[dev-dependencies] +fake = { workspace = true } +rand = { workspace = true } +sha256 = { workspace = true } +crypto-hash = "0.3" +base64 = { workspace = true } diff --git a/contract/src/adjust.rs b/contract/src/adjust.rs new file mode 100644 index 0000000..5c7efcb --- /dev/null +++ b/contract/src/adjust.rs @@ -0,0 +1,131 @@ +use near_sdk::require; + +use crate::*; + +#[near_bindgen] +impl AdjustApi for Contract { + #[payable] + fn adjust(&mut self, beneficiary_id: AccountId, lockup_index: LockupIndex) -> PromiseOrValue<()> { + assert_one_yocto(); + self.assert_deposit_whitelist(&env::predecessor_account_id()); + + let mut lockup = self.lockups.get(lockup_index as _).expect("Lockup not found"); + require!(lockup.is_adjustable, "Lockup is not adjustable"); + + let now = current_timestamp_sec(); + let cliff_end = lockup.schedule.0.first().expect("Checkpoint is required").timestamp; + + if now >= cliff_end { + panic_str("Cliff already ended"); + } + + let created_at = cliff_end - ONE_YEAR_SECONDS; + let percent = (now - created_at) as f64 / ONE_YEAR_SECONDS as f64; + + let initial_schedule = lockup.schedule.clone(); + for checkpoint in lockup.schedule.0.iter_mut() { + checkpoint.balance = (checkpoint.balance as f64 * percent) as _; + } + self.lockups.replace(lockup_index as _, &lockup); + + let balance_to_refund = initial_schedule.total_balance() - lockup.schedule.total_balance(); + + if balance_to_refund > 0 { + Promise::new(self.token_account_id.clone()) + .ft_transfer( + &beneficiary_id.clone(), + balance_to_refund, + Some(format!("Adjusted lockup #{lockup_index}")), + ) + .then( + ext_self::ext(env::current_account_id()) + .with_static_gas(GAS_FOR_AFTER_FT_TRANSFER) + .after_lockup_adjustment(lockup_index, initial_schedule), + ) + .into() + } else { + PromiseOrValue::Value(()) + } + } + + #[payable] + fn revoke(&mut self, beneficiary_id: AccountId, lockup_indices: Vec) -> PromiseOrValue<()> { + assert_one_yocto(); + self.assert_deposit_whitelist(&env::predecessor_account_id()); + + let mut total_balance = 0; + let mut original_schedules: Vec<(LockupIndex, Schedule)> = vec![]; + + for index in lockup_indices.clone() { + let mut lockup = self.lockups.get(index as _).expect("Lockup not found"); + require!(lockup.is_adjustable, "Lockup is not adjustable"); + + total_balance += lockup.schedule.total_balance(); + + original_schedules.push((index, lockup.schedule)); + lockup.schedule = Schedule::new_unlocked(0); + self.lockups.replace(index as _, &lockup); + } + + if total_balance > 0 { + Promise::new(self.token_account_id.clone()) + .ft_transfer( + &beneficiary_id.clone(), + total_balance, + Some(format!("Revoke for lockups {lockup_indices:?}")), + ) + .then( + ext_self::ext(env::current_account_id()) + .with_static_gas(GAS_FOR_AFTER_FT_TRANSFER) + .after_lockups_revoke(original_schedules), + ) + .into() + } else { + PromiseOrValue::Value(()) + } + } +} + +#[ext_contract(ext_self)] +pub trait AdjustmentSelfCallbacks { + fn after_lockup_adjustment(&mut self, lockup_index: LockupIndex, schedule: Schedule); + + fn after_lockups_revoke(&mut self, original_schedules: Vec<(LockupIndex, Schedule)>); +} + +#[near_bindgen] +impl AdjustmentSelfCallbacks for Contract { + #[private] + fn after_lockup_adjustment(&mut self, lockup_index: LockupIndex, schedule: Schedule) { + if is_promise_success() { + return; + } + + let mut lockup = self.lockups.get(lockup_index as _).expect("Lockup not found"); + lockup.schedule = schedule; + } + + fn after_lockups_revoke(&mut self, original_schedules: Vec<(LockupIndex, Schedule)>) { + if is_promise_success() { + for (index, _) in original_schedules { + let mut lockup = self.lockups.get(index as _).expect("Lockup not found"); + + let mut account_lockups = self + .account_lockups + .get(&lockup.account_id) + .expect("Account lockups not found"); + account_lockups.remove(&index); + self.account_lockups.insert(&lockup.account_id, &account_lockups); + + lockup.account_id = env::current_account_id(); + self.lockups.replace(index as _, &lockup); + } + } else { + for (index, schedule) in original_schedules { + let mut lockup = self.lockups.get(index as _).expect("Lockup not found"); + lockup.schedule = schedule; + self.lockups.replace(index as _, &lockup); + } + } + } +} diff --git a/contract/src/callbacks.rs b/contract/src/callbacks.rs new file mode 100644 index 0000000..713f335 --- /dev/null +++ b/contract/src/callbacks.rs @@ -0,0 +1,135 @@ +use model::{ + draft::{DraftGroup, DraftGroupIndex, DraftIndex}, + lockup::{Lockup, LockupClaim, LockupIndex}, + util::current_timestamp_sec, + WrappedBalance, +}; + +use crate::{ + emit, ext_contract, is_promise_success, log, near_bindgen, AccountId, Contract, ContractExt, EventKind, + FtLockupClaimLockup, FtLockupCreateLockup, HashMap, Into, +}; + +#[ext_contract(ext_self)] +pub trait SelfCallbacks { + fn after_ft_transfer(&mut self, account_id: AccountId, lockup_claims: Vec) -> WrappedBalance; + + fn after_lockup_termination(&mut self, account_id: AccountId, amount: WrappedBalance) -> WrappedBalance; + + fn convert_drafts(&mut self, draft_ids: Vec) -> Vec; +} + +#[near_bindgen] +impl SelfCallbacks for Contract { + #[private] + fn after_ft_transfer(&mut self, account_id: AccountId, lockup_claims: Vec) -> WrappedBalance { + let promise_success = is_promise_success(); + let mut total_balance = 0; + if promise_success { + let mut remove_indices = vec![]; + let mut events: Vec = vec![]; + for LockupClaim { + index, + is_final, + claim_amount, + } in lockup_claims + { + if is_final { + remove_indices.push(index); + } + total_balance += claim_amount.0; + let event = FtLockupClaimLockup { + id: index, + amount: claim_amount, + }; + events.push(event); + } + if !remove_indices.is_empty() { + let mut indices = self.account_lockups.get(&account_id).unwrap_or_default(); + for index in remove_indices { + indices.remove(&index); + } + self.internal_save_account_lockups(&account_id, indices); + } + emit(EventKind::FtLockupClaimLockup(events)); + } else { + log!("Token transfer has failed. Refunding."); + let mut modified = false; + let mut indices = self.account_lockups.get(&account_id).unwrap_or_default(); + for LockupClaim { + index, claim_amount, .. + } in lockup_claims + { + if indices.insert(index) { + modified = true; + } + let mut lockup = self.lockups.get(u64::from(index)).unwrap(); + lockup.claimed_balance -= claim_amount.0; + self.lockups.replace(u64::from(index), &lockup); + } + + if modified { + self.internal_save_account_lockups(&account_id, indices); + } + } + total_balance.into() + } + + #[private] + fn after_lockup_termination(&mut self, account_id: AccountId, amount: WrappedBalance) -> WrappedBalance { + if is_promise_success() { + return amount; + } + + log!("Lockup termination transfer has failed."); + // There is no internal balance, so instead we create a new lockup. + let lockup = Lockup::new_unlocked_since(account_id, amount.0, current_timestamp_sec()); + let lockup_index = self.internal_add_lockup(&lockup); + let event: FtLockupCreateLockup = (lockup_index, lockup, None).into(); + emit(EventKind::FtLockupCreateLockup(vec![event])); + 0.into() + } + + fn convert_drafts(&mut self, draft_ids: Vec) -> Vec { + let mut draft_group_lookup: HashMap = HashMap::new(); + let mut events: Vec = vec![]; + let lockup_ids: Vec = draft_ids + .iter() + .map(|draft_id| { + let draft = self.drafts.remove(&draft_id as _).expect("draft not found"); + let draft_group = draft_group_lookup.entry(draft.draft_group_id).or_insert_with(|| { + self.draft_groups + .get(&draft.draft_group_id as _) + .expect("draft group not found") + }); + draft_group.assert_can_convert_draft(); + let payer_id = draft_group.payer_id.as_mut().expect("expected present payer_id"); + + assert!(draft_group.draft_indices.remove(draft_id), "Invariant"); + let amount = draft.total_balance(); + assert!(draft_group.total_amount >= amount, "Invariant"); + draft_group.total_amount -= amount; + + let lockup = draft.lockup_create.into_lockup(payer_id); + let index = self.internal_add_lockup(&lockup); + + let event: FtLockupCreateLockup = (index, lockup, Some(*draft_id)).into(); + events.push(event); + + index + }) + .collect(); + + emit(EventKind::FtLockupCreateLockup(events)); + + for (draft_group_id, draft_group) in &draft_group_lookup { + if draft_group.draft_indices.is_empty() { + self.draft_groups.remove(&draft_group_id as _); + } else { + self.draft_groups.insert(&draft_group_id as _, draft_group); + } + } + + lockup_ids + } +} diff --git a/src/event.rs b/contract/src/event.rs similarity index 53% rename from src/event.rs rename to contract/src/event.rs index e122d32..bbc4a4f 100644 --- a/src/event.rs +++ b/contract/src/event.rs @@ -1,4 +1,10 @@ -use crate::*; +use model::{ + draft::{Draft, DraftGroupIndex, DraftIndex}, + lockup::{Lockup, LockupIndex}, + TimestampSec, WrappedBalance, +}; + +use crate::{log, serde_json, AccountId, Into, Serialize, PACKAGE_NAME, VERSION}; /// Events to be generated by the contract according to NEP-297 @@ -49,7 +55,7 @@ impl From<(LockupIndex, Lockup, Option)> for FtLockupCreateLockup { let (id, lockup, draft_id) = tuple; Self { id, - account_id: lockup.account_id.to_string(), + account_id: lockup.account_id, balance: lockup.schedule.total_balance().into(), start: lockup.schedule.0.first().unwrap().timestamp, finish: lockup.schedule.0.last().unwrap().timestamp, @@ -98,7 +104,7 @@ impl From<(DraftIndex, Draft)> for FtLockupCreateDraft { Self { id, draft_group_id: draft.draft_group_id, - account_id: draft.lockup_create.account_id.to_string(), + account_id: draft.lockup_create.account_id, balance: draft.lockup_create.schedule.total_balance().into(), start: draft.lockup_create.schedule.0.first().unwrap().timestamp, finish: draft.lockup_create.schedule.0.last().unwrap().timestamp, @@ -130,6 +136,7 @@ pub struct FtLockupDeleteDraft { #[serde(crate = "near_sdk::serde")] #[serde(tag = "event", content = "data")] #[serde(rename_all = "snake_case")] +#[allow(clippy::enum_variant_names)] pub(crate) enum EventKind { FtLockupNew(FtLockupNew), FtLockupAddToDepositWhitelist(FtLockupAddToDepositWhitelist), @@ -186,33 +193,42 @@ pub(crate) fn emit(event_kind: EventKind) { #[cfg(test)] mod tests { + use model::{lockup::LockupCreate, schedule::Schedule}; + use near_sdk::{ + serde_json::{from_str, json, Value}, + test_utils, + test_utils::VMContextBuilder, + testing_env, VMContext, + }; + use super::*; - use near_sdk::serde_json::json; - use near_sdk::test_utils::VMContextBuilder; - use near_sdk::{test_utils, testing_env, MockedBlockchain, VMContext}; pub fn get_context() -> VMContext { VMContextBuilder::new().is_view(true).build() } + fn compare_json(log: &str, value: Value) { + let log = log.strip_prefix("EVENT_JSON:").unwrap(); + let json: Value = from_str(log).unwrap(); + + assert_eq!(json, value); + } + #[test] fn test_ft_lockup_init() { testing_env!(get_context()); - let token_account_id = "token.near".into(); + let token_account_id = AccountId::new_unchecked("token.near".to_string()); emit(EventKind::FtLockupNew(FtLockupNew { token_account_id })); - assert_eq!( - test_utils::get_logs()[0], - format!( - r"EVENT_JSON:{}", - json!({ - "standard": PACKAGE_NAME, - "version": VERSION, - "event": "ft_lockup_new", - "data": { "token_account_id": "token.near" }, - }) - .to_string(), - ) + + compare_json( + &test_utils::get_logs()[0], + json!({ + "standard": PACKAGE_NAME, + "version": VERSION, + "event": "ft_lockup_new", + "data": { "token_account_id": "token.near" }, + }), ); } @@ -222,23 +238,17 @@ mod tests { let account_ids: Vec = vec!["alice.near", "bob.near"] .iter() - .map(|&x| x.into()) + .map(|s| AccountId::new_unchecked(s.to_string())) .collect(); - emit(EventKind::FtLockupAddToDepositWhitelist( - FtLockupAddToDepositWhitelist { account_ids }, - )); - assert_eq!( - test_utils::get_logs()[0], - format!( - r"EVENT_JSON:{}", - json!({ - "standard": PACKAGE_NAME, - "version": VERSION, - "event": "ft_lockup_add_to_deposit_whitelist", - "data": { "account_ids": ["alice.near", "bob.near"] }, - }) - .to_string(), - ) + emit(EventKind::FtLockupAddToDepositWhitelist(FtLockupAddToDepositWhitelist { account_ids })); + compare_json( + &test_utils::get_logs()[0], + json!({ + "standard": PACKAGE_NAME, + "version": VERSION, + "event": "ft_lockup_add_to_deposit_whitelist", + "data": { "account_ids": ["alice.near", "bob.near"] }, + }), ); } @@ -248,23 +258,17 @@ mod tests { let account_ids: Vec = vec!["alice.near", "bob.near"] .iter() - .map(|&x| x.into()) + .map(|s| AccountId::new_unchecked(s.to_string())) .collect(); - emit(EventKind::FtLockupRemoveFromDepositWhitelist( - FtLockupRemoveFromDepositWhitelist { account_ids }, - )); - assert_eq!( - test_utils::get_logs()[0], - format!( - r"EVENT_JSON:{}", - json!({ - "standard": PACKAGE_NAME, - "version": VERSION, - "event": "ft_lockup_remove_from_deposit_whitelist", - "data": { "account_ids": ["alice.near", "bob.near"] }, - }) - .to_string(), - ) + emit(EventKind::FtLockupRemoveFromDepositWhitelist(FtLockupRemoveFromDepositWhitelist { account_ids })); + compare_json( + &test_utils::get_logs()[0], + json!({ + "standard": PACKAGE_NAME, + "version": VERSION, + "event": "ft_lockup_remove_from_deposit_whitelist", + "data": { "account_ids": ["alice.near", "bob.near"] }, + }), ); } @@ -274,23 +278,17 @@ mod tests { let account_ids: Vec = vec!["alice.near", "bob.near"] .iter() - .map(|&x| x.into()) + .map(|s| AccountId::new_unchecked(s.to_string())) .collect(); - emit(EventKind::FtLockupAddToDraftOperatorsWhitelist( - FtLockupAddToDraftOperatorsWhitelist { account_ids }, - )); - assert_eq!( - test_utils::get_logs()[0], - format!( - r"EVENT_JSON:{}", - json!({ - "standard": PACKAGE_NAME, - "version": VERSION, - "event": "ft_lockup_add_to_draft_operators_whitelist", - "data": { "account_ids": ["alice.near", "bob.near"] }, - }) - .to_string(), - ) + emit(EventKind::FtLockupAddToDraftOperatorsWhitelist(FtLockupAddToDraftOperatorsWhitelist { account_ids })); + compare_json( + &test_utils::get_logs()[0], + json!({ + "standard": PACKAGE_NAME, + "version": VERSION, + "event": "ft_lockup_add_to_draft_operators_whitelist", + "data": { "account_ids": ["alice.near", "bob.near"] }, + }), ); } @@ -300,23 +298,19 @@ mod tests { let account_ids: Vec = vec!["alice.near", "bob.near"] .iter() - .map(|&x| x.into()) + .map(|s| AccountId::new_unchecked(s.to_string())) .collect(); emit(EventKind::FtLockupRemoveFromDraftOperatorsWhitelist( FtLockupRemoveFromDraftOperatorsWhitelist { account_ids }, )); - assert_eq!( - test_utils::get_logs()[0], - format!( - r"EVENT_JSON:{}", - json!({ - "standard": PACKAGE_NAME, - "version": VERSION, - "event": "ft_lockup_remove_from_draft_operators_whitelist", - "data": { "account_ids": ["alice.near", "bob.near"] }, - }) - .to_string(), - ) + compare_json( + &test_utils::get_logs()[0], + json!({ + "standard": PACKAGE_NAME, + "version": VERSION, + "event": "ft_lockup_remove_from_draft_operators_whitelist", + "data": { "account_ids": ["alice.near", "bob.near"] }, + }), ); } @@ -324,7 +318,7 @@ mod tests { fn test_ft_lockup_create_lockup() { testing_env!(get_context()); - let account_id: AccountId = "alice.near".into(); + let account_id = AccountId::new_unchecked("alice.near".to_string()); let balance: WrappedBalance = 10_000.into(); let timestamp: TimestampSec = 1_500_000_000; let lockup = Lockup::new_unlocked_since(account_id.clone(), balance.0, timestamp); @@ -334,28 +328,24 @@ mod tests { let event: FtLockupCreateLockup = (100, lockup, Some(draft_id)).into(); emit(EventKind::FtLockupCreateLockup(vec![event])); - assert_eq!( - test_utils::get_logs()[0], - format!( - r"EVENT_JSON:{}", - json!({ - "standard": PACKAGE_NAME, - "version": VERSION, - "event": "ft_lockup_create_lockup", - "data": [ - { - "id": lockup_id, - "account_id": account_id, - "balance": balance, - "start": timestamp - 1, - "finish": timestamp, - "terminatable": false, - "draft_id": Some(draft_id), - }, - ], - }) - .to_string(), - ) + compare_json( + &test_utils::get_logs()[0], + json!({ + "standard": PACKAGE_NAME, + "version": VERSION, + "event": "ft_lockup_create_lockup", + "data": [ + { + "id": lockup_id, + "account_id": account_id, + "balance": balance, + "start": timestamp - 1, + "finish": timestamp, + "terminatable": false, + "draft_id": Some(draft_id), + }, + ], + }), ); } @@ -366,29 +356,22 @@ mod tests { let lockup_id: LockupIndex = 100; let amount: WrappedBalance = 10000.into(); - let event = FtLockupClaimLockup { - id: lockup_id, - amount, - }; + let event = FtLockupClaimLockup { id: lockup_id, amount }; emit(EventKind::FtLockupClaimLockup(vec![event])); - assert_eq!( - test_utils::get_logs()[0], - format!( - r"EVENT_JSON:{}", - json!({ - "standard": PACKAGE_NAME, - "version": VERSION, - "event": "ft_lockup_claim_lockup", - "data": [ - { - "id": lockup_id, - "amount": amount, - }, - ], - }) - .to_string(), - ) + compare_json( + &test_utils::get_logs()[0], + json!({ + "standard": PACKAGE_NAME, + "version": VERSION, + "event": "ft_lockup_claim_lockup", + "data": [ + { + "id": lockup_id, + "amount": amount, + }, + ], + }), ); } @@ -407,24 +390,20 @@ mod tests { }; emit(EventKind::FtLockupTerminateLockup(vec![event])); - assert_eq!( - test_utils::get_logs()[0], - format!( - r"EVENT_JSON:{}", - json!({ - "standard": PACKAGE_NAME, - "version": VERSION, - "event": "ft_lockup_terminate_lockup", - "data": [ - { - "id": lockup_id, - "termination_timestamp": termination_timestamp, - "unvested_balance": unvested_balance, - }, - ], - }) - .to_string(), - ) + compare_json( + &test_utils::get_logs()[0], + json!({ + "standard": PACKAGE_NAME, + "version": VERSION, + "event": "ft_lockup_terminate_lockup", + "data": [ + { + "id": lockup_id, + "termination_timestamp": termination_timestamp, + "unvested_balance": unvested_balance, + }, + ], + }), ); } @@ -437,22 +416,18 @@ mod tests { let event = FtLockupCreateDraftGroup { id: draft_group_id }; emit(EventKind::FtLockupCreateDraftGroup(vec![event])); - assert_eq!( - test_utils::get_logs()[0], - format!( - r"EVENT_JSON:{}", - json!({ - "standard": PACKAGE_NAME, - "version": VERSION, - "event": "ft_lockup_create_draft_group", - "data": [ - { - "id": draft_group_id, - }, - ], - }) - .to_string(), - ) + compare_json( + &test_utils::get_logs()[0], + json!({ + "standard": PACKAGE_NAME, + "version": VERSION, + "event": "ft_lockup_create_draft_group", + "data": [ + { + "id": draft_group_id, + }, + ], + }), ); } @@ -460,14 +435,15 @@ mod tests { fn test_ft_lockup_create_draft() { testing_env!(get_context()); - let account_id: ValidAccountId = "alice.near".try_into().unwrap(); + let account_id = AccountId::new_unchecked("alice.near".to_string()); let balance: WrappedBalance = 10_000.into(); let timestamp: TimestampSec = 1_500_000_000; - let lockup_create = LockupCreate { - account_id: account_id.clone(), - schedule: Schedule::new_unlocked_since(balance.0, timestamp), - vesting_schedule: None, - }; + let lockup_create = + LockupCreate { + account_id: account_id.clone(), + schedule: Schedule::new_unlocked_since(balance.0, timestamp), + vesting_schedule: None, + }; let draft_group_id: DraftGroupIndex = 123; let draft = Draft { draft_group_id, @@ -478,28 +454,25 @@ mod tests { let event: FtLockupCreateDraft = (draft_id, draft).into(); emit(EventKind::FtLockupCreateDraft(vec![event])); - assert_eq!( - test_utils::get_logs()[0], - format!( - r"EVENT_JSON:{}", - json!({ - "standard": PACKAGE_NAME, - "version": VERSION, - "event": "ft_lockup_create_draft", - "data": [ - { - "id": draft_id, - "draft_group_id": draft_group_id, - "account_id": account_id.to_string(), - "balance": balance, - "start": timestamp - 1, - "finish": timestamp, - "terminatable": false, - }, - ], - }) - .to_string(), - ) + + compare_json( + &test_utils::get_logs()[0], + json!({ + "standard": PACKAGE_NAME, + "version": VERSION, + "event": "ft_lockup_create_draft", + "data": [ + { + "id": draft_id, + "draft_group_id": draft_group_id, + "account_id": account_id.to_string(), + "balance": balance, + "start": timestamp - 1, + "finish": timestamp, + "terminatable": false, + }, + ], + }), ); } @@ -516,23 +489,20 @@ mod tests { }; emit(EventKind::FtLockupFundDraftGroup(vec![event])); - assert_eq!( - test_utils::get_logs()[0], - format!( - r"EVENT_JSON:{}", - json!({ - "standard": PACKAGE_NAME, - "version": VERSION, - "event": "ft_lockup_fund_draft_group", - "data": [ - { - "id": draft_group_id, - "amount": amount, - }, - ], - }) - .to_string(), - ) + + compare_json( + &test_utils::get_logs()[0], + json!({ + "standard": PACKAGE_NAME, + "version": VERSION, + "event": "ft_lockup_fund_draft_group", + "data": [ + { + "id": draft_group_id, + "amount": amount, + }, + ], + }), ); } @@ -545,22 +515,19 @@ mod tests { let event = FtLockupDiscardDraftGroup { id: draft_group_id }; emit(EventKind::FtLockupDiscardDraftGroup(vec![event])); - assert_eq!( - test_utils::get_logs()[0], - format!( - r"EVENT_JSON:{}", - json!({ - "standard": PACKAGE_NAME, - "version": VERSION, - "event": "ft_lockup_discard_draft_group", - "data": [ - { - "id": draft_group_id, - }, - ], - }) - .to_string(), - ) + + compare_json( + &test_utils::get_logs()[0], + json!({ + "standard": PACKAGE_NAME, + "version": VERSION, + "event": "ft_lockup_discard_draft_group", + "data": [ + { + "id": draft_group_id, + }, + ], + }), ); } @@ -573,22 +540,19 @@ mod tests { let event = FtLockupDeleteDraft { id: draft_id }; emit(EventKind::FtLockupDeleteDraft(vec![event])); - assert_eq!( - test_utils::get_logs()[0], - format!( - r"EVENT_JSON:{}", - json!({ - "standard": PACKAGE_NAME, - "version": VERSION, - "event": "ft_lockup_delete_draft", - "data": [ - { - "id": draft_id, - }, - ], - }) - .to_string(), - ) + + compare_json( + &test_utils::get_logs()[0], + json!({ + "standard": PACKAGE_NAME, + "version": VERSION, + "event": "ft_lockup_delete_draft", + "data": [ + { + "id": draft_id, + }, + ], + }), ); } } diff --git a/src/ft_token_receiver.rs b/contract/src/ft_token_receiver.rs similarity index 60% rename from src/ft_token_receiver.rs rename to contract/src/ft_token_receiver.rs index ff2c63d..0204a82 100644 --- a/src/ft_token_receiver.rs +++ b/contract/src/ft_token_receiver.rs @@ -1,36 +1,16 @@ -use crate::*; +use model::ft_token_receiver::FtMessage; -#[derive(Serialize, Deserialize)] -#[serde(crate = "near_sdk::serde")] -pub struct DraftGroupFunding { - pub draft_group_id: DraftGroupIndex, - // use remaining gas to try converting drafts - pub try_convert: Option, -} - -#[derive(Serialize, Deserialize)] -#[serde(crate = "near_sdk::serde")] -#[serde(untagged)] -pub enum FtMessage { - LockupCreate(LockupCreate), - DraftGroupFunding(DraftGroupFunding), -} +use crate::{ + emit, env, log, near_bindgen, serde_json, AccountId, Contract, ContractExt, EventKind, FtLockupCreateLockup, + FtLockupFundDraftGroup, FungibleTokenReceiver, PromiseOrValue, GAS_EXT_CALL_COST, GAS_MIN_FOR_CONVERT, U128, +}; #[near_bindgen] impl FungibleTokenReceiver for Contract { - fn ft_on_transfer( - &mut self, - sender_id: ValidAccountId, - amount: U128, - msg: String, - ) -> PromiseOrValue { - assert_eq!( - env::predecessor_account_id(), - self.token_account_id, - "Invalid token ID" - ); + fn ft_on_transfer(&mut self, sender_id: AccountId, amount: U128, msg: String) -> PromiseOrValue { + assert_eq!(env::predecessor_account_id(), self.token_account_id, "Invalid token ID"); let amount = amount.into(); - self.assert_deposit_whitelist(sender_id.as_ref()); + self.assert_deposit_whitelist(&sender_id); let ft_message: FtMessage = serde_json::from_str(&msg).unwrap(); match ft_message { @@ -62,16 +42,14 @@ impl FungibleTokenReceiver for Contract { if funding.try_convert.unwrap_or(false) { // Using remaining gas to try convert drafts, not waiting for results - if let Some(remaining_gas) = - env::prepaid_gas().checked_sub(env::used_gas() + GAS_EXT_CALL_COST) + if let Some(remaining_gas) = env::prepaid_gas() + .0 + .checked_sub((env::used_gas() + GAS_EXT_CALL_COST).into()) { - if remaining_gas > GAS_MIN_FOR_CONVERT { - ext_self::convert_drafts( - draft_group.draft_indices.into_iter().collect(), - &env::current_account_id(), - NO_DEPOSIT, - remaining_gas, - ); + if remaining_gas > GAS_MIN_FOR_CONVERT.into() { + crate::callbacks::ext_self::ext(env::current_account_id()) + .with_static_gas(remaining_gas.into()) + .convert_drafts(draft_group.draft_indices.into_iter().collect()); } } } diff --git a/contract/src/integration_test.rs b/contract/src/integration_test.rs new file mode 100644 index 0000000..8c8084d --- /dev/null +++ b/contract/src/integration_test.rs @@ -0,0 +1,16 @@ +#![cfg(feature = "integration-test")] + +use near_sdk::{env, near_bindgen, Timestamp}; + +use crate::{Contract, ContractExt}; + +pub trait IntegrationTestMethods { + fn block_timestamp_ms(&self) -> Timestamp; +} + +#[near_bindgen] +impl IntegrationTestMethods for Contract { + fn block_timestamp_ms(&self) -> Timestamp { + env::block_timestamp_ms() + } +} diff --git a/src/internal.rs b/contract/src/internal.rs similarity index 55% rename from src/internal.rs rename to contract/src/internal.rs index 421495f..dd683b1 100644 --- a/src/internal.rs +++ b/contract/src/internal.rs @@ -1,38 +1,29 @@ -use crate::*; +use model::lockup::{Lockup, LockupIndex}; + +use crate::{AccountId, Contract, HashSet}; impl Contract { pub(crate) fn assert_deposit_whitelist(&self, account_id: &AccountId) { - assert!( - self.deposit_whitelist.contains(account_id), - "Not in deposit whitelist" - ); + assert!(self.deposit_whitelist.contains(account_id), "Not in deposit whitelist"); } pub(crate) fn assert_draft_operators_whitelist(&self, account_id: &AccountId) { assert!( - (self.deposit_whitelist.contains(account_id) - || self.draft_operators_whitelist.contains(account_id)), + (self.deposit_whitelist.contains(account_id) || self.draft_operators_whitelist.contains(account_id)), "Not in draft operators whitelist" ); } pub(crate) fn internal_add_lockup(&mut self, lockup: &Lockup) -> LockupIndex { - let index = self.lockups.len() as LockupIndex; + let index = LockupIndex::try_from(self.lockups.len()).unwrap(); self.lockups.push(lockup); - let mut indices = self - .account_lockups - .get(lockup.account_id.as_ref()) - .unwrap_or_default(); + let mut indices = self.account_lockups.get(&lockup.account_id).unwrap_or_default(); indices.insert(index); - self.internal_save_account_lockups(lockup.account_id.as_ref(), indices); + self.internal_save_account_lockups(&lockup.account_id, indices); index } - pub(crate) fn internal_save_account_lockups( - &mut self, - account_id: &AccountId, - indices: HashSet, - ) { + pub(crate) fn internal_save_account_lockups(&mut self, account_id: &AccountId, indices: HashSet) { if indices.is_empty() { self.account_lockups.remove(account_id); } else { @@ -40,15 +31,12 @@ impl Contract { } } - pub(crate) fn internal_get_account_lockups( - &self, - account_id: &AccountId, - ) -> Vec<(LockupIndex, Lockup)> { + pub(crate) fn internal_get_account_lockups(&self, account_id: &AccountId) -> Vec<(LockupIndex, Lockup)> { self.account_lockups .get(account_id) .unwrap_or_default() .into_iter() - .map(|lockup_index| (lockup_index, self.lockups.get(lockup_index as _).unwrap())) + .map(|lockup_index| (lockup_index, self.lockups.get(u64::from(lockup_index)).unwrap())) .collect() } @@ -64,11 +52,10 @@ impl Contract { .map(|&lockup_index| { assert!( account_lockup_ids.contains(&lockup_index), - "lockup not found for account: {}", - lockup_index, + "lockup not found for account: {lockup_index}", ); - let lockup = self.lockups.get(lockup_index as _).unwrap(); - (lockup_index.clone(), lockup) + let lockup = self.lockups.get(u64::from(lockup_index)).unwrap(); + (lockup_index, lockup) }) .collect() } diff --git a/src/lib.rs b/contract/src/lib.rs similarity index 50% rename from src/lib.rs rename to contract/src/lib.rs index 441fae4..1874e95 100644 --- a/src/lib.rs +++ b/contract/src/lib.rs @@ -1,72 +1,66 @@ -use std::convert::TryInto; - -use near_contract_standards::fungible_token::core_impl::ext_fungible_token; +use std::convert::Into; + +use model::{ + adjust_api::AdjustApi, + draft::{Draft, DraftGroup, DraftGroupIndex, DraftIndex}, + lockup::{Lockup, LockupIndex}, + lockup_api::LockupApi, + schedule::Schedule, + util::current_timestamp_sec, + TimestampSec, TokenAccountId, WrappedBalance, +}; +// use near_contract_standards::fungible_token::core_impl::ext_fungible_token; use near_contract_standards::fungible_token::receiver::FungibleTokenReceiver; -use near_sdk::borsh::maybestd::collections::{HashMap, HashSet}; -use near_sdk::borsh::{self, BorshDeserialize, BorshSerialize}; -use near_sdk::collections::{LookupMap, UnorderedMap, UnorderedSet, Vector}; -use near_sdk::json_types::{Base58CryptoHash, ValidAccountId, WrappedBalance, U128}; -use near_sdk::serde::{Deserialize, Serialize}; use near_sdk::{ - assert_one_yocto, env, ext_contract, is_promise_success, log, near_bindgen, serde_json, - AccountId, Balance, BorshStorageKey, CryptoHash, Gas, PanicOnDefault, PromiseOrValue, - Timestamp, + assert_one_yocto, + borsh::{ + self, + maybestd::collections::{HashMap, HashSet}, + BorshDeserialize, BorshSerialize, + }, + collections::{LookupMap, UnorderedMap, UnorderedSet, Vector}, + env, + env::panic_str, + ext_contract, is_promise_success, + json_types::{Base58CryptoHash, U128}, + log, near_bindgen, + serde::Serialize, + serde_json, AccountId, BorshStorageKey, Gas, PanicOnDefault, Promise, PromiseOrValue, +}; + +use crate::{ + callbacks::{ext_self, SelfCallbacks}, + event::{ + emit, EventKind, FtLockupAddToDepositWhitelist, FtLockupAddToDraftOperatorsWhitelist, FtLockupClaimLockup, + FtLockupCreateDraft, FtLockupCreateDraftGroup, FtLockupCreateLockup, FtLockupDeleteDraft, + FtLockupDiscardDraftGroup, FtLockupFundDraftGroup, FtLockupNew, FtLockupRemoveFromDepositWhitelist, + FtLockupRemoveFromDraftOperatorsWhitelist, FtLockupTerminateLockup, + }, + serde_json::json, + termination::TerminableLockup, }; pub mod callbacks; -pub mod draft; pub mod event; pub mod ft_token_receiver; pub mod internal; -pub mod lockup; -pub mod schedule; -pub mod termination; -pub mod util; -pub mod view; -use crate::draft::*; -use crate::event::*; -use crate::lockup::*; -use crate::schedule::*; -use crate::termination::*; -use crate::util::*; - -near_sdk::setup_alloc!(); - -pub type TimestampSec = u32; -pub type TokenAccountId = AccountId; +mod adjust; +mod integration_test; +mod migration; +mod termination; +mod update; +pub mod view; pub const PACKAGE_NAME: &str = env!("CARGO_PKG_NAME"); pub const VERSION: &str = env!("CARGO_PKG_VERSION"); -const GAS_FOR_FT_TRANSFER: Gas = 15_000_000_000_000; -const GAS_FOR_AFTER_FT_TRANSFER: Gas = 20_000_000_000_000; -const GAS_EXT_CALL_COST: Gas = 10_000_000_000_000; -const GAS_MIN_FOR_CONVERT: Gas = 15_000_000_000_000; - -const ONE_YOCTO: Balance = 1; -const NO_DEPOSIT: Balance = 0; +const GAS_FOR_FT_TRANSFER: Gas = Gas(15_000_000_000_000); +const GAS_FOR_AFTER_FT_TRANSFER: Gas = Gas(20_000_000_000_000); +const GAS_EXT_CALL_COST: Gas = Gas(10_000_000_000_000); +const GAS_MIN_FOR_CONVERT: Gas = Gas(15_000_000_000_000); -uint::construct_uint! { - pub struct U256(4); -} - -#[ext_contract(ext_self)] -pub trait SelfCallbacks { - fn after_ft_transfer( - &mut self, - account_id: AccountId, - lockup_claims: Vec, - ) -> WrappedBalance; - - fn after_lockup_termination( - &mut self, - account_id: AccountId, - amount: WrappedBalance, - ) -> WrappedBalance; - - fn convert_drafts(&mut self, draft_ids: Vec) -> Vec; -} +const ONE_YEAR_SECONDS: u32 = 365 * 24 * 60 * 60; #[near_bindgen] #[derive(BorshDeserialize, BorshSerialize, PanicOnDefault)] @@ -91,6 +85,8 @@ pub struct Contract { pub drafts: LookupMap, pub next_draft_group_id: DraftGroupIndex, pub draft_groups: UnorderedMap, + + pub multisig: Option, } #[derive(BorshStorageKey, BorshSerialize)] @@ -104,66 +100,60 @@ pub(crate) enum StorageKey { } #[near_bindgen] -impl Contract { +impl LockupApi for Contract { #[init] - pub fn new( - token_account_id: ValidAccountId, - deposit_whitelist: Vec, - draft_operators_whitelist: Option>, + fn new( + token_account_id: AccountId, + deposit_whitelist: Vec, + draft_operators_whitelist: Option>, ) -> Self { let mut deposit_whitelist_set = UnorderedSet::new(StorageKey::DepositWhitelist); - deposit_whitelist_set.extend(deposit_whitelist.clone().into_iter().map(|a| a.into())); - let mut draft_operators_whitelist_set = - UnorderedSet::new(StorageKey::DraftOperatorsWhitelist); + deposit_whitelist_set.extend(deposit_whitelist.clone().into_iter().map(Into::into)); + let mut draft_operators_whitelist_set = UnorderedSet::new(StorageKey::DraftOperatorsWhitelist); draft_operators_whitelist_set.extend( draft_operators_whitelist .clone() - .unwrap_or(vec![]) + .unwrap_or_default() .into_iter() - .map(|a| a.into()), + .map(Into::into), ); emit(EventKind::FtLockupNew(FtLockupNew { - token_account_id: token_account_id.clone().into(), + token_account_id: token_account_id.clone(), })); emit(EventKind::FtLockupAddToDepositWhitelist( FtLockupAddToDepositWhitelist { - account_ids: deposit_whitelist.into_iter().map(|x| x.into()).collect(), + account_ids: deposit_whitelist.into_iter().map(Into::into).collect(), }, )); emit(EventKind::FtLockupAddToDraftOperatorsWhitelist( FtLockupAddToDraftOperatorsWhitelist { account_ids: draft_operators_whitelist - .unwrap_or(vec![]) + .unwrap_or_default() .into_iter() - .map(|x| x.into()) + .map(Into::into) .collect(), }, )); Self { lockups: Vector::new(StorageKey::Lockups), account_lockups: LookupMap::new(StorageKey::AccountLockups), - token_account_id: token_account_id.into(), + token_account_id, deposit_whitelist: deposit_whitelist_set, draft_operators_whitelist: draft_operators_whitelist_set, next_draft_id: 0, drafts: LookupMap::new(StorageKey::Drafts), next_draft_group_id: 0, draft_groups: UnorderedMap::new(StorageKey::DraftGroups), + multisig: None, } } - pub fn claim( - &mut self, - amounts: Option)>>, - ) -> PromiseOrValue { + fn claim(&mut self, amounts: Option)>>) -> PromiseOrValue { let account_id = env::predecessor_account_id(); let (claim_amounts, mut lockups_by_id) = if let Some(amounts) = amounts { let lockups_by_id: HashMap = self - .internal_get_account_lockups_by_id( - &account_id, - &amounts.iter().map(|x| x.0).collect(), - ) + .internal_get_account_lockups_by_id(&account_id, &amounts.iter().map(|x| x.0).collect()) .into_iter() .collect(); let amounts: HashMap = amounts @@ -171,33 +161,27 @@ impl Contract { .map(|(lockup_id, amount)| { ( lockup_id, - match amount { - Some(amount) => amount, - None => { - let lockup = - lockups_by_id.get(&lockup_id).expect("lockup not found"); - let unlocked_balance = - lockup.schedule.unlocked_balance(current_timestamp_sec()); - (unlocked_balance - lockup.claimed_balance).into() - } + if let Some(amount) = amount { + amount + } else { + let lockup = lockups_by_id.get(&lockup_id).expect("lockup not found"); + let unlocked_balance = lockup.schedule.unlocked_balance(current_timestamp_sec()); + (unlocked_balance - lockup.claimed_balance).into() }, ) }) .collect(); (amounts, lockups_by_id) } else { - let lockups_by_id: HashMap = self - .internal_get_account_lockups(&account_id) - .into_iter() - .collect(); + let lockups_by_id: HashMap = + self.internal_get_account_lockups(&account_id).into_iter().collect(); let amounts: HashMap = lockups_by_id .iter() .map(|(lockup_id, lockup)| { - let unlocked_balance = - lockup.schedule.unlocked_balance(current_timestamp_sec()); + let unlocked_balance = lockup.schedule.unlocked_balance(current_timestamp_sec()); let amount: WrappedBalance = (unlocked_balance - lockup.claimed_balance).into(); - (lockup_id.clone(), amount) + (*lockup_id, amount) }) .collect(); (amounts, lockups_by_id) @@ -211,46 +195,38 @@ impl Contract { let lockup_claim = lockup.claim(lockup_index, lockup_claim_amount.0); if lockup_claim.claim_amount.0 > 0 { - log!( - "Claiming {} form lockup #{}", - lockup_claim.claim_amount.0, - lockup_index - ); + log!("Claiming {} form lockup #{}", lockup_claim.claim_amount.0, lockup_index); total_claim_amount += lockup_claim.claim_amount.0; - self.lockups.replace(lockup_index as _, &lockup); + self.lockups.replace(u64::from(lockup_index), lockup); lockup_claims.push(lockup_claim); } } log!("Total claim {}", total_claim_amount); if total_claim_amount > 0 { - ext_fungible_token::ft_transfer( - account_id.clone(), - total_claim_amount.into(), - Some(format!( - "Claiming unlocked {} balance from {}", + Promise::new(self.token_account_id.clone()) + .ft_transfer( + &account_id, total_claim_amount, - env::current_account_id() - )), - &self.token_account_id, - ONE_YOCTO, - GAS_FOR_FT_TRANSFER, - ) - .then(ext_self::after_ft_transfer( - account_id, - lockup_claims, - &env::current_account_id(), - NO_DEPOSIT, - GAS_FOR_AFTER_FT_TRANSFER, - )) - .into() + Some(format!( + "Claiming unlocked {} balance from {}", + total_claim_amount, + env::current_account_id() + )), + ) + .then( + ext_self::ext(env::current_account_id()) + .with_static_gas(GAS_FOR_AFTER_FT_TRANSFER) + .after_ft_transfer(account_id, lockup_claims), + ) + .into() } else { PromiseOrValue::Value(0.into()) } } #[payable] - pub fn terminate( + fn terminate( &mut self, lockup_index: LockupIndex, hashed_schedule: Option, @@ -258,29 +234,21 @@ impl Contract { ) -> PromiseOrValue { assert_one_yocto(); self.assert_deposit_whitelist(&env::predecessor_account_id()); - let mut lockup = self - .lockups - .get(lockup_index as _) - .expect("Lockup not found"); + let mut lockup = self.lockups.get(u64::from(lockup_index)).expect("Lockup not found"); let current_timestamp = current_timestamp_sec(); let termination_timestamp = termination_timestamp.unwrap_or(current_timestamp); assert!( termination_timestamp >= current_timestamp, "expected termination_timestamp >= now", ); - let (unvested_balance, beneficiary_id) = - lockup.terminate(hashed_schedule, termination_timestamp); - self.lockups.replace(lockup_index as _, &lockup); + let (unvested_balance, beneficiary_id) = lockup.terminate(hashed_schedule, termination_timestamp); + self.lockups.replace(u64::from(lockup_index), &lockup); // no need to store empty lockup if lockup.schedule.total_balance() == 0 { - let lockup_account_id: AccountId = lockup.account_id.into(); - let mut indices = self - .account_lockups - .get(&lockup_account_id) - .unwrap_or_default(); + let mut indices = self.account_lockups.get(&lockup.account_id).unwrap_or_default(); indices.remove(&lockup_index); - self.internal_save_account_lockups(&lockup_account_id, indices); + self.internal_save_account_lockups(&lockup.account_id, indices); } let event = FtLockupTerminateLockup { @@ -291,22 +259,18 @@ impl Contract { emit(EventKind::FtLockupTerminateLockup(vec![event])); if unvested_balance > 0 { - ext_fungible_token::ft_transfer( - beneficiary_id.clone(), - unvested_balance.into(), - Some(format!("Terminated lockup #{}", lockup_index)), - &self.token_account_id, - ONE_YOCTO, - GAS_FOR_FT_TRANSFER, - ) - .then(ext_self::after_lockup_termination( - beneficiary_id, - unvested_balance.into(), - &env::current_account_id(), - NO_DEPOSIT, - GAS_FOR_AFTER_FT_TRANSFER, - )) - .into() + Promise::new(self.token_account_id.clone()) + .ft_transfer( + &beneficiary_id.clone(), + unvested_balance, + Some(format!("Terminated lockup #{lockup_index}")), + ) + .then( + ext_self::ext(env::current_account_id()) + .with_static_gas(GAS_FOR_AFTER_FT_TRANSFER) + .after_lockup_termination(beneficiary_id, unvested_balance.into()), + ) + .into() } else { PromiseOrValue::Value(0.into()) } @@ -314,11 +278,7 @@ impl Contract { // preserving both options for API compatibility #[payable] - pub fn add_to_deposit_whitelist( - &mut self, - account_id: Option, - account_ids: Option>, - ) { + fn add_to_deposit_whitelist(&mut self, account_id: Option, account_ids: Option>) { assert_one_yocto(); self.assert_deposit_whitelist(&env::predecessor_account_id()); let account_ids = if let Some(account_ids) = account_ids { @@ -327,22 +287,18 @@ impl Contract { vec![account_id.expect("expected either account_id or account_ids")] }; for account_id in &account_ids { - self.deposit_whitelist.insert(account_id.as_ref()); + self.deposit_whitelist.insert(account_id); } emit(EventKind::FtLockupAddToDepositWhitelist( FtLockupAddToDepositWhitelist { - account_ids: account_ids.into_iter().map(|x| x.into()).collect(), + account_ids: account_ids.into_iter().map(Into::into).collect(), }, )); } // preserving both options for API compatibility #[payable] - pub fn remove_from_deposit_whitelist( - &mut self, - account_id: Option, - account_ids: Option>, - ) { + fn remove_from_deposit_whitelist(&mut self, account_id: Option, account_ids: Option>) { assert_one_yocto(); self.assert_deposit_whitelist(&env::predecessor_account_id()); let account_ids = if let Some(account_ids) = account_ids { @@ -351,7 +307,7 @@ impl Contract { vec![account_id.expect("expected either account_id or account_ids")] }; for account_id in &account_ids { - self.deposit_whitelist.remove(&account_id.to_string()); + self.deposit_whitelist.remove(account_id); } assert!( !self.deposit_whitelist.is_empty(), @@ -359,75 +315,71 @@ impl Contract { ); emit(EventKind::FtLockupRemoveFromDepositWhitelist( FtLockupRemoveFromDepositWhitelist { - account_ids: account_ids.into_iter().map(|x| x.into()).collect(), + account_ids: account_ids.into_iter().map(Into::into).collect(), }, )); } #[payable] - pub fn add_to_draft_operators_whitelist(&mut self, account_ids: Vec) { + fn add_to_draft_operators_whitelist(&mut self, account_ids: Vec) { assert_one_yocto(); self.assert_deposit_whitelist(&env::predecessor_account_id()); for account_id in &account_ids { - self.draft_operators_whitelist.insert(account_id.as_ref()); + self.draft_operators_whitelist.insert(account_id); } emit(EventKind::FtLockupAddToDraftOperatorsWhitelist( FtLockupAddToDraftOperatorsWhitelist { - account_ids: account_ids.into_iter().map(|x| x.into()).collect(), + account_ids: account_ids.into_iter().map(Into::into).collect(), }, )); } #[payable] - pub fn remove_from_draft_operators_whitelist(&mut self, account_ids: Vec) { + fn remove_from_draft_operators_whitelist(&mut self, account_ids: Vec) { assert_one_yocto(); self.assert_deposit_whitelist(&env::predecessor_account_id()); for account_id in &account_ids { - self.draft_operators_whitelist.remove(account_id.as_ref()); + self.draft_operators_whitelist.remove(account_id); } emit(EventKind::FtLockupRemoveFromDraftOperatorsWhitelist( FtLockupRemoveFromDraftOperatorsWhitelist { - account_ids: account_ids.into_iter().map(|x| x.into()).collect(), + account_ids: account_ids.into_iter().map(Into::into).collect(), }, )); } - pub fn create_draft_group(&mut self) -> DraftGroupIndex { + fn create_draft_group(&mut self) -> DraftGroupIndex { self.assert_draft_operators_whitelist(&env::predecessor_account_id()); let index = self.next_draft_group_id; self.next_draft_group_id += 1; assert!( - self.draft_groups - .insert(&index, &DraftGroup::new()) - .is_none(), + self.draft_groups.insert(&index, &DraftGroup::default()).is_none(), "Invariant" ); - emit(EventKind::FtLockupCreateDraftGroup(vec![ - FtLockupCreateDraftGroup { id: index }, - ])); + emit(EventKind::FtLockupCreateDraftGroup(vec![FtLockupCreateDraftGroup { + id: index, + }])); index } - pub fn create_draft(&mut self, draft: Draft) -> DraftIndex { + fn create_draft(&mut self, draft: Draft) -> DraftIndex { self.create_drafts(vec![draft])[0] } - pub fn create_drafts(&mut self, drafts: Vec) -> Vec { + fn create_drafts(&mut self, drafts: Vec) -> Vec { self.assert_draft_operators_whitelist(&env::predecessor_account_id()); let mut draft_group_lookup: HashMap = HashMap::new(); let mut events: Vec = vec![]; let draft_ids: Vec = drafts .into_iter() .map(|draft| { - let draft_group = draft_group_lookup - .entry(draft.draft_group_id) - .or_insert_with(|| { - self.draft_groups - .get(&draft.draft_group_id as _) - .expect("draft group not found") - }); + let draft_group = draft_group_lookup.entry(draft.draft_group_id).or_insert_with(|| { + self.draft_groups + .get(&draft.draft_group_id as _) + .expect("draft group not found") + }); draft_group.assert_can_add_draft(); draft.assert_new_valid(); @@ -447,70 +399,18 @@ impl Contract { .collect(); emit(EventKind::FtLockupCreateDraft(events)); - draft_group_lookup - .iter() - .for_each(|(draft_group_id, draft_group)| { - self.draft_groups.insert(&draft_group_id as _, &draft_group); - }); + for (draft_group_id, draft_group) in draft_group_lookup { + self.draft_groups.insert(&draft_group_id as _, &draft_group); + } draft_ids } - pub fn convert_draft(&mut self, draft_id: DraftIndex) -> LockupIndex { + fn convert_draft(&mut self, draft_id: DraftIndex) -> LockupIndex { self.convert_drafts(vec![draft_id])[0] } - pub fn convert_drafts(&mut self, draft_ids: Vec) -> Vec { - let mut draft_group_lookup: HashMap = HashMap::new(); - let mut events: Vec = vec![]; - let lockup_ids: Vec = draft_ids - .iter() - .map(|draft_id| { - let draft = self.drafts.remove(&draft_id as _).expect("draft not found"); - let draft_group = draft_group_lookup - .entry(draft.draft_group_id) - .or_insert_with(|| { - self.draft_groups - .get(&draft.draft_group_id as _) - .expect("draft group not found") - }); - draft_group.assert_can_convert_draft(); - let payer_id = draft_group - .payer_id - .as_mut() - .expect("expected present payer_id"); - - assert!(draft_group.draft_indices.remove(&draft_id), "Invariant"); - let amount = draft.total_balance(); - assert!(draft_group.total_amount >= amount, "Invariant"); - draft_group.total_amount -= amount; - - let lockup = draft.lockup_create.into_lockup(&payer_id); - let index = self.internal_add_lockup(&lockup); - - let event: FtLockupCreateLockup = (index, lockup, Some(draft_id.clone())).into(); - events.push(event); - - index - }) - .collect(); - - emit(EventKind::FtLockupCreateLockup(events)); - - draft_group_lookup - .iter() - .for_each(|(draft_group_id, draft_group)| { - if draft_group.draft_indices.is_empty() { - self.draft_groups.remove(&draft_group_id as _); - } else { - self.draft_groups.insert(&draft_group_id as _, &draft_group); - } - }); - - lockup_ids - } - - pub fn discard_draft_group(&mut self, draft_group_id: DraftGroupIndex) { + fn discard_draft_group(&mut self, draft_group_id: DraftGroupIndex) { self.assert_draft_operators_whitelist(&env::predecessor_account_id()); let mut draft_group = self @@ -525,41 +425,37 @@ impl Contract { self.draft_groups.insert(&draft_group_id as _, &draft_group); } - emit(EventKind::FtLockupDiscardDraftGroup(vec![ - FtLockupDiscardDraftGroup { id: draft_group_id }, - ])); + emit(EventKind::FtLockupDiscardDraftGroup(vec![FtLockupDiscardDraftGroup { + id: draft_group_id, + }])); } - pub fn delete_drafts(&mut self, draft_ids: Vec) { + fn delete_drafts(&mut self, draft_ids: Vec) { // no authorization required here since the draft group discard has been authorized let mut draft_group_lookup: HashMap = HashMap::new(); let mut events: Vec = vec![]; - for draft_id in &draft_ids { + for draft_id in draft_ids { let draft = self.drafts.remove(&draft_id as _).expect("draft not found"); - let draft_group = draft_group_lookup - .entry(draft.draft_group_id) - .or_insert_with(|| { - self.draft_groups - .get(&draft.draft_group_id as _) - .expect("draft group not found") - }); + let draft_group = draft_group_lookup.entry(draft.draft_group_id).or_insert_with(|| { + self.draft_groups + .get(&draft.draft_group_id as _) + .expect("draft group not found") + }); draft_group.assert_can_delete_draft(); let amount = draft.total_balance(); assert!(draft_group.total_amount >= amount, "Invariant"); draft_group.total_amount -= amount; - assert!(draft_group.draft_indices.remove(draft_id), "Invariant"); + assert!(draft_group.draft_indices.remove(&draft_id), "Invariant"); - let event = FtLockupDeleteDraft { - id: draft_id.clone(), - }; + let event = FtLockupDeleteDraft { id: draft_id }; events.push(event); } emit(EventKind::FtLockupDeleteDraft(events)); - for (draft_group_id, draft_group) in &draft_group_lookup { + for (draft_group_id, draft_group) in draft_group_lookup { if draft_group.draft_indices.is_empty() { self.draft_groups.remove(&draft_group_id as _); } else { @@ -567,4 +463,35 @@ impl Contract { } } } + + #[payable] + fn make_lockup_terminable(&mut self, beneficiary_id: AccountId, lockup_index: LockupIndex) { + assert_one_yocto(); + self.assert_deposit_whitelist(&env::predecessor_account_id()); + + let mut lockup = self.lockups.get(lockup_index as _).expect("Lockup not found"); + lockup.make_terminable(beneficiary_id); + + self.lockups.replace(lockup_index as _, &lockup); + } +} + +/// Amount of fungible tokens +pub type TokenAmount = u128; + +trait FtTransferPromise { + fn ft_transfer(self, receiver_id: &AccountId, amount: TokenAmount, memo: Option) -> Promise; +} + +impl FtTransferPromise for Promise { + fn ft_transfer(self, receiver_id: &AccountId, amount: TokenAmount, memo: Option) -> Promise { + let args = serde_json::to_vec(&json!({ + "receiver_id": receiver_id, + "amount": amount.to_string(), + "memo": memo.unwrap_or_default(), + })) + .expect("Failed to serialize arguments"); + + self.function_call("ft_transfer".to_string(), args, 1, GAS_FOR_FT_TRANSFER) + } } diff --git a/contract/src/migration.rs b/contract/src/migration.rs new file mode 100644 index 0000000..34f2ef5 --- /dev/null +++ b/contract/src/migration.rs @@ -0,0 +1,37 @@ +use model::migration::ContractDeprecated; + +use crate::*; + +#[near_bindgen] +impl Contract { + #[private] + #[init(ignore_state)] + pub fn migrate(adjustable_account_ids: HashSet) -> Self { + let old_state: ContractDeprecated = env::state_read().expect("Failed to read old state"); + let mut lockups: Vector = Vector::new(StorageKey::Lockups); + + for lockup in old_state.lockups.iter() { + let modified_lockup = Lockup { + account_id: lockup.account_id.clone(), + schedule: lockup.schedule.clone(), + claimed_balance: lockup.claimed_balance, + termination_config: lockup.termination_config.clone(), + is_adjustable: adjustable_account_ids.contains(&lockup.account_id), + }; + lockups.push(&modified_lockup); + } + + Contract { + token_account_id: old_state.token_account_id, + lockups, + account_lockups: old_state.account_lockups, + deposit_whitelist: old_state.deposit_whitelist, + draft_operators_whitelist: old_state.draft_operators_whitelist, + next_draft_id: old_state.next_draft_id, + drafts: old_state.drafts, + next_draft_group_id: old_state.next_draft_group_id, + draft_groups: old_state.draft_groups, + multisig: old_state.multisig, + } + } +} diff --git a/contract/src/termination.rs b/contract/src/termination.rs new file mode 100644 index 0000000..daa8f9d --- /dev/null +++ b/contract/src/termination.rs @@ -0,0 +1,55 @@ +use model::{ + lockup::Lockup, + schedule::Schedule, + termination::{TerminationConfig, VestingConditions}, + TimestampSec, +}; +use near_sdk::{AccountId, Balance, CryptoHash}; + +pub(crate) trait TerminableLockup { + fn terminate( + &mut self, + hashed_schedule: Option, + termination_timestamp: TimestampSec, + ) -> (Balance, AccountId); + + fn make_terminable(&mut self, beneficiary_id: AccountId); +} + +impl TerminableLockup for Lockup { + fn terminate( + &mut self, + hashed_schedule: Option, + termination_timestamp: TimestampSec, + ) -> (Balance, AccountId) { + let termination_config = self.termination_config.take().expect("No termination config"); + let total_balance = self.schedule.total_balance(); + let vested_balance = match &termination_config.vesting_schedule { + VestingConditions::SameAsLockupSchedule => &self.schedule, + VestingConditions::Hash(hash) => { + let schedule = hashed_schedule + .as_ref() + .expect("Revealed schedule required for the termination"); + let hash: CryptoHash = (*hash).into(); + assert_eq!(hash, schedule.hash(), "The revealed schedule hash doesn't match"); + schedule.assert_valid(total_balance); + self.schedule.assert_valid_termination_schedule(schedule); + schedule + } + VestingConditions::Schedule(schedule) => schedule, + } + .unlocked_balance(termination_timestamp); + let unvested_balance = total_balance - vested_balance; + if unvested_balance > 0 { + self.schedule.terminate(vested_balance, termination_timestamp); + } + (unvested_balance, termination_config.beneficiary_id) + } + + fn make_terminable(&mut self, beneficiary_id: AccountId) { + self.termination_config = Some(TerminationConfig { + beneficiary_id, + vesting_schedule: VestingConditions::SameAsLockupSchedule, + }); + } +} diff --git a/contract/src/update.rs b/contract/src/update.rs new file mode 100644 index 0000000..c85c1ae --- /dev/null +++ b/contract/src/update.rs @@ -0,0 +1,34 @@ +use model::update::UpdateApi; +use near_sdk::{env, near_bindgen, AccountId, Promise, PromiseOrValue}; + +use crate::{Contract, ContractExt}; + +#[near_bindgen] +impl UpdateApi for Contract { + #[payable] + fn update_contract(&mut self) -> PromiseOrValue<()> { + let Some(ref multisig) = self.multisig else { + env::panic_str("Multisig account is not set. Operation is impossible"); + }; + + assert_eq!( + env::predecessor_account_id(), + *multisig, + "Only multisig account can update the contract" + ); + + env::log_str("Skogo4"); + + let code = env::input().expect("Error: No input"); + + Promise::new(env::current_account_id()) + .deploy_contract(code) + .as_return() + .into() + } + + #[private] + fn set_multisig(&mut self, multisig: AccountId) { + self.multisig = multisig.into(); + } +} diff --git a/contract/src/view.rs b/contract/src/view.rs new file mode 100644 index 0000000..18e91b5 --- /dev/null +++ b/contract/src/view.rs @@ -0,0 +1,126 @@ +use model::{ + draft::{DraftGroupIndex, DraftGroupView, DraftIndex, DraftView}, + lockup::{LockupIndex, LockupView}, + schedule::Schedule, + view_api::LockupViewApi, + WrappedBalance, +}; + +use crate::{near_bindgen, AccountId, Base58CryptoHash, Contract, ContractExt, Into, VERSION}; + +#[near_bindgen] +impl LockupViewApi for Contract { + fn get_token_account_id(&self) -> AccountId { + self.token_account_id.clone() + } + + fn get_account_lockups(&self, account_id: AccountId) -> Vec<(LockupIndex, LockupView)> { + self.internal_get_account_lockups(&account_id) + .into_iter() + .map(|(lockup_index, lockup)| (lockup_index, lockup.into())) + .collect() + } + + fn get_lockup(&self, index: LockupIndex) -> Option { + self.lockups.get(u64::from(index)).map(Into::into) + } + + fn get_lockups(&self, indices: Vec) -> Vec<(LockupIndex, LockupView)> { + indices + .into_iter() + .filter_map(|index| self.get_lockup(index).map(|lockup| (index, lockup))) + .collect() + } + + fn get_num_lockups(&self) -> u32 { + self.lockups.len().try_into().unwrap() + } + + fn get_lockups_paged( + &self, + from_index: Option, + limit: Option, + ) -> Vec<(LockupIndex, LockupView)> { + let from_index = from_index.unwrap_or(0); + let limit = limit.unwrap_or(self.get_num_lockups()); + (from_index..std::cmp::min(self.get_num_lockups(), limit)) + .filter_map(|index| self.get_lockup(index).map(|lockup| (index, lockup))) + .collect() + } + + fn get_deposit_whitelist(&self) -> Vec { + self.deposit_whitelist.to_vec() + } + + fn get_draft_operators_whitelist(&self) -> Vec { + self.draft_operators_whitelist.to_vec() + } + + fn hash_schedule(&self, schedule: Schedule) -> Base58CryptoHash { + schedule.hash().into() + } + + fn validate_schedule( + &self, + schedule: Schedule, + total_balance: WrappedBalance, + termination_schedule: Option, + ) { + schedule.assert_valid(total_balance.0); + if let Some(termination_schedule) = termination_schedule { + termination_schedule.assert_valid(total_balance.0); + schedule.assert_valid_termination_schedule(&termination_schedule); + } + } + + fn get_next_draft_group_id(&self) -> DraftGroupIndex { + self.next_draft_group_id + } + + fn get_next_draft_id(&self) -> DraftGroupIndex { + self.next_draft_id + } + + fn get_num_draft_groups(&self) -> u32 { + self.draft_groups.len().try_into().unwrap() + } + + fn get_draft_group(&self, index: DraftGroupIndex) -> Option { + self.draft_groups.get(&index as _).map(Into::into) + } + + fn get_draft_groups_paged( + &self, + // not the draft_id, but internal index used inside the LookupMap struct + from_index: Option, + to_index: Option, + ) -> Vec<(DraftGroupIndex, DraftGroupView)> { + let from_index = from_index.unwrap_or(0); + let to_index = to_index.unwrap_or(self.draft_groups.len().try_into().unwrap()); + let keys = self.draft_groups.keys_as_vector(); + let values = self.draft_groups.values_as_vector(); + (from_index..std::cmp::min(self.next_draft_group_id as _, to_index)) + .map(|index| { + ( + keys.get(u64::from(index)).unwrap(), + values.get(u64::from(index)).unwrap().into(), + ) + }) + .collect() + } + + fn get_draft(&self, index: DraftIndex) -> Option { + self.drafts.get(&index as _).map(Into::into) + } + + fn get_drafts(&self, indices: Vec) -> Vec<(DraftIndex, DraftView)> { + indices + .into_iter() + .filter_map(|index| self.get_draft(index).map(|draft| (index, draft))) + .collect() + } + + fn get_version(&self) -> String { + VERSION.into() + } +} diff --git a/integration-tests/Cargo.toml b/integration-tests/Cargo.toml new file mode 100644 index 0000000..8f4c6fa --- /dev/null +++ b/integration-tests/Cargo.toml @@ -0,0 +1,29 @@ +[package] +name = "integration-tests" +version = "1.0.0" +publish = false +edition = "2021" + +[lib] +name = "integration_tests" + +[dependencies] +base58 = "0.2.0" + +rand = { workspace = true } +tokio = { workspace = true } +anyhow = { workspace = true } +async-trait = { workspace = true } +ed25519-dalek = { workspace = true } + +near-sdk = { workspace = true } +near-workspaces = { workspace = true } + +model = { workspace = true } +integration-utils = { workspace = true } + +sweat-model = { workspace = true } +sweat-integration = { workspace = true } + +multisig-model = { workspace = true } +multisig-integration = { workspace = true } diff --git a/integration-tests/src/common.rs b/integration-tests/src/common.rs new file mode 100644 index 0000000..ec62e62 --- /dev/null +++ b/integration-tests/src/common.rs @@ -0,0 +1,44 @@ +use integration_utils::integration_contract::IntegrationContract; +use near_workspaces::result::{ExecutionFailure, ExecutionFinalResult, ExecutionResult, ExecutionSuccess}; +use sweat_model::SweatApiIntegration; + +use crate::context::{prepare_contract, IntegrationContext}; + +pub(crate) trait PanicFinder { + fn has_panic(&self, message: &str) -> bool; +} + +impl PanicFinder for Result { + fn has_panic(&self, message: &str) -> bool { + match self { + Ok(ok) => ok.has_panic(message), + Err(err) => err.has_panic(message), + } + } +} + +impl PanicFinder for ExecutionResult { + fn has_panic(&self, message: &str) -> bool { + self.outcomes() + .into_iter() + .map(|item| match item.clone().into_result() { + Ok(_) => None, + Err(err) => Some(err), + }) + .any(|item| match item { + None => false, + Some(err) => format!("{err:?}").contains(message), + }) + } +} + +pub(crate) fn log_result(result: ExecutionFinalResult) { + let result = result.into_result(); + println!(" 📬 Result: {result:?}"); + + if let Ok(result) = result { + for log in result.logs() { + println!(" 📖 {log}"); + } + } +} diff --git a/integration-tests/src/context.rs b/integration-tests/src/context.rs new file mode 100644 index 0000000..2d5a1bd --- /dev/null +++ b/integration-tests/src/context.rs @@ -0,0 +1,95 @@ +#![cfg(test)] + +use anyhow::Result; +use async_trait::async_trait; +use integration_utils::{integration_contract::IntegrationContract, misc::ToNear}; +use model::lockup_api::LockupApiIntegration; +use multisig_integration::{Multisig, MULTISIG}; +use multisig_model::api::MultisigApiIntegration; +use near_sdk::serde::de::Unexpected::Option; +use near_workspaces::Account; +use sweat_integration::{SweatFt, FT_CONTRACT}; +use sweat_model::{StorageManagementIntegration, SweatApiIntegration}; + +use crate::lockup_interface::{GetContractAccount, LockupContract, LOCKUP_CONTRACT}; + +const UTILS_CONTRACT: &str = "utils"; + +pub type Context = integration_utils::context::Context; + +#[async_trait] +pub trait IntegrationContext { + async fn manager(&mut self) -> Result; + async fn bob(&mut self) -> Result; + async fn alice(&mut self) -> Result; + fn lockup(&self) -> LockupContract<'_>; + fn multisig(&self) -> Multisig<'_>; + fn ft_contract(&self) -> SweatFt<'_>; +} + +#[async_trait] +impl IntegrationContext for Context { + async fn manager(&mut self) -> Result { + self.account("manager").await + } + + async fn bob(&mut self) -> Result { + self.account("bob").await + } + + async fn alice(&mut self) -> Result { + self.account("alice").await + } + + fn lockup(&self) -> LockupContract<'_> { + LockupContract::with_contract(&self.contracts[LOCKUP_CONTRACT]) + } + + fn multisig(&self) -> Multisig<'_> { + Multisig::with_contract(&self.contracts[MULTISIG]) + } + + fn ft_contract(&self) -> SweatFt<'_> { + SweatFt::with_contract(&self.contracts[FT_CONTRACT]) + } +} + +pub(crate) async fn prepare_contract() -> Result { + let mut context = Context::new( + &[LOCKUP_CONTRACT, MULTISIG, FT_CONTRACT, UTILS_CONTRACT], + "build-integration".into(), + ) + .await?; + + let manager = context.manager().await?; + + context.ft_contract().new(".u.sweat.testnet".to_string().into()).await?; + context.ft_contract().add_oracle(&manager.to_near()).await?; + + context.multisig().new(0).await?; + + // let holding_contract_init_result = context.utils().call("new").max_gas().transact().await?.into_result()?; + // println!("Initialized holding contract: {:?}", holding_contract_init_result); + + context + .lockup() + .new( + context.ft_contract().contract_account(), + vec![manager.to_near()], + Some(vec![manager.to_near()]), + ) + .await?; + + context + .ft_contract() + .with_user(&manager) + .storage_deposit(Some(context.lockup().contract().as_account().to_near()), None) + .await?; + context + .ft_contract() + .with_user(&manager) + .storage_deposit(Some(manager.to_near()), None) + .await?; + + Ok(context) +} diff --git a/integration-tests/src/lib.rs b/integration-tests/src/lib.rs new file mode 100644 index 0000000..7715ddd --- /dev/null +++ b/integration-tests/src/lib.rs @@ -0,0 +1,6 @@ +mod common; +pub mod context; +mod lockup_interface; +mod termination; +mod update; +mod utils; diff --git a/integration-tests/src/lockup_interface.rs b/integration-tests/src/lockup_interface.rs new file mode 100644 index 0000000..8f3bc90 --- /dev/null +++ b/integration-tests/src/lockup_interface.rs @@ -0,0 +1,439 @@ +#![cfg(test)] + +use anyhow::Result; +use async_trait::async_trait; +use integration_utils::{integration_contract::IntegrationContract, misc::ToNear}; +use model::{ + adjust_api::AdjustApiIntegration, + draft::{Draft, DraftGroupIndex, DraftGroupView, DraftIndex, DraftView}, + lockup::{LockupIndex, LockupView}, + lockup_api::LockupApiIntegration, + schedule::Schedule, + update::UpdateApiIntegration, + view_api::{LockupViewApi, LockupViewApiIntegration}, + TimestampSec, WrappedBalance, +}; +use near_sdk::{json_types::Base58CryptoHash, serde_json::json, AccountId, Timestamp}; +use near_workspaces::{result::ExecutionFinalResult, types::NearToken, Account, Contract}; + +use crate::common::log_result; + +pub const LOCKUP_CONTRACT: &str = "hodl_lockup"; + +pub struct LockupContract<'a> { + account: Option, + contract: &'a Contract, +} + +impl LockupContract<'_> { + pub(crate) async fn terminate_raw( + &mut self, + lockup_index: LockupIndex, + hashed_schedule: Option, + termination_timestamp: Option, + ) -> Result { + println!("▶️ terminate"); + + let result = self + .user_account() + .expect("User account is required") + .call(self.contract.id(), "terminate") + .args_json(json!({ + "lockup_index": lockup_index, + "hashed_schedule": hashed_schedule, + "termination_timestamp": termination_timestamp + })) + .max_gas() + .deposit(NearToken::from_yoctonear(1)) + .transact() + .await?; + + log_result(result.clone()); + + Ok(result) + } + + pub(crate) async fn block_timestamp_ms(&self) -> anyhow::Result { + println!("▶️ block_timestamp_ms"); + let result = self.contract.view("block_timestamp_ms").await?.json()?; + println!(" ✅ {:?}", result); + Ok(result) + } +} + +#[async_trait] +impl LockupApiIntegration for LockupContract<'_> { + async fn new( + &self, + token_account_id: AccountId, + deposit_whitelist: Vec, + draft_operators_whitelist: Option>, + ) -> Result<()> { + self.call( + "new", + json!({ + "token_account_id": token_account_id, + "deposit_whitelist": deposit_whitelist, + "draft_operators_whitelist": draft_operators_whitelist + }), + ) + .await + } + + async fn claim(&mut self, amounts: Option)>>) -> Result { + self.call( + "claim", + json!({ + "amounts": amounts + }), + ) + .await + } + + async fn terminate( + &mut self, + lockup_index: LockupIndex, + hashed_schedule: Option, + termination_timestamp: Option, + ) -> Result { + let result = self + .terminate_raw(lockup_index, hashed_schedule, termination_timestamp) + .await? + .json()?; + + Ok(result) + } + + async fn add_to_deposit_whitelist( + &mut self, + account_id: Option, + account_ids: Option>, + ) -> Result<()> { + println!("▶️ add_to_deposit_whitelist"); + + let account = self.user_account().expect("User account is required"); + let result = account + .call(self.contract.id(), "add_to_deposit_whitelist") + .args_json(json!({ + "account_id": account_id, + "account_ids": account_ids, + })) + .max_gas() + .deposit(NearToken::from_yoctonear(1)) + .transact() + .await?; + + log_result(result.clone()); + + Ok(result.json()?) + } + + async fn remove_from_deposit_whitelist( + &mut self, + account_id: Option, + account_ids: Option>, + ) -> Result<()> { + self.call( + "remove_from_deposit_whitelist", + json!({ + "account_id": account_id, + "account_ids": account_ids, + }), + ) + .await + } + + async fn add_to_draft_operators_whitelist(&mut self, account_ids: Vec) -> Result<()> { + self.call( + "add_to_draft_operators_whitelist", + json!({ + "account_ids": account_ids + }), + ) + .await + } + + async fn remove_from_draft_operators_whitelist(&mut self, account_ids: Vec) -> Result<()> { + self.call( + "remove_from_draft_operators_whitelist", + json!({ + "account_ids": account_ids + }), + ) + .await + } + + async fn create_draft_group(&mut self) -> Result { + self.call("create_draft_group", ()).await + } + + async fn create_draft(&mut self, draft: Draft) -> Result { + self.call( + "create_draft", + json!({ + "draft": draft + }), + ) + .await + } + + async fn create_drafts(&mut self, drafts: Vec) -> Result> { + self.call( + "create_drafts", + json!({ + "drafts": drafts + }), + ) + .await + } + + async fn convert_draft(&mut self, draft_id: DraftIndex) -> Result { + self.call( + "convert_draft", + json!({ + "draft_id": draft_id + }), + ) + .await + } + + async fn discard_draft_group(&mut self, draft_group_id: DraftGroupIndex) -> Result<()> { + self.call( + "discard_draft_group", + json!({ + "draft_group_id": draft_group_id + }), + ) + .await + } + + async fn delete_drafts(&mut self, draft_ids: Vec) -> Result<()> { + self.call( + "delete_drafts", + json!({ + "draft_ids": draft_ids + }), + ) + .await + } + + async fn make_lockup_terminable(&mut self, beneficiary_id: AccountId, lockup_index: LockupIndex) -> Result<()> { + println!("▶️ make_lockup_terminable"); + + let account = self.user_account().expect("User account is required"); + let result = account + .call(self.contract.id(), "make_lockup_terminable") + .args_json(json!({ + "beneficiary_id": beneficiary_id, + "lockup_index": lockup_index, + })) + .max_gas() + .deposit(NearToken::from_yoctonear(1)) + .transact() + .await?; + + log_result(result.clone()); + + Ok(()) + } +} + +#[async_trait] +impl AdjustApiIntegration for LockupContract<'_> { + async fn adjust(&mut self, beneficiary_id: AccountId, lockup_index: LockupIndex) -> Result<()> { + println!("▶️ recall"); + + let account = self.user_account().expect("User account is required"); + let result = account + .call(self.contract.id(), "adjust") + .args_json(json!({ + "beneficiary_id": beneficiary_id, + "lockup_index": lockup_index, + })) + .max_gas() + .deposit(NearToken::from_yoctonear(1)) + .transact() + .await?; + + log_result(result.clone()); + + Ok(()) + } + + async fn revoke(&mut self, beneficiary_id: AccountId, lockup_indices: Vec) -> Result<()> { + println!("▶️ revoke"); + + let account = self.user_account().expect("User account is required"); + let result = account + .call(self.contract.id(), "revoke") + .args_json(json!({ + "beneficiary_id": beneficiary_id, + "lockup_indices": lockup_indices, + })) + .max_gas() + .deposit(NearToken::from_yoctonear(1)) + .transact() + .await?; + + log_result(result.clone()); + + Ok(()) + } +} + +#[async_trait] +impl LockupViewApiIntegration for LockupContract<'_> { + async fn get_token_account_id(&self) -> Result { + todo!() + } + + async fn get_account_lockups(&self, account_id: AccountId) -> Result> { + self.call( + "get_account_lockups", + json!({ + "account_id": account_id + }), + ) + .await + } + + async fn get_lockup(&self, index: LockupIndex) -> Result> { + todo!() + } + + async fn get_lockups(&self, indices: Vec) -> Result> { + todo!() + } + + async fn get_num_lockups(&self) -> Result { + todo!() + } + + async fn get_lockups_paged( + &self, + from_index: Option, + limit: Option, + ) -> Result> { + todo!() + } + + async fn get_deposit_whitelist(&self) -> Result> { + todo!() + } + + async fn get_draft_operators_whitelist(&self) -> Result> { + todo!() + } + + async fn hash_schedule(&self, schedule: Schedule) -> Result { + todo!() + } + + async fn validate_schedule( + &self, + schedule: Schedule, + total_balance: WrappedBalance, + termination_schedule: Option, + ) -> Result<()> { + todo!() + } + + async fn get_next_draft_group_id(&self) -> Result { + todo!() + } + + async fn get_next_draft_id(&self) -> Result { + todo!() + } + + async fn get_num_draft_groups(&self) -> Result { + todo!() + } + + async fn get_draft_group(&self, index: DraftGroupIndex) -> Result> { + todo!() + } + + async fn get_draft_groups_paged( + &self, + from_index: Option, + to_index: Option, + ) -> Result> { + todo!() + } + + async fn get_draft(&self, index: DraftIndex) -> Result> { + todo!() + } + + async fn get_drafts(&self, indices: Vec) -> Result> { + todo!() + } + + async fn get_version(&self) -> Result { + todo!() + } +} + +#[async_trait] +impl<'a> UpdateApiIntegration for LockupContract<'a> { + async fn update_contract(&mut self, code: Vec) -> Result<()> { + println!("▶️ update_contract"); + + let result = self + .user_account() + .unwrap() + .call(self.contract().id(), "update_contract") + .args(code) + .max_gas() + .transact() + .await? + .into_result()?; + + println!("Result: {result:?}"); + + Ok(()) + } + + async fn set_multisig(&mut self, multisig: AccountId) -> Result<()> { + self.call( + "set_multisig", + json!({ + "multisig": multisig + }), + ) + .await + } +} + +impl<'a> IntegrationContract<'a> for LockupContract<'a> { + fn with_contract(contract: &'a Contract) -> Self { + Self { + contract, + account: None, + } + } + + fn with_user(&mut self, account: &Account) -> &mut Self { + self.account = account.clone().into(); + self + } + + fn user_account(&self) -> Option { + self.account.clone() + } + + fn contract(&self) -> &'a Contract { + self.contract + } +} + +pub trait GetContractAccount { + fn contract_account(&self) -> AccountId; +} + +impl<'a, T: IntegrationContract<'a>> GetContractAccount for T { + fn contract_account(&self) -> AccountId { + self.contract().as_account().to_near() + } +} diff --git a/integration-tests/src/termination.rs b/integration-tests/src/termination.rs new file mode 100644 index 0000000..96d650d --- /dev/null +++ b/integration-tests/src/termination.rs @@ -0,0 +1,232 @@ +#![cfg(test)] + +use async_trait::async_trait; +use integration_utils::{integration_contract::IntegrationContract, misc::ToNear}; +use model::{ + adjust_api::AdjustApiIntegration, + ft_token_receiver::FtMessage, + lockup::LockupCreate, + lockup_api::LockupApiIntegration, + schedule::{Checkpoint, Schedule}, + view_api::LockupViewApiIntegration, +}; +use near_sdk::{json_types::U128, serde_json, serde_json::json, AccountId, Balance}; +use near_workspaces::types::NearToken; +use sweat_integration::SweatFt; +use sweat_model::{FungibleTokenCoreIntegration, StorageManagementIntegration, SweatApiIntegration}; + +use crate::{ + common::log_result, + context::{prepare_contract, IntegrationContext}, +}; + +#[tokio::test] +async fn test_adjust() -> anyhow::Result<()> { + let mut context = prepare_contract().await?; + + let alice = context.alice().await?; + let bob = context.bob().await?; + let manager = context.manager().await?; + + let total_amount: Balance = 1_000_000_000_000_000; + + context + .ft_contract() + .with_user(&manager) + .storage_deposit(Some(alice.to_near()), None) + .await?; + context + .ft_contract() + .with_user(&manager) + .storage_deposit(Some(bob.to_near()), None) + .await?; + + context + .ft_contract() + .with_user(context.ft_contract().contract().as_account()) + .tge_mint(&manager.to_near(), U128(total_amount * 2)) + .await?; + + let now = (context.lockup().block_timestamp_ms().await?) / 1_000; + + let lockup_term = 2 * 60 * 60; + let cliff_end_timestamp = now + 365 * 24 * 60 * 60; + let lockup = FtMessage::LockupCreate(LockupCreate { + account_id: alice.to_near(), + schedule: Schedule(vec![ + Checkpoint { + timestamp: cliff_end_timestamp as _, + balance: 0, + }, + Checkpoint { + timestamp: (cliff_end_timestamp + lockup_term) as _, + balance: total_amount, + }, + ]), + vesting_schedule: None, + is_adjustable: true, + }); + let lockup_json = serde_json::to_string(&lockup).unwrap(); + + context + .ft_contract() + .with_user(&manager) + .ft_transfer_call_raw( + context.lockup().contract().as_account().to_near(), + U128(total_amount), + None, + lockup_json, + ) + .await?; + + let alice_lockups = context.lockup().get_account_lockups(alice.to_near()).await?; + let (index, lockup) = alice_lockups.first().expect("Must be a single index"); + let lockup_balance_before_recall = lockup.total_balance; + let manager_balance_before_recall = context.ft_contract().ft_balance_of(manager.to_near()).await?.0; + + context + .lockup() + .with_user(&manager) + .adjust(manager.to_near(), *index) + .await?; + + let alice_lockups = context.lockup().get_account_lockups(alice.to_near()).await?; + let lockup_balance_after_recall = alice_lockups.first().expect("Must be a single index").1.total_balance; + let manager_balance_after_recall = context.ft_contract().ft_balance_of(manager.to_near()).await?.0; + + let lockup_balance_diff = lockup_balance_before_recall - lockup_balance_after_recall; + let manager_balance_diff = manager_balance_after_recall - manager_balance_before_recall; + + assert_eq!(lockup_balance_diff, manager_balance_diff); + + Ok(()) +} + +#[tokio::test] +async fn test_revoke() -> anyhow::Result<()> { + let mut context = prepare_contract().await?; + + let alice = context.alice().await?; + let bob = context.bob().await?; + let manager = context.manager().await?; + + let total_amount: Balance = 1_000_000_000_000_000; + + context + .ft_contract() + .with_user(&manager) + .storage_deposit(Some(alice.to_near()), None) + .await?; + context + .ft_contract() + .with_user(&manager) + .storage_deposit(Some(bob.to_near()), None) + .await?; + + context + .ft_contract() + .with_user(context.ft_contract().contract().as_account()) + .tge_mint(&manager.to_near(), U128(total_amount * 2)) + .await?; + + let now = (context.lockup().block_timestamp_ms().await?) / 1_000; + + let lockup_term = 2 * 60 * 60; + let cliff_end_timestamp = now + 365 * 24 * 60 * 60; + let lockup = FtMessage::LockupCreate(LockupCreate { + account_id: alice.to_near(), + schedule: Schedule(vec![ + Checkpoint { + timestamp: cliff_end_timestamp as _, + balance: 0, + }, + Checkpoint { + timestamp: (cliff_end_timestamp + lockup_term) as _, + balance: total_amount, + }, + ]), + vesting_schedule: None, + is_adjustable: true, + }); + let lockup_json = serde_json::to_string(&lockup).unwrap(); + + context + .ft_contract() + .with_user(&manager) + .ft_transfer_call_raw( + context.lockup().contract().as_account().to_near(), + U128(total_amount), + None, + lockup_json, + ) + .await?; + + let alice_lockups = context.lockup().get_account_lockups(alice.to_near()).await?; + let (index, _) = alice_lockups.first().expect("Must be a single index"); + let manager_balance_before_recall = context.ft_contract().ft_balance_of(manager.to_near()).await?.0; + + context + .lockup() + .with_user(&manager) + .revoke(manager.to_near(), vec![*index]) + .await?; + + let alice_lockups = context.lockup().get_account_lockups(alice.to_near()).await?; + assert!(alice_lockups.is_empty()); + + let manager_balance_after_recall = context.ft_contract().ft_balance_of(manager.to_near()).await?.0; + + let manager_balance_diff = manager_balance_after_recall - manager_balance_before_recall; + + assert_eq!(total_amount, manager_balance_diff); + + Ok(()) +} + +#[async_trait] +trait FtContractHelper { + async fn ft_transfer_call_raw( + &mut self, + receiver_id: AccountId, + amount: U128, + memo: Option, + msg: String, + ) -> anyhow::Result; +} + +#[async_trait] +impl FtContractHelper for SweatFt<'_> { + async fn ft_transfer_call_raw( + &mut self, + receiver_id: AccountId, + amount: U128, + memo: Option, + msg: String, + ) -> anyhow::Result { + println!( + "▶️ Transfer {:?} fungible tokens to {} with message: {}", + amount, receiver_id, msg + ); + + let args = json!({ + "receiver_id": receiver_id, + "amount": amount, + "memo": memo, + "msg": msg.to_string(), + }); + + let result = self + .user_account() + .unwrap() + .call(self.contract().id(), "ft_transfer_call") + .args_json(args) + .max_gas() + .deposit(NearToken::from_yoctonear(1)) + .transact() + .await?; + + log_result(result.clone()); + + Ok(result.json()?) + } +} diff --git a/integration-tests/src/update/mod.rs b/integration-tests/src/update/mod.rs new file mode 100644 index 0000000..4290168 --- /dev/null +++ b/integration-tests/src/update/mod.rs @@ -0,0 +1,2 @@ +pub mod multisig; +pub mod simple; diff --git a/integration-tests/src/update/multisig.rs b/integration-tests/src/update/multisig.rs new file mode 100644 index 0000000..b88ae55 --- /dev/null +++ b/integration-tests/src/update/multisig.rs @@ -0,0 +1,210 @@ +#![cfg(test)] + +use std::str::FromStr; + +use anyhow::Result; +use integration_utils::integration_contract::IntegrationContract; +use model::update::UpdateApiIntegration; +use multisig_integration::Multisig; +use multisig_model::{ + api::{MultisigApiIntegration, MultisigViewIntegration}, + data::{FunctionCallPermission, MultiSigRequest, MultiSigRequestAction}, +}; +use near_sdk::json_types::U128; +use near_workspaces::{ + types::{Gas, KeyType, NearToken, SecretKey}, + Account, AccountId, +}; + +use crate::{ + context::{prepare_contract, Context, IntegrationContext}, + lockup_interface::GetContractAccount, + utils::{load_wasm, AccountExtension}, +}; + +#[tokio::test] +async fn update_contract() -> Result<()> { + println!("👷🏽 Run update contract test"); + + let mut context = prepare_contract().await?; + + let multisig_account_id = context.multisig().contract_account(); + + context.lockup().set_multisig(multisig_account_id.clone()).await?; + + assert_eq!(0, context.multisig().get_num_confirmations().await?); + + let signers = [ + SecretKey::from_random(KeyType::ED25519), + SecretKey::from_random(KeyType::ED25519), + SecretKey::from_random(KeyType::ED25519), + ]; + + let signers_accounts: Vec<_> = signers + .iter() + .map(|key| { + Account::from_secret_key( + AccountId::from_str(&multisig_account_id.to_string()).unwrap(), + key.clone(), + &mut context.worker, + ) + }) + .collect(); + + add_keys(context.multisig(), &signers).await?; + + context + .multisig() + .add_request_and_confirm(MultiSigRequest { + receiver_id: context.multisig().contract_account(), + actions: vec![MultiSigRequestAction::SetNumConfirmations { num_confirmations: 2 }], + }) + .await?; + + assert_eq!(2, context.multisig().get_num_confirmations().await?); + + redeploy_multisig(&mut context, &signers_accounts).await?; + + let wasm = load_wasm("../res/hodl_lockup.wasm")?; + + update_with_method_call(&mut context, &signers_accounts, &wasm).await?; + + update_with_multisig(&mut context, &signers_accounts, &wasm).await?; + + Ok(()) +} + +async fn update_with_method_call(context: &mut Context, signers_accounts: &[Account], wasm: &[u8]) -> Result<()> { + println!("👷🏽 update_with_method_call"); + + dbg!(signers_accounts[0].near_balance().await?); + + let update_request = context + .multisig() + .with_user(&signers_accounts[0]) + .add_request_and_confirm(MultiSigRequest { + receiver_id: context.lockup().contract_account(), + actions: vec![MultiSigRequestAction::FunctionCall { + method_name: "update_contract_kok".to_string(), + args: wasm.to_vec().into(), + deposit: U128(NearToken::from_near(10).as_yoctonear()), + gas: Gas::from_tgas(300).as_gas().into(), + }], + }) + .await?; + + let confirmations = context.multisig().get_confirmations(update_request).await?; + + assert_eq!(1, confirmations.len()); + + dbg!(signers_accounts[1].near_balance().await?); + + let confirmed = context + .multisig() + .with_user(&signers_accounts[1]) + .confirm(update_request) + .await?; + + dbg!(confirmed); + + let confirmations = context.multisig().get_confirmations(update_request).await?; + + assert_eq!(2, confirmations.len()); + + Ok(()) +} + +async fn update_with_multisig(context: &mut Context, signers_accounts: &[Account], wasm: &[u8]) -> Result<()> { + println!("👷🏽 update_with_multisig"); + + let update_request = context + .multisig() + .with_user(&signers_accounts[0]) + .add_request_and_confirm(MultiSigRequest { + receiver_id: context.lockup().contract_account(), + actions: vec![MultiSigRequestAction::DeployContract { + code: wasm.to_vec().into(), + }], + }) + .await?; + + let confirmed = context + .multisig() + .with_user(&signers_accounts[1]) + .confirm(update_request) + .await?; + + dbg!(&confirmed); + + Ok(()) +} + +async fn redeploy_multisig(context: &mut Context, signers_accounts: &[Account]) -> Result<()> { + println!("👷🏽 redeploy_multisig"); + + let wasm = load_wasm("../res/multisig.wasm")?; + + let request_id = context + .multisig() + .with_user(&signers_accounts[0]) + .add_request(MultiSigRequest { + receiver_id: context.multisig().contract_account(), + actions: vec![MultiSigRequestAction::DeployContract { + code: wasm.to_vec().into(), + }], + }) + .await?; + + assert_eq!([request_id], context.multisig().list_request_ids().await?.as_slice()); + assert_eq!(0, context.multisig().get_confirmations(request_id).await?.len()); + + context + .multisig() + .with_user(&signers_accounts[0]) + .confirm(request_id) + .await?; + + assert_eq!([request_id], context.multisig().list_request_ids().await?.as_slice()); + assert_eq!(1, context.multisig().get_confirmations(request_id).await?.len()); + + context + .multisig() + .with_user(&signers_accounts[1]) + .confirm(request_id) + .await?; + + assert!(context.multisig().list_request_ids().await?.is_empty()); + + Ok(()) +} + +async fn add_keys(mut multisig: Multisig<'_>, signers: &[SecretKey]) -> Result<()> { + let multisig_keys_actions: Vec<_> = signers + .into_iter() + .map(|key| MultiSigRequestAction::AddKey { + public_key: to_near_pk(key.public_key()), + permission: FunctionCallPermission { + allowance: U128(NearToken::from_near(5).as_yoctonear()).into(), + receiver_id: multisig.contract_account(), + method_names: ["add_request", "add_request_and_confirm", "delete_request", "confirm"] + .into_iter() + .map(Into::into) + .collect(), + } + .into(), + }) + .collect(); + + multisig + .add_request_and_confirm(MultiSigRequest { + receiver_id: multisig.contract_account(), + actions: multisig_keys_actions, + }) + .await?; + + Ok(()) +} + +fn to_near_pk(key: near_workspaces::types::PublicKey) -> near_sdk::PublicKey { + near_sdk::PublicKey::from_str(&key.to_string()).unwrap() +} diff --git a/integration-tests/src/update/simple.rs b/integration-tests/src/update/simple.rs new file mode 100644 index 0000000..707b3dd --- /dev/null +++ b/integration-tests/src/update/simple.rs @@ -0,0 +1,34 @@ +#![cfg(test)] + +use anyhow::Result; +use integration_utils::{integration_contract::IntegrationContract, misc::ToNear}; +use model::update::UpdateApiIntegration; + +use crate::{ + context::{prepare_contract, IntegrationContext}, + utils::load_wasm, +}; + +#[tokio::test] +async fn simple_update() -> Result<()> { + let mut context = prepare_contract().await?; + + let bob = context.bob().await?; + let alice = context.alice().await?; + + context.lockup().set_multisig(alice.to_near()).await?; + + let wasm = load_wasm("../res/hodl_lockup.wasm")?; + + context.lockup().with_user(&alice).update_contract(wasm.clone()).await?; + + let Err(error) = context.lockup().with_user(&bob).update_contract(wasm).await else { + panic!("Update with non multisig account should fail"); + }; + + assert!(error + .to_string() + .contains("Only multisig account can update the contract")); + + Ok(()) +} diff --git a/integration-tests/src/utils.rs b/integration-tests/src/utils.rs new file mode 100644 index 0000000..1891200 --- /dev/null +++ b/integration-tests/src/utils.rs @@ -0,0 +1,24 @@ +use std::{env, fs}; + +use anyhow::Result; +use async_trait::async_trait; +use near_workspaces::Account; + +pub fn load_wasm(wasm_path: &str) -> anyhow::Result> { + let current_dir = env::current_dir()?; + let wasm_filepath = fs::canonicalize(current_dir.join(wasm_path))?; + let data = fs::read(wasm_filepath)?; + Ok(data) +} + +#[async_trait] +pub trait AccountExtension { + async fn near_balance(&self) -> Result; +} + +#[async_trait] +impl AccountExtension for Account { + async fn near_balance(&self) -> Result { + Ok(self.view_account().await?.balance.as_near()) + } +} diff --git a/integration-tests/src/utils_interface.rs b/integration-tests/src/utils_interface.rs new file mode 100644 index 0000000..d90eaf7 --- /dev/null +++ b/integration-tests/src/utils_interface.rs @@ -0,0 +1,379 @@ +#![cfg(test)] + +use anyhow::Result; +use async_trait::async_trait; +use integration_utils::{integration_contract::IntegrationContract, misc::ToNear}; +use model::{ + draft::{Draft, DraftGroupIndex, DraftGroupView, DraftIndex, DraftView}, + lockup::{LockupIndex, LockupView}, + lockup_api::LockupApiIntegration, + schedule::Schedule, + update::UpdateApiIntegration, + view_api::{LockupViewApi, LockupViewApiIntegration}, + TimestampSec, WrappedBalance, +}; +use near_sdk::{json_types::Base58CryptoHash, serde_json::json, AccountId}; +use near_workspaces::{result::ExecutionFinalResult, types::NearToken, Account, Contract}; + +use crate::common::log_result; + +pub const UTILS_CONTRACT: &str = "utils"; + +pub struct UtilsContract<'a> { + account: Option, + contract: &'a Contract, +} + +#[async_trait] +impl LockupApiIntegration for LockupContract<'_> { + async fn new( + &self, + token_account_id: AccountId, + deposit_whitelist: Vec, + draft_operators_whitelist: Option>, + ) -> Result<()> { + self.call( + "new", + json!({ + "token_account_id": token_account_id, + "deposit_whitelist": deposit_whitelist, + "draft_operators_whitelist": draft_operators_whitelist + }), + ) + .await + } + + async fn claim(&mut self, amounts: Option)>>) -> Result { + self.call( + "claim", + json!({ + "amounts": amounts + }), + ) + .await + } + + async fn terminate( + &mut self, + lockup_index: LockupIndex, + hashed_schedule: Option, + termination_timestamp: Option, + ) -> Result { + let result = self + .terminate_raw(lockup_index, hashed_schedule, termination_timestamp) + .await? + .json()?; + + Ok(result) + } + + async fn add_to_deposit_whitelist( + &mut self, + account_id: Option, + account_ids: Option>, + ) -> Result<()> { + println!("▶️ add_to_deposit_whitelist"); + + let account = self.user_account().expect("User account is required"); + let result = account + .call(self.contract.id(), "add_to_deposit_whitelist") + .args_json(json!({ + "account_id": account_id, + "account_ids": account_ids, + })) + .max_gas() + .deposit(NearToken::from_yoctonear(1)) + .transact() + .await?; + + log_result(result.clone()); + + Ok(result.json()?) + } + + async fn remove_from_deposit_whitelist( + &mut self, + account_id: Option, + account_ids: Option>, + ) -> Result<()> { + self.call( + "remove_from_deposit_whitelist", + json!({ + "account_id": account_id, + "account_ids": account_ids, + }), + ) + .await + } + + async fn add_to_draft_operators_whitelist(&mut self, account_ids: Vec) -> Result<()> { + self.call( + "add_to_draft_operators_whitelist", + json!({ + "account_ids": account_ids + }), + ) + .await + } + + async fn remove_from_draft_operators_whitelist(&mut self, account_ids: Vec) -> Result<()> { + self.call( + "remove_from_draft_operators_whitelist", + json!({ + "account_ids": account_ids + }), + ) + .await + } + + async fn create_draft_group(&mut self) -> Result { + self.call("create_draft_group", ()).await + } + + async fn create_draft(&mut self, draft: Draft) -> Result { + self.call( + "create_draft", + json!({ + "draft": draft + }), + ) + .await + } + + async fn create_drafts(&mut self, drafts: Vec) -> Result> { + self.call( + "create_drafts", + json!({ + "drafts": drafts + }), + ) + .await + } + + async fn convert_draft(&mut self, draft_id: DraftIndex) -> Result { + self.call( + "convert_draft", + json!({ + "draft_id": draft_id + }), + ) + .await + } + + async fn discard_draft_group(&mut self, draft_group_id: DraftGroupIndex) -> Result<()> { + self.call( + "discard_draft_group", + json!({ + "draft_group_id": draft_group_id + }), + ) + .await + } + + async fn delete_drafts(&mut self, draft_ids: Vec) -> Result<()> { + self.call( + "delete_drafts", + json!({ + "draft_ids": draft_ids + }), + ) + .await + } + + async fn make_lockup_terminable(&mut self, beneficiary_id: AccountId, lockup_index: LockupIndex) -> Result<()> { + println!("▶️ make_lockup_terminable"); + + let account = self.user_account().expect("User account is required"); + let result = account + .call(self.contract.id(), "make_lockup_terminable") + .args_json(json!({ + "beneficiary_id": beneficiary_id, + "lockup_index": lockup_index, + })) + .max_gas() + .deposit(NearToken::from_yoctonear(1)) + .transact() + .await?; + + log_result(result.clone()); + + Ok(()) + } + + async fn recall(&mut self, beneficiary_id: AccountId, lockup_index: LockupIndex) -> Result<()> { + println!("▶️ recall"); + + let account = self.user_account().expect("User account is required"); + let result = account + .call(self.contract.id(), "recall") + .args_json(json!({ + "beneficiary_id": beneficiary_id, + "lockup_index": lockup_index, + })) + .max_gas() + .deposit(NearToken::from_yoctonear(1)) + .transact() + .await?; + + log_result(result.clone()); + + Ok(()) + } +} + +#[async_trait] +impl LockupViewApiIntegration for LockupContract<'_> { + async fn get_token_account_id(&self) -> Result { + todo!() + } + + async fn get_account_lockups(&self, account_id: AccountId) -> Result> { + self.call( + "get_account_lockups", + json!({ + "account_id": account_id + }), + ) + .await + } + + async fn get_lockup(&self, index: LockupIndex) -> Result> { + todo!() + } + + async fn get_lockups(&self, indices: Vec) -> Result> { + todo!() + } + + async fn get_num_lockups(&self) -> Result { + todo!() + } + + async fn get_lockups_paged( + &self, + from_index: Option, + limit: Option, + ) -> Result> { + todo!() + } + + async fn get_deposit_whitelist(&self) -> Result> { + todo!() + } + + async fn get_draft_operators_whitelist(&self) -> Result> { + todo!() + } + + async fn hash_schedule(&self, schedule: Schedule) -> Result { + todo!() + } + + async fn validate_schedule( + &self, + schedule: Schedule, + total_balance: WrappedBalance, + termination_schedule: Option, + ) -> Result<()> { + todo!() + } + + async fn get_next_draft_group_id(&self) -> Result { + todo!() + } + + async fn get_next_draft_id(&self) -> Result { + todo!() + } + + async fn get_num_draft_groups(&self) -> Result { + todo!() + } + + async fn get_draft_group(&self, index: DraftGroupIndex) -> Result> { + todo!() + } + + async fn get_draft_groups_paged( + &self, + from_index: Option, + to_index: Option, + ) -> Result> { + todo!() + } + + async fn get_draft(&self, index: DraftIndex) -> Result> { + todo!() + } + + async fn get_drafts(&self, indices: Vec) -> Result> { + todo!() + } + + async fn get_version(&self) -> Result { + todo!() + } +} + +#[async_trait] +impl<'a> UpdateApiIntegration for LockupContract<'a> { + async fn update_contract(&mut self, code: Vec) -> Result<()> { + println!("▶️ update_contract"); + + let result = self + .user_account() + .unwrap() + .call(self.contract().id(), "update_contract") + .args(code) + .max_gas() + .transact() + .await? + .into_result()?; + + println!("Result: {result:?}"); + + Ok(()) + } + + async fn set_multisig(&mut self, multisig: AccountId) -> Result<()> { + self.call( + "set_multisig", + json!({ + "multisig": multisig + }), + ) + .await + } +} + +impl<'a> IntegrationContract<'a> for LockupContract<'a> { + fn with_contract(contract: &'a Contract) -> Self { + Self { + contract, + account: None, + } + } + + fn with_user(&mut self, account: &Account) -> &mut Self { + self.account = account.clone().into(); + self + } + + fn user_account(&self) -> Option { + self.account.clone() + } + + fn contract(&self) -> &'a Contract { + self.contract + } +} + +pub trait GetContractAccount { + fn contract_account(&self) -> AccountId; +} + +impl<'a, T: IntegrationContract<'a>> GetContractAccount for T { + fn contract_account(&self) -> AccountId { + self.contract().as_account().to_near() + } +} diff --git a/model/Cargo.toml b/model/Cargo.toml new file mode 100644 index 0000000..93b6a45 --- /dev/null +++ b/model/Cargo.toml @@ -0,0 +1,17 @@ +[package] +name = "model" +version = "1.0.0" +publish = false +edition = "2021" + +[lib] +name = "model" + +[dependencies] +uint = { workspace = true } +anyhow = { workspace = true } +async-trait = { workspace = true } + +near-sdk = { workspace = true } + +integration-trait = { workspace = true } diff --git a/model/src/adjust_api.rs b/model/src/adjust_api.rs new file mode 100644 index 0000000..722b0ac --- /dev/null +++ b/model/src/adjust_api.rs @@ -0,0 +1,11 @@ +use integration_trait::make_integration_version; +use near_sdk::{AccountId, PromiseOrValue}; + +use crate::lockup::LockupIndex; + +#[make_integration_version] +pub trait AdjustApi { + fn adjust(&mut self, beneficiary_id: AccountId, lockup_index: LockupIndex) -> PromiseOrValue<()>; + + fn revoke(&mut self, beneficiary_id: AccountId, lockup_indices: Vec) -> PromiseOrValue<()>; +} diff --git a/model/src/draft.rs b/model/src/draft.rs new file mode 100644 index 0000000..6a83e79 --- /dev/null +++ b/model/src/draft.rs @@ -0,0 +1,122 @@ +use near_sdk::{ + borsh::{self, maybestd::collections::HashSet, BorshDeserialize, BorshSerialize}, + env, + serde::{Deserialize, Serialize}, + AccountId, Balance, +}; + +use crate::{ + lockup::{LockupCreate, LockupCreateView}, + util::u128_dec_format, +}; + +pub type DraftGroupIndex = u32; +pub type DraftIndex = u32; + +#[derive(BorshDeserialize, BorshSerialize, Deserialize, Serialize, Debug, PartialEq, Clone)] +#[serde(crate = "near_sdk::serde")] +pub struct Draft { + pub draft_group_id: DraftGroupIndex, + pub lockup_create: LockupCreate, +} + +impl Draft { + pub fn total_balance(&self) -> Balance { + self.lockup_create.schedule.total_balance() + } + + pub fn assert_new_valid(&self) { + let amount = self.lockup_create.schedule.total_balance(); + // any valid near account id will work fine here as a parameter + self.lockup_create + .into_lockup(&env::predecessor_account_id()) + .assert_new_valid(amount); + } +} + +#[derive(Default, BorshDeserialize, BorshSerialize)] +pub struct DraftGroup { + pub total_amount: Balance, + pub payer_id: Option, + pub draft_indices: HashSet, + pub discarded: bool, +} + +impl DraftGroup { + pub fn assert_can_add_draft(&self) { + assert!(!self.discarded, "cannot add draft, draft group is discarded"); + assert!(self.payer_id.is_none(), "cannot add draft, group already funded"); + } + + pub fn assert_can_convert_draft(&self) { + assert!(!self.discarded, "cannot convert draft, draft group is discarded"); + assert!(self.payer_id.is_some(), "cannot convert draft from not funded group"); + } + + pub fn assert_can_fund(&self) { + assert!(!self.discarded, "cannot fund draft, draft group is discarded"); + assert!(self.payer_id.is_none(), "draft group already funded"); + } + + pub fn fund(&mut self, payer_id: &AccountId) { + self.assert_can_fund(); + self.payer_id = Some(payer_id.clone()); + } + + pub fn assert_can_discard(&mut self) { + assert!(!self.discarded, "cannot discard, draft group already discarded"); + assert!(self.payer_id.is_none(), "cannot discard, draft group already funded"); + } + + pub fn discard(&mut self) { + self.assert_can_discard(); + self.discarded = true; + } + + pub fn assert_can_delete_draft(&mut self) { + assert!(self.discarded, "cannot delete draft, draft group is not discarded"); + assert!( + self.payer_id.is_none(), + "cannot delete draft, draft group already funded" + ); + } +} + +#[derive(Serialize, Debug, PartialEq, Deserialize)] +#[serde(crate = "near_sdk::serde")] +pub struct DraftGroupView { + #[serde(with = "u128_dec_format")] + pub total_amount: Balance, + pub payer_id: Option, + pub draft_indices: Vec, + pub discarded: bool, + pub funded: bool, +} + +impl From for DraftGroupView { + fn from(draft_group: DraftGroup) -> Self { + Self { + total_amount: draft_group.total_amount, + payer_id: draft_group.payer_id.clone(), + draft_indices: draft_group.draft_indices.into_iter().collect(), + discarded: draft_group.discarded, + funded: draft_group.payer_id.is_some(), + } + } +} + +#[derive(Serialize, Debug, PartialEq, Deserialize)] +#[serde(crate = "near_sdk::serde")] +pub struct DraftView { + pub draft_group_id: DraftGroupIndex, + pub lockup_create: LockupCreateView, +} + +impl From for DraftView { + fn from(draft: Draft) -> Self { + Self { + draft_group_id: draft.draft_group_id, + lockup_create: draft.lockup_create.into(), + } + } +} diff --git a/model/src/ft_token_receiver.rs b/model/src/ft_token_receiver.rs new file mode 100644 index 0000000..1d0d15e --- /dev/null +++ b/model/src/ft_token_receiver.rs @@ -0,0 +1,19 @@ +use near_sdk::serde::{Deserialize, Serialize}; + +use crate::{draft::DraftGroupIndex, lockup::LockupCreate}; + +#[derive(Serialize, Deserialize)] +#[serde(crate = "near_sdk::serde")] +#[serde(untagged)] +pub enum FtMessage { + LockupCreate(LockupCreate), + DraftGroupFunding(DraftGroupFunding), +} + +#[derive(Serialize, Deserialize)] +#[serde(crate = "near_sdk::serde")] +pub struct DraftGroupFunding { + pub draft_group_id: DraftGroupIndex, + // use remaining gas to try converting drafts + pub try_convert: Option, +} diff --git a/model/src/lib.rs b/model/src/lib.rs new file mode 100644 index 0000000..3ee46bf --- /dev/null +++ b/model/src/lib.rs @@ -0,0 +1,26 @@ +use near_sdk::{json_types::U128, AccountId}; + +pub mod adjust_api; +pub mod draft; +pub mod ft_token_receiver; +pub mod lockup; +pub mod lockup_api; +pub mod migration; +pub mod schedule; +pub mod termination; +pub mod update; +pub mod util; +pub mod view_api; + +pub type WrappedBalance = U128; +pub type TimestampSec = u32; +pub type TokenAccountId = AccountId; + +pub mod u256 { + #![allow(clippy::doc_markdown)] + #![allow(clippy::assign_op_pattern)] + + uint::construct_uint! { + pub struct U256(4); + } +} diff --git a/model/src/lockup.rs b/model/src/lockup.rs new file mode 100644 index 0000000..2d9c612 --- /dev/null +++ b/model/src/lockup.rs @@ -0,0 +1,215 @@ +use near_sdk::{ + borsh::{self, BorshDeserialize, BorshSerialize}, + serde::{Deserialize, Serialize}, + AccountId, Balance, +}; + +use crate::{ + schedule::Schedule, + termination::{TerminationConfig, VestingConditions}, + util::{current_timestamp_sec, u128_dec_format}, + TimestampSec, WrappedBalance, +}; + +pub type LockupIndex = u32; + +#[derive(Serialize, Deserialize, Debug, PartialEq)] +#[serde(crate = "near_sdk::serde")] +pub struct LockupClaim { + pub index: LockupIndex, + pub claim_amount: WrappedBalance, + pub is_final: bool, +} + +#[derive(BorshDeserialize, BorshSerialize, Deserialize, Debug, PartialEq, Clone)] +#[serde(crate = "near_sdk::serde")] +pub struct Lockup { + pub account_id: AccountId, + pub schedule: Schedule, + + #[serde(default)] + #[serde(with = "u128_dec_format")] + pub claimed_balance: Balance, + /// An optional configuration that allows vesting/lockup termination. + pub termination_config: Option, + pub is_adjustable: bool, +} + +impl Lockup { + pub fn new_unlocked_since(account_id: AccountId, total_balance: Balance, timestamp: TimestampSec) -> Self { + Self { + account_id, + schedule: Schedule::new_unlocked_since(total_balance, timestamp), + claimed_balance: 0, + termination_config: None, + is_adjustable: false, + } + } + + pub fn new_unlocked(account_id: AccountId, total_balance: Balance) -> Self { + Self::new_unlocked_since(account_id, total_balance, 1) + } + + pub fn claim(&mut self, index: LockupIndex, claim_amount: Balance) -> LockupClaim { + let unlocked_balance = self.schedule.unlocked_balance(current_timestamp_sec()); + let balance_claimed_new = self + .claimed_balance + .checked_add(claim_amount) + .expect("attempt to add with overflow"); + assert!( + unlocked_balance >= balance_claimed_new, + "too big claim_amount for lockup {index}", + ); + + self.claimed_balance = balance_claimed_new; + LockupClaim { + index, + claim_amount: claim_amount.into(), + is_final: balance_claimed_new == self.schedule.total_balance(), + } + } + + pub fn assert_new_valid(&self, total_balance: Balance) { + assert_eq!( + self.claimed_balance, 0, + "The initial lockup claimed balance should be 0" + ); + self.schedule.assert_valid(total_balance); + + if let Some(termination_config) = &self.termination_config { + match &termination_config.vesting_schedule { + VestingConditions::SameAsLockupSchedule => { + // Ok, using lockup schedule. + } + VestingConditions::Hash(_hash) => { + // Ok, using unknown hash. Can't verify. + } + VestingConditions::Schedule(schedule) => { + schedule.assert_valid(total_balance); + self.schedule.assert_valid_termination_schedule(schedule); + } + } + } + } +} + +#[derive(BorshDeserialize, BorshSerialize, Deserialize, Serialize, Debug, PartialEq, Clone)] +#[serde(crate = "near_sdk::serde")] +pub struct LockupCreate { + pub account_id: AccountId, + pub schedule: Schedule, + pub vesting_schedule: Option, + pub is_adjustable: bool, +} + +#[cfg(not(target_arch = "wasm32"))] +impl LockupCreate { + pub fn new_unlocked(account_id: AccountId, total_balance: Balance) -> Self { + Self { + account_id, + schedule: Schedule::new_unlocked(total_balance), + vesting_schedule: None, + is_adjustable: false, + } + } +} + +impl LockupCreate { + pub fn into_lockup(&self, payer_id: &AccountId) -> Lockup { + let vesting_schedule = self.vesting_schedule.clone(); + Lockup { + account_id: self.account_id.clone(), + schedule: self.schedule.clone(), + claimed_balance: 0, + termination_config: vesting_schedule.map(|vesting_schedule| TerminationConfig { + beneficiary_id: payer_id.clone(), + vesting_schedule, + }), + is_adjustable: self.is_adjustable, + } + } +} + +#[derive(Serialize, Debug, PartialEq, Deserialize)] +#[serde(crate = "near_sdk::serde")] +pub struct LockupView { + pub account_id: AccountId, + pub schedule: Schedule, + + #[serde(default)] + #[serde(with = "u128_dec_format")] + pub claimed_balance: Balance, + /// An optional configuration that allows vesting/lockup termination. + pub termination_config: Option, + + #[serde(with = "u128_dec_format")] + pub total_balance: Balance, + #[serde(with = "u128_dec_format")] + pub unclaimed_balance: Balance, + /// The current timestamp + pub timestamp: TimestampSec, +} + +impl From for LockupView { + fn from(lockup: Lockup) -> Self { + let total_balance = lockup.schedule.total_balance(); + let timestamp = current_timestamp_sec(); + let unclaimed_balance = lockup.schedule.unlocked_balance(timestamp) - lockup.claimed_balance; + let Lockup { + account_id, + schedule, + claimed_balance, + termination_config, + is_adjustable: _, + } = lockup; + Self { + account_id, + schedule, + claimed_balance, + termination_config, + total_balance, + unclaimed_balance, + timestamp, + } + } +} + +#[derive(Serialize, Debug, PartialEq, Deserialize)] +#[serde(crate = "near_sdk::serde")] +pub struct LockupCreateView { + pub account_id: AccountId, + pub schedule: Schedule, + pub vesting_schedule: Option, + + #[serde(with = "u128_dec_format")] + pub claimed_balance: Balance, + #[serde(with = "u128_dec_format")] + pub total_balance: Balance, + #[serde(with = "u128_dec_format")] + pub unclaimed_balance: Balance, + /// The current timestamp + pub timestamp: TimestampSec, +} + +impl From for LockupCreateView { + fn from(lockup_create: LockupCreate) -> Self { + let total_balance = lockup_create.schedule.total_balance(); + let timestamp = current_timestamp_sec(); + let unclaimed_balance = lockup_create.schedule.unlocked_balance(timestamp); + let LockupCreate { + account_id, + schedule, + vesting_schedule, + is_adjustable: _, + } = lockup_create; + Self { + account_id, + schedule, + vesting_schedule, + claimed_balance: 0, + total_balance, + unclaimed_balance, + timestamp, + } + } +} diff --git a/model/src/lockup_api.rs b/model/src/lockup_api.rs new file mode 100644 index 0000000..b64cef4 --- /dev/null +++ b/model/src/lockup_api.rs @@ -0,0 +1,51 @@ +use integration_trait::make_integration_version; +use near_sdk::{AccountId, PromiseOrValue}; + +use crate::{ + draft::{Draft, DraftGroupIndex, DraftIndex}, + lockup::LockupIndex, + schedule::Schedule, + TimestampSec, WrappedBalance, +}; + +#[make_integration_version] +pub trait LockupApi { + fn new( + token_account_id: AccountId, + deposit_whitelist: Vec, + draft_operators_whitelist: Option>, + ) -> Self; + + fn claim(&mut self, amounts: Option)>>) -> PromiseOrValue; + + fn terminate( + &mut self, + lockup_index: LockupIndex, + hashed_schedule: Option, + termination_timestamp: Option, + ) -> PromiseOrValue; + + // preserving both options for API compatibility + fn add_to_deposit_whitelist(&mut self, account_id: Option, account_ids: Option>); + + // preserving both options for API compatibility + fn remove_from_deposit_whitelist(&mut self, account_id: Option, account_ids: Option>); + + fn add_to_draft_operators_whitelist(&mut self, account_ids: Vec); + + fn remove_from_draft_operators_whitelist(&mut self, account_ids: Vec); + + fn create_draft_group(&mut self) -> DraftGroupIndex; + + fn create_draft(&mut self, draft: Draft) -> DraftIndex; + + fn create_drafts(&mut self, drafts: Vec) -> Vec; + + fn convert_draft(&mut self, draft_id: DraftIndex) -> LockupIndex; + + fn discard_draft_group(&mut self, draft_group_id: DraftGroupIndex); + + fn delete_drafts(&mut self, draft_ids: Vec); + + fn make_lockup_terminable(&mut self, beneficiary_id: AccountId, lockup_index: LockupIndex); +} diff --git a/model/src/migration.rs b/model/src/migration.rs new file mode 100644 index 0000000..25c6384 --- /dev/null +++ b/model/src/migration.rs @@ -0,0 +1,58 @@ +use std::collections::HashSet; + +use near_sdk::{ + borsh::{self, BorshDeserialize, BorshSerialize}, + collections::{LookupMap, UnorderedMap, UnorderedSet, Vector}, + near_bindgen, + serde::Deserialize, + AccountId, Balance, PanicOnDefault, +}; + +use crate::{ + draft::{Draft, DraftGroup, DraftGroupIndex, DraftIndex}, + lockup::LockupIndex, + schedule::Schedule, + termination::TerminationConfig, + util::u128_dec_format, + TokenAccountId, +}; + +#[near_bindgen] +#[derive(BorshDeserialize, BorshSerialize, PanicOnDefault)] +pub struct ContractDeprecated { + pub token_account_id: TokenAccountId, + + pub lockups: Vector, + + pub account_lockups: LookupMap>, + + /// account ids that can perform all actions: + /// - manage deposit_whitelist + /// - manage drafts, draft_groups + /// - create lockups, terminate lockups, fund draft_groups + pub deposit_whitelist: UnorderedSet, + + /// account ids that can perform all actions on drafts: + /// - manage drafts, draft_groups + pub draft_operators_whitelist: UnorderedSet, + + pub next_draft_id: DraftIndex, + pub drafts: LookupMap, + pub next_draft_group_id: DraftGroupIndex, + pub draft_groups: UnorderedMap, + + pub multisig: Option, +} + +#[derive(BorshDeserialize, BorshSerialize, Deserialize, Debug, PartialEq, Clone)] +#[serde(crate = "near_sdk::serde")] +pub struct LockupDeprecated { + pub account_id: AccountId, + pub schedule: Schedule, + + #[serde(default)] + #[serde(with = "u128_dec_format")] + pub claimed_balance: Balance, + /// An optional configuration that allows vesting/lockup termination. + pub termination_config: Option, +} diff --git a/src/schedule.rs b/model/src/schedule.rs similarity index 81% rename from src/schedule.rs rename to model/src/schedule.rs index 0f4bed0..bf8d10f 100644 --- a/src/schedule.rs +++ b/model/src/schedule.rs @@ -1,8 +1,14 @@ -use crate::*; +use near_sdk::{ + borsh::{self, BorshDeserialize, BorshSerialize}, + env, + serde::{Deserialize, Serialize}, + Balance, CryptoHash, +}; -#[derive(BorshDeserialize, BorshSerialize, Serialize, Deserialize, Clone)] +use crate::{u256::U256, util::u128_dec_format, TimestampSec}; + +#[derive(BorshDeserialize, BorshSerialize, Serialize, Deserialize, Clone, Debug, PartialEq)] #[serde(crate = "near_sdk::serde")] -#[cfg_attr(not(target_arch = "wasm32"), derive(Debug, PartialEq))] pub struct Checkpoint { /// The unix-timestamp in seconds since the epoch. pub timestamp: TimestampSec, @@ -10,16 +16,12 @@ pub struct Checkpoint { pub balance: Balance, } -#[derive(BorshDeserialize, BorshSerialize, Serialize, Deserialize, Clone)] +#[derive(BorshDeserialize, BorshSerialize, Serialize, Deserialize, Clone, Debug, PartialEq)] #[serde(crate = "near_sdk::serde")] -#[cfg_attr(not(target_arch = "wasm32"), derive(Debug, PartialEq))] pub struct Schedule(pub Vec); impl Schedule { - pub fn new_zero_balance_from_to( - start_timestamp: TimestampSec, - finish_timestamp: TimestampSec, - ) -> Self { + pub fn new_zero_balance_from_to(start_timestamp: TimestampSec, finish_timestamp: TimestampSec) -> Self { assert!(finish_timestamp > start_timestamp, "Invariant"); Self(vec![ @@ -42,13 +44,12 @@ impl Schedule { balance: 0, }, Checkpoint { - timestamp: timestamp, + timestamp, balance: total_balance, }, ]) } - #[cfg(not(target_arch = "wasm32"))] pub fn new_unlocked(total_balance: Balance) -> Self { Self::new_unlocked_since(total_balance, 1) } @@ -61,13 +62,18 @@ impl Schedule { "The first checkpoint balance should be 0" ); for i in 1..self.0.len() { - assert!(self.0[i - 1].timestamp < self.0[i].timestamp, "The timestamp of checkpoint #{} should be less than the timestamp of the next checkpoint", i - 1); - assert!(self.0[i - 1].balance <= self.0[i].balance, "The balance of checkpoint #{} should be not greater than the balance of the next checkpoint", i - 1); + assert!( + self.0[i - 1].timestamp < self.0[i].timestamp, + "The timestamp of checkpoint #{} should be less than the timestamp of the next checkpoint", + i - 1 + ); + assert!( + self.0[i - 1].balance <= self.0[i].balance, + "The balance of checkpoint #{} should be not greater than the balance of the next checkpoint", + i - 1 + ); } - assert!( - self.total_balance() > 0, - "expected total balance to be positive", - ); + assert!(self.total_balance() > 0, "expected total balance to be positive",); assert_eq!( self.total_balance(), total_balance, @@ -121,8 +127,7 @@ impl Schedule { let total_duration = next_checkpoint.timestamp - checkpoint.timestamp; let passed_duration = current_timestamp - checkpoint.timestamp; checkpoint.balance - + (U256::from(passed_duration) - * U256::from(next_checkpoint.balance - checkpoint.balance) + + (U256::from(passed_duration) * U256::from(next_checkpoint.balance - checkpoint.balance) / U256::from(total_duration)) .as_u128() } @@ -132,7 +137,7 @@ impl Schedule { } /// Terminates the lockup schedule earlier. - /// Assumes new_total_balance is not greater than the current total balance. + /// Assumes `new_total_balance` is not greater than the current total balance. pub fn terminate(&mut self, new_total_balance: Balance, finish_timestamp: TimestampSec) { if new_total_balance == 0 { // finish_timestamp is a hint, only used for fully unvested schedules @@ -147,10 +152,7 @@ impl Schedule { self.0 = Self::new_zero_balance_from_to(start_timestamp, finish_timestamp).0; return; } - assert!( - new_total_balance <= self.0.last().unwrap().balance, - "Invariant" - ); + assert!(new_total_balance <= self.0.last().unwrap().balance, "Invariant"); while let Some(checkpoint) = self.0.pop() { if self.0.last().unwrap().balance < new_total_balance { let prev_checkpoint = self.0.last().unwrap().clone(); @@ -159,8 +161,7 @@ impl Schedule { let required_balance_diff = new_total_balance - prev_checkpoint.balance; // Computing the new timestamp rounding up let new_timestamp = prev_checkpoint.timestamp - + ((U256::from(timestamp_diff) * U256::from(required_balance_diff) - + U256::from(balance_diff - 1)) + + ((U256::from(timestamp_diff) * U256::from(required_balance_diff) + U256::from(balance_diff - 1)) / U256::from(balance_diff)) .as_u32(); self.0.push(Checkpoint { diff --git a/model/src/termination.rs b/model/src/termination.rs new file mode 100644 index 0000000..1c3ce8b --- /dev/null +++ b/model/src/termination.rs @@ -0,0 +1,26 @@ +use near_sdk::{ + borsh::{self, BorshDeserialize, BorshSerialize}, + json_types::Base58CryptoHash, + serde::{Deserialize, Serialize}, + AccountId, +}; + +use crate::schedule::Schedule; + +#[derive(BorshDeserialize, BorshSerialize, Serialize, Deserialize, Clone, Debug, PartialEq)] +#[serde(crate = "near_sdk::serde")] +pub enum VestingConditions { + SameAsLockupSchedule, + Hash(Base58CryptoHash), + Schedule(Schedule), +} + +#[derive(BorshDeserialize, BorshSerialize, Serialize, Deserialize, Debug, PartialEq, Clone)] +#[serde(crate = "near_sdk::serde")] +pub struct TerminationConfig { + /// The account ID who paid for the lockup creation + /// and will receive unvested balance upon termination + pub beneficiary_id: AccountId, + /// An optional vesting schedule + pub vesting_schedule: VestingConditions, +} diff --git a/model/src/update.rs b/model/src/update.rs new file mode 100644 index 0000000..3e695c8 --- /dev/null +++ b/model/src/update.rs @@ -0,0 +1,9 @@ +use integration_trait::make_integration_version; +use near_sdk::{AccountId, PromiseOrValue}; + +#[make_integration_version] +pub trait UpdateApi { + #[update] + fn update_contract(&mut self) -> PromiseOrValue<()>; + fn set_multisig(&mut self, multisig: AccountId); +} diff --git a/src/util.rs b/model/src/util.rs similarity index 50% rename from src/util.rs rename to model/src/util.rs index bcb7fab..51e4e9a 100644 --- a/src/util.rs +++ b/model/src/util.rs @@ -1,16 +1,17 @@ -use crate::*; +use near_sdk::{env, Timestamp}; -pub(crate) fn nano_to_sec(timestamp: Timestamp) -> TimestampSec { - (timestamp / 10u64.pow(9)) as _ +use crate::TimestampSec; + +pub fn nano_to_sec(timestamp: Timestamp) -> TimestampSec { + (timestamp / 10u64.pow(9)).try_into().unwrap() } -pub(crate) fn current_timestamp_sec() -> TimestampSec { +pub fn current_timestamp_sec() -> TimestampSec { nano_to_sec(env::block_timestamp()) } pub mod u128_dec_format { - use near_sdk::serde::de; - use near_sdk::serde::{Deserialize, Deserializer, Serializer}; + use near_sdk::serde::{de, Deserialize, Deserializer, Serializer}; pub fn serialize(num: &u128, serializer: S) -> Result where @@ -23,8 +24,6 @@ pub mod u128_dec_format { where D: Deserializer<'de>, { - String::deserialize(deserializer)? - .parse() - .map_err(de::Error::custom) + String::deserialize(deserializer)?.parse().map_err(de::Error::custom) } } diff --git a/model/src/view_api.rs b/model/src/view_api.rs new file mode 100644 index 0000000..a9b9152 --- /dev/null +++ b/model/src/view_api.rs @@ -0,0 +1,61 @@ +use integration_trait::make_integration_version; +use near_sdk::{json_types::Base58CryptoHash, AccountId}; + +use crate::{ + draft::{DraftGroupIndex, DraftGroupView, DraftIndex, DraftView}, + lockup::{LockupIndex, LockupView}, + schedule::Schedule, + WrappedBalance, +}; + +#[make_integration_version] +pub trait LockupViewApi { + fn get_token_account_id(&self) -> AccountId; + + fn get_account_lockups(&self, account_id: AccountId) -> Vec<(LockupIndex, LockupView)>; + + fn get_lockup(&self, index: LockupIndex) -> Option; + fn get_lockups(&self, indices: Vec) -> Vec<(LockupIndex, LockupView)>; + + fn get_num_lockups(&self) -> u32; + + fn get_lockups_paged( + &self, + from_index: Option, + limit: Option, + ) -> Vec<(LockupIndex, LockupView)>; + + fn get_deposit_whitelist(&self) -> Vec; + + fn get_draft_operators_whitelist(&self) -> Vec; + + fn hash_schedule(&self, schedule: Schedule) -> Base58CryptoHash; + + fn validate_schedule( + &self, + schedule: Schedule, + total_balance: WrappedBalance, + termination_schedule: Option, + ); + + fn get_next_draft_group_id(&self) -> DraftGroupIndex; + + fn get_next_draft_id(&self) -> DraftGroupIndex; + + fn get_num_draft_groups(&self) -> u32; + + fn get_draft_group(&self, index: DraftGroupIndex) -> Option; + + fn get_draft_groups_paged( + &self, + // not the draft_id, but internal index used inside the LookupMap struct + from_index: Option, + to_index: Option, + ) -> Vec<(DraftGroupIndex, DraftGroupView)>; + + fn get_draft(&self, index: DraftIndex) -> Option; + + fn get_drafts(&self, indices: Vec) -> Vec<(DraftIndex, DraftView)>; + + fn get_version(&self) -> String; +} diff --git a/res/.gitkeep b/res/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/res/MULTISIG-README.md b/res/MULTISIG-README.md new file mode 100644 index 0000000..a18b5ff --- /dev/null +++ b/res/MULTISIG-README.md @@ -0,0 +1,254 @@ +# Basic Multisig contract + +This contract provides: +- Set K out of N multi sig scheme +- Request to sign transfers, function calls, adding and removing keys. +- Any of the access keys can confirm, until the required number of confirmation achieved. + +## Multisig implementation details + +Mutlisig uses set of `FunctionCall` `AccessKey`s as a set of allowed N keys. +When contract is being setup, it should be initialized with set of keys that will be initially managing this account. +All operations going forward will require `K` signatures to be performed. + +### Initialization + +### Request + +There are number of different request types that multisig can confirm and execute: +```rust +/// Lowest level action that can be performed by the multisig contract. +pub enum MultiSigRequestAction { + /// Transfers given amount to receiver. + Transfer { + amount: U128, + }, + /// Create a new account. + CreateAccount, + /// Deploys contract to receiver's account. Can upgrade given contract as well. + DeployContract { code: Base64VecU8 }, + /// Adds key, either new key for multisig or full access key to another account. + AddKey { + public_key: Base58PublicKey, + #[serde(skip_serializing_if = "Option::is_none")] + permission: Option, + }, + /// Deletes key, either one of the keys from multisig or key from another account. + DeleteKey { + public_key: Base58PublicKey, + }, + /// Call function on behalf of this contract. + FunctionCall { + method_name: String, + args: Base64VecU8, + deposit: U128, + gas: U64, + }, + /// Sets number of confirmations required to authorize requests. + /// Can not be bundled with any other actions or transactions. + SetNumConfirmations { + num_confirmations: u32, + }, + /// Sets number of active requests (unconfirmed requests) per access key + /// Default is 12 unconfirmed requests at a time + /// The REQUEST_COOLDOWN for requests is 15min + /// Worst gas attack a malicious keyholder could do is 12 requests every 15min + SetActiveRequestsLimit { + active_requests_limit: u32, + }, +} + +/// Permission for an access key, scoped to receiving account and method names with allowance to add when key is added to accoount +pub struct FunctionCallPermission { + allowance: Option, + receiver_id: AccountId, + method_names: Vec, +} + +// The request the user makes specifying the receiving account and actions they want to execute (1 tx) +pub struct MultiSigRequest { + receiver_id: AccountId, + actions: Vec, +} + +// An internal request wrapped with the signer_pk and added timestamp to determine num_requests_pk and prevent against malicious key holder gas attacks +pub struct MultiSigRequestWithSigner { + request: MultiSigRequest, + signer_pk: PublicKey, + added_timestamp: u64, +} +``` + +### Methods + +```rust +/// Add request for multisig. +pub fn add_request(&mut self, request: MultiSigRequest) -> RequestId { + +/// Add request for multisig and confirm right away with the key that is adding the request. +pub fn add_request_and_confirm(&mut self, request: MultiSigRequest) -> RequestId { + +/// Remove given request and associated confirmations. +pub fn delete_request(&mut self, request_id: RequestId) { + +/// Confirm given request with given signing key. +/// If with this, there has been enough confirmation, a promise with request will be scheduled. +pub fn confirm(&mut self, request_id: RequestId) -> PromiseOrValue { +``` + +### View Methods +```rust +pub fn get_request(&self, request_id: RequestId) -> MultiSigRequest +pub fn get_num_requests_pk(&self, public_key: Base58PublicKey) -> u32 +pub fn list_request_ids(&self) -> Vec +pub fn get_confirmations(&self, request_id: RequestId) -> Vec +pub fn get_num_confirmations(&self) -> u32 +pub fn get_request_nonce(&self) -> u32 +``` + +### State machine + +Per each request, multisig maintains next state machine: +- `add_request` adds new request with empty list of confirmations. +- `add_request_and_confirm` adds new request with 1 confirmation from the adding key. +- `delete_request` deletes request and ends state machine. +- `confirm` either adds new confirmation to list of confirmations or if there is more than `num_confirmations` confirmations with given call - switches to execution of request. `confirm` fails if request is already has been confirmed and already is executing which is determined if `confirmations` contain given `request_id`. +- each step of execution, schedules a promise of given set of actions on `receiver_id` and puts a callback. +- when callback executes, it checks if promise executed successfully: if no - stops executing the request and return failure. If yes - execute next transaction in the request if present. +- when all transactions are executed, remove request from `requests` and with that finish the execution of the request. + +### Gotchas + +User can delete access keys on the multisig such that total number of different access keys will fall below `num_confirmations`, rendering contract locked. +This is due to not having a way to query blockchain for current number of access keys on the account. See discussion here - https://github.com/nearprotocol/NEPs/issues/79. + +## Pre-requisites + +To develop Rust contracts you would need to: +* Install [Rustup](https://rustup.rs/): +```bash +curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh +``` +* Add wasm target to your toolchain: +```bash +rustup target add wasm32-unknown-unknown +``` + +## Building the contract + +```bash +./build.sh +``` + +## Usage + +Before deploying the contract, you need to collect all public keys that it will be initialized with. + +Commands to deploy and initialize a 2 out of 3 multisig contract via `near repl`: + +```javascript +const fs = require('fs'); +const account = await near.account("illia"); +const contractName = "multisig.illia"; +const methodNames = ["add_request","delete_request","confirm"]; +const newArgs = {"num_confirmations": 2}; +const result = account.signAndSendTransaction( + contractName, + [ + nearAPI.transactions.createAccount(), + nearAPI.transactions.transfer("100000000000000000000000000"), + nearAPI.transactions.addKey( + nearAPI.utils.PublicKey.from("Eg2jtsiMrprn7zgKKUk79qM1hWhANsFyE6JSX4txLEuy"), + nearAPI.transactions.functionCallAccessKey(contractName, methodNames, null)), + nearAPI.transactions.addKey( + nearAPI.utils.PublicKey.from("HghiythFFPjVXwc9BLNi8uqFmfQc1DWFrJQ4nE6ANo7R"), + nearAPI.transactions.functionCallAccessKey(contractName, methodNames, null)), + nearAPI.transactions.addKey( + nearAPI.utils.PublicKey.from("2EfbwnQHPBWQKbNczLiVznFghh9qs716QT71zN6L1D95"), + nearAPI.transactions.functionCallAccessKey(contractName, methodNames, null)), + nearAPI.transactions.deployContract(fs.readFileSync("res/multisig.wasm")), + nearAPI.transactions.functionCall("new", Buffer.from(JSON.stringify(newArgs)), 10000000000000, "0"), + ]); +``` + +### Create request + +To create request for transfer funds: +```bash +near call multisig.illia add_request '{"request": {"receiver_id": "illia", "actions": [{"type": "Transfer", "amount": "1000000000000000000000"}]}}' --accountId multisig.illia +``` + +Add another key to multisig: +```bash +near call multisig.illia add_request '{"request": {"receiver_id": "multisig.illia", "actions": [{"type": "AddKey", "public_key": ""}]}}' --accountId multisig.illia +``` + +Change number of confirmations required to approve multisig: +```bash +near call multisig.illia add_request '{"request": {"receiver_id": "multisig.illia", "actions": [{"type": "SetNumConfirmations", "num_confirmations": 2}]}}' --accountId multisig.illia +``` + +Returns the `request_id` of this request that can be used to confirm or see details. + +As a side note, for this to work one of the keys from multisig should be available in your `~/.near-credentials//.json` or use `--useLedgerKey` to sign with Ledger. + +You can also create a way more complex call that chains calling multiple different contracts: + +### Confirm request + +To confirm a specific request: +```bash +near call multisig.illia confirm '{"request_id": 0}' --accountId multisig.illia +``` + +### View requests + +To list all requests ids: +```bash +near view multisig.illia list_request_ids +``` + +To see information about specific request: +```bash +near view multisig.illia get_request '{"request_id": 0}' +``` + +To see confirmations for specific request: +```bash +near view multisig.illia get_confirmations '{"request_id": 0}' +``` + +Total confirmations required for any request: +``` +near view multisig.illia get_num_confirmations +``` + +### Upgrade given multisig with new code + +Create a request that deploys new contract code on the given account. +Be careful about data and requiring migrations (contract updates should include data migrations going forward). + +```javascript +const fs = require('fs'); +const account = await near.account("multisig.illia"); +const contractName = "multisig.illia"; +const requestArgs = {"request": [ + {"receiver_id": "multisig.illia", "actions": [{"type": "DeployContract", "code": fs.readFileSync("res/multisig.wasm")}]} +]}; +const result = account.signAndSendTransaction( + contractName, + [ + nearAPI.transactions.functionCall("add_request", Buffer.from(JSON.stringify(requestArgs)), 10000000000000, "0"), + ]); +``` + +After this, still will need to confirm this with `num_confirmations` you have setup for given contract. + +### Common commands for multisig + +__Create an account__ + +``` +near call illia.near add_request '{"request": {"receiver_id": "new_account.near", "actions": [{"type", "CreateAccount"}, {"type": "Transfer", "amount": "1000000000000000000000"}, {"type": "AddKey", "public_key": ""}]}}' --accountId illia.near +near call illia.near confirm '{"request_id": }' +``` diff --git a/res/ft_lockup.wasm b/res/ft_lockup.wasm deleted file mode 100755 index 87c113c..0000000 Binary files a/res/ft_lockup.wasm and /dev/null differ diff --git a/res/fungible_token.wasm b/res/fungible_token.wasm deleted file mode 100644 index 5d87432..0000000 Binary files a/res/fungible_token.wasm and /dev/null differ diff --git a/res/hodl_lockup.wasm b/res/hodl_lockup.wasm new file mode 100755 index 0000000..c1cc5fd Binary files /dev/null and b/res/hodl_lockup.wasm differ diff --git a/res/multisig.wasm b/res/multisig.wasm new file mode 100755 index 0000000..bc03004 Binary files /dev/null and b/res/multisig.wasm differ diff --git a/res/sweat.wasm b/res/sweat.wasm new file mode 100755 index 0000000..16e2f87 Binary files /dev/null and b/res/sweat.wasm differ diff --git a/res/utils.wasm b/res/utils.wasm new file mode 100755 index 0000000..32e20c4 Binary files /dev/null and b/res/utils.wasm differ diff --git a/rust-toolchain.toml b/rust-toolchain.toml new file mode 100644 index 0000000..71326c3 --- /dev/null +++ b/rust-toolchain.toml @@ -0,0 +1,2 @@ +[toolchain] +channel = "1.74" diff --git a/scripts/build-in-docker.sh b/scripts/build-in-docker.sh new file mode 100755 index 0000000..ab42129 --- /dev/null +++ b/scripts/build-in-docker.sh @@ -0,0 +1,12 @@ +#!/bin/bash +set -eox pipefail + +HOST_DIR="${HOST_DIR:-$(pwd)}" + +docker run \ + --rm \ + --mount type=bind,source=$HOST_DIR,target=/host \ + --cap-add=SYS_PTRACE \ + --security-opt seccomp=unconfined \ + -t nearprotocol/contract-builder:latest-amd64 \ + /bin/bash -c "cd /host && make build" diff --git a/scripts/build-integration.sh b/scripts/build-integration.sh new file mode 100755 index 0000000..e979dc0 --- /dev/null +++ b/scripts/build-integration.sh @@ -0,0 +1,9 @@ +#!/bin/bash +set -eox pipefail + +echo ">> Building contract" + +rustup target add wasm32-unknown-unknown +cargo build -p hodl-lockup --target wasm32-unknown-unknown --profile=contract --features integration-test + +cp ./target/wasm32-unknown-unknown/contract/hodl_lockup.wasm res/hodl_lockup.wasm diff --git a/scripts/build-utils.sh b/scripts/build-utils.sh new file mode 100755 index 0000000..60031ad --- /dev/null +++ b/scripts/build-utils.sh @@ -0,0 +1,9 @@ +#!/bin/bash +set -eox pipefail + +echo ">> Building utils contract" + +rustup target add wasm32-unknown-unknown +cargo build -p utils --target wasm32-unknown-unknown --profile=contract + +cp ./target/wasm32-unknown-unknown/contract/utils.wasm res/utils.wasm diff --git a/scripts/build.sh b/scripts/build.sh new file mode 100755 index 0000000..a8c61cb --- /dev/null +++ b/scripts/build.sh @@ -0,0 +1,9 @@ +#!/bin/bash +set -eox pipefail + +echo ">> Building contract" + +rustup target add wasm32-unknown-unknown +cargo build -p hodl-lockup --target wasm32-unknown-unknown --profile=contract + +cp ./target/wasm32-unknown-unknown/contract/hodl_lockup.wasm res/hodl_lockup.wasm diff --git a/scripts/deploy.sh b/scripts/deploy.sh new file mode 100755 index 0000000..5dff159 --- /dev/null +++ b/scripts/deploy.sh @@ -0,0 +1,14 @@ +#!/bin/sh + +source dev.env + +make build + +if [ $? -ne 0 ]; then + echo ">> Error building contract" + exit 1 +fi + +echo ">> Deploying contract" + +near dev-deploy --wasmFile "res/sweat_jar.wasm" --initFunction "init" --initArgs "{\"token_account_id\": \"$TOKEN_ACCOUNT_ID\", \"manager\": \"$ADMIN_ACCOUNT_ID\", \"fee_account_id\": \"$FEE_ACCOUNT_ID\"}" diff --git a/scripts/lint.sh b/scripts/lint.sh new file mode 100755 index 0000000..77c7a73 --- /dev/null +++ b/scripts/lint.sh @@ -0,0 +1,19 @@ +#!/bin/bash +set -eox pipefail + +rustup component add clippy + +cargo clippy --all \ + -- \ + \ + -W clippy::all \ + -W clippy::pedantic \ + \ + -A clippy::module_name_repetitions \ + -A clippy::needless-pass-by-value \ + -A clippy::must-use-candidate \ + -A clippy::missing-panics-doc \ + -A clippy::missing-errors-doc \ + -A clippy::doc-markdown \ + \ + -D warnings diff --git a/scripts/measure.sh b/scripts/measure.sh new file mode 100755 index 0000000..1a5a9ea --- /dev/null +++ b/scripts/measure.sh @@ -0,0 +1,12 @@ +#!/bin/bash +set -eox pipefail + +if [ -z ${MEASURE_JARS_COUNT+x} ]; then echo "MEASURE_JARS_COUNT is unset"; else echo "MEASURE_JARS_COUNT is set to '$MEASURE_JARS_COUNT'"; fi +if [ -z ${MEASURE_JARS_MULTIPLIER+x} ]; then echo "MEASURE_JARS_MULTIPLIER is unset"; else echo "MEASURE_JARS_MULTIPLIER is set to '$MEASURE_JARS_MULTIPLIER'"; fi + +rm -f measured.txt + +cargo test --package integration-tests --lib measure::stake::measure_stake_total_test -- --ignored --exact +cargo test --package integration-tests --lib measure::restake::measure_restake_total_test -- --ignored --exact +cargo test --package integration-tests --lib measure::claim::measure_claim_total_test -- --ignored --exact +cargo test --package integration-tests --lib measure::withdraw::measure_withdraw_total_test -- --ignored --exact diff --git a/scripts/mutation.sh b/scripts/mutation.sh new file mode 100755 index 0000000..8bfc63b --- /dev/null +++ b/scripts/mutation.sh @@ -0,0 +1,7 @@ +#!/bin/bash +set -eox pipefail + +echo ">> Mutation tests" + +cargo install --locked cargo-mutants +cargo mutants diff --git a/src/callbacks.rs b/src/callbacks.rs deleted file mode 100644 index 3e16169..0000000 --- a/src/callbacks.rs +++ /dev/null @@ -1,98 +0,0 @@ -use crate::*; - -trait SelfCallbacks { - fn after_ft_transfer( - &mut self, - account_id: AccountId, - lockup_claims: Vec, - ) -> WrappedBalance; - - fn after_lockup_termination( - &mut self, - account_id: AccountId, - amount: WrappedBalance, - ) -> WrappedBalance; -} - -#[near_bindgen] -impl SelfCallbacks for Contract { - #[private] - fn after_ft_transfer( - &mut self, - account_id: AccountId, - lockup_claims: Vec, - ) -> WrappedBalance { - let promise_success = is_promise_success(); - let mut total_balance = 0; - if promise_success { - let mut remove_indices = vec![]; - let mut events: Vec = vec![]; - for LockupClaim { - index, - is_final, - claim_amount, - } in lockup_claims - { - if is_final { - remove_indices.push(index); - } - total_balance += claim_amount.0; - let event = FtLockupClaimLockup { - id: index, - amount: claim_amount, - }; - events.push(event); - } - if !remove_indices.is_empty() { - let mut indices = self.account_lockups.get(&account_id).unwrap_or_default(); - for index in remove_indices { - indices.remove(&index); - } - self.internal_save_account_lockups(&account_id, indices); - } - emit(EventKind::FtLockupClaimLockup(events)); - } else { - log!("Token transfer has failed. Refunding."); - let mut modified = false; - let mut indices = self.account_lockups.get(&account_id).unwrap_or_default(); - for LockupClaim { - index, - claim_amount, - .. - } in lockup_claims - { - if indices.insert(index) { - modified = true; - } - let mut lockup = self.lockups.get(index as _).unwrap(); - lockup.claimed_balance -= claim_amount.0; - self.lockups.replace(index as _, &lockup); - } - - if modified { - self.internal_save_account_lockups(&account_id, indices); - } - } - total_balance.into() - } - - #[private] - fn after_lockup_termination( - &mut self, - account_id: AccountId, - amount: WrappedBalance, - ) -> WrappedBalance { - let promise_success = is_promise_success(); - if !promise_success { - log!("Lockup termination transfer has failed."); - // There is no internal balance, so instead we create a new lockup. - let lockup = Lockup::new_unlocked_since(account_id, amount.0, current_timestamp_sec()); - let lockup_index = self.internal_add_lockup(&lockup); - let event: FtLockupCreateLockup = (lockup_index, lockup, None).into(); - emit(EventKind::FtLockupCreateLockup(vec![event])); - 0.into() - } else { - amount - } - } -} diff --git a/src/draft.rs b/src/draft.rs deleted file mode 100644 index faa2a0b..0000000 --- a/src/draft.rs +++ /dev/null @@ -1,107 +0,0 @@ -use crate::*; - -pub type DraftGroupIndex = u32; -pub type DraftIndex = u32; - -#[derive(BorshDeserialize, BorshSerialize, Deserialize, Serialize)] -#[serde(crate = "near_sdk::serde")] -#[cfg_attr(not(target_arch = "wasm32"), derive(Debug, PartialEq, Clone))] -pub struct Draft { - pub draft_group_id: DraftGroupIndex, - pub lockup_create: LockupCreate, -} - -impl Draft { - pub fn total_balance(&self) -> Balance { - self.lockup_create.schedule.total_balance() - } - - pub fn assert_new_valid(&self) { - let amount = self.lockup_create.schedule.total_balance(); - // any valid near account id will work fine here as a parameter - self.lockup_create - .into_lockup(&env::predecessor_account_id().try_into().unwrap()) - .assert_new_valid(amount); - } -} - -#[derive(BorshDeserialize, BorshSerialize)] -pub struct DraftGroup { - pub total_amount: Balance, - pub payer_id: Option, - pub draft_indices: HashSet, - pub discarded: bool, -} - -impl DraftGroup { - pub fn new() -> Self { - Self { - total_amount: 0, - payer_id: None, - draft_indices: HashSet::new(), - discarded: false, - } - } - - pub fn assert_can_add_draft(&self) { - assert!( - !self.discarded, - "cannot add draft, draft group is discarded" - ); - assert!( - self.payer_id.is_none(), - "cannot add draft, group already funded" - ); - } - - pub fn assert_can_convert_draft(&self) { - assert!( - !self.discarded, - "cannot convert draft, draft group is discarded" - ); - assert!( - self.payer_id.is_some(), - "cannot convert draft from not funded group" - ); - } - - pub fn assert_can_fund(&self) { - assert!( - !self.discarded, - "cannot fund draft, draft group is discarded" - ); - assert!(self.payer_id.is_none(), "draft group already funded"); - } - - pub fn fund(&mut self, payer_id: &ValidAccountId) { - self.assert_can_fund(); - self.payer_id = Some(payer_id.clone()); - } - - pub fn assert_can_discard(&mut self) { - assert!( - !self.discarded, - "cannot discard, draft group already discarded" - ); - assert!( - self.payer_id.is_none(), - "cannot discard, draft group already funded" - ); - } - - pub fn discard(&mut self) { - self.assert_can_discard(); - self.discarded = true; - } - - pub fn assert_can_delete_draft(&mut self) { - assert!( - self.discarded, - "cannot delete draft, draft group is not discarded" - ); - assert!( - self.payer_id.is_none(), - "cannot delete draft, draft group already funded" - ); - } -} diff --git a/src/lockup.rs b/src/lockup.rs deleted file mode 100644 index bf8932c..0000000 --- a/src/lockup.rs +++ /dev/null @@ -1,127 +0,0 @@ -use crate::*; -use std::convert::TryInto; - -pub type LockupIndex = u32; - -#[derive(Serialize, Deserialize)] -#[serde(crate = "near_sdk::serde")] -#[cfg_attr(not(target_arch = "wasm32"), derive(Debug, PartialEq))] -pub struct LockupClaim { - pub index: LockupIndex, - pub claim_amount: WrappedBalance, - pub is_final: bool, -} - -#[derive(BorshDeserialize, BorshSerialize, Deserialize)] -#[serde(crate = "near_sdk::serde")] -#[cfg_attr(not(target_arch = "wasm32"), derive(Debug, PartialEq, Clone))] -pub struct Lockup { - pub account_id: ValidAccountId, - pub schedule: Schedule, - - #[serde(default)] - #[serde(with = "u128_dec_format")] - pub claimed_balance: Balance, - /// An optional configuration that allows vesting/lockup termination. - pub termination_config: Option, -} - -impl Lockup { - pub fn new_unlocked_since( - account_id: AccountId, - total_balance: Balance, - timestamp: TimestampSec, - ) -> Self { - Self { - account_id: account_id.try_into().unwrap(), - schedule: Schedule::new_unlocked_since(total_balance, timestamp), - claimed_balance: 0, - termination_config: None, - } - } - - pub fn new_unlocked(account_id: AccountId, total_balance: Balance) -> Self { - Self::new_unlocked_since(account_id, total_balance, 1) - } - - pub fn claim(&mut self, index: LockupIndex, claim_amount: Balance) -> LockupClaim { - let unlocked_balance = self.schedule.unlocked_balance(current_timestamp_sec()); - let balance_claimed_new = self - .claimed_balance - .checked_add(claim_amount) - .expect("attempt to add with overflow"); - assert!( - unlocked_balance >= balance_claimed_new, - "too big claim_amount for lockup {}", - index, - ); - - self.claimed_balance = balance_claimed_new; - LockupClaim { - index, - claim_amount: claim_amount.into(), - is_final: balance_claimed_new == self.schedule.total_balance(), - } - } - - pub fn assert_new_valid(&self, total_balance: Balance) { - assert_eq!( - self.claimed_balance, 0, - "The initial lockup claimed balance should be 0" - ); - self.schedule.assert_valid(total_balance); - - if let Some(termination_config) = &self.termination_config { - match &termination_config.vesting_schedule { - VestingConditions::SameAsLockupSchedule => { - // Ok, using lockup schedule. - } - VestingConditions::Hash(_hash) => { - // Ok, using unknown hash. Can't verify. - } - VestingConditions::Schedule(schedule) => { - schedule.assert_valid(total_balance); - self.schedule.assert_valid_termination_schedule(&schedule); - } - } - } - } -} - -#[derive(BorshDeserialize, BorshSerialize, Deserialize, Serialize)] -#[serde(crate = "near_sdk::serde")] -#[cfg_attr(not(target_arch = "wasm32"), derive(Debug, PartialEq, Clone))] -pub struct LockupCreate { - pub account_id: ValidAccountId, - pub schedule: Schedule, - pub vesting_schedule: Option, -} - -#[cfg(not(target_arch = "wasm32"))] -impl LockupCreate { - pub fn new_unlocked(account_id: ValidAccountId, total_balance: Balance) -> Self { - Self { - account_id, - schedule: Schedule::new_unlocked(total_balance), - vesting_schedule: None, - } - } -} - -impl LockupCreate { - pub fn into_lockup(&self, payer_id: &ValidAccountId) -> Lockup { - let vesting_schedule = self.vesting_schedule.clone(); - Lockup { - account_id: self.account_id.clone(), - schedule: self.schedule.clone(), - claimed_balance: 0, - termination_config: match vesting_schedule { - None => None, - Some(vesting_schedule) => Some(TerminationConfig { - beneficiary_id: payer_id.clone(), - vesting_schedule, - }), - }, - } - } -} diff --git a/src/termination.rs b/src/termination.rs deleted file mode 100644 index d638dd9..0000000 --- a/src/termination.rs +++ /dev/null @@ -1,60 +0,0 @@ -use crate::*; - -#[derive(BorshDeserialize, BorshSerialize, Serialize, Deserialize, Clone)] -#[serde(crate = "near_sdk::serde")] -#[cfg_attr(not(target_arch = "wasm32"), derive(Debug, PartialEq))] -pub enum VestingConditions { - SameAsLockupSchedule, - Hash(Base58CryptoHash), - Schedule(Schedule), -} - -#[derive(BorshDeserialize, BorshSerialize, Serialize, Deserialize)] -#[serde(crate = "near_sdk::serde")] -#[cfg_attr(not(target_arch = "wasm32"), derive(Debug, PartialEq, Clone))] -pub struct TerminationConfig { - /// The account ID who paid for the lockup creation - /// and will receive unvested balance upon termination - pub beneficiary_id: ValidAccountId, - /// An optional vesting schedule - pub vesting_schedule: VestingConditions, -} - -impl Lockup { - pub fn terminate( - &mut self, - hashed_schedule: Option, - termination_timestamp: TimestampSec, - ) -> (Balance, AccountId) { - let termination_config = self - .termination_config - .take() - .expect("No termination config"); - let total_balance = self.schedule.total_balance(); - let vested_balance = match &termination_config.vesting_schedule { - VestingConditions::SameAsLockupSchedule => &self.schedule, - VestingConditions::Hash(hash) => { - let schedule = hashed_schedule - .as_ref() - .expect("Revealed schedule required for the termination"); - let hash: CryptoHash = (*hash).into(); - assert_eq!( - hash, - schedule.hash(), - "The revealed schedule hash doesn't match" - ); - schedule.assert_valid(total_balance); - self.schedule.assert_valid_termination_schedule(schedule); - schedule - } - VestingConditions::Schedule(schedule) => &schedule, - } - .unlocked_balance(termination_timestamp); - let unvested_balance = total_balance - vested_balance; - if unvested_balance > 0 { - self.schedule - .terminate(vested_balance, termination_timestamp); - } - (unvested_balance, termination_config.beneficiary_id.into()) - } -} diff --git a/src/view.rs b/src/view.rs deleted file mode 100644 index 6b8b756..0000000 --- a/src/view.rs +++ /dev/null @@ -1,248 +0,0 @@ -use crate::*; -use std::convert::TryInto; - -#[derive(Serialize)] -#[serde(crate = "near_sdk::serde")] -#[cfg_attr(not(target_arch = "wasm32"), derive(Debug, PartialEq, Deserialize))] -pub struct LockupView { - pub account_id: ValidAccountId, - pub schedule: Schedule, - - #[serde(default)] - #[serde(with = "u128_dec_format")] - pub claimed_balance: Balance, - /// An optional configuration that allows vesting/lockup termination. - pub termination_config: Option, - - #[serde(with = "u128_dec_format")] - pub total_balance: Balance, - #[serde(with = "u128_dec_format")] - pub unclaimed_balance: Balance, - /// The current timestamp - pub timestamp: TimestampSec, -} - -impl From for LockupView { - fn from(lockup: Lockup) -> Self { - let total_balance = lockup.schedule.total_balance(); - let timestamp = current_timestamp_sec(); - let unclaimed_balance = - lockup.schedule.unlocked_balance(timestamp) - lockup.claimed_balance; - let Lockup { - account_id, - schedule, - claimed_balance, - termination_config, - } = lockup; - Self { - account_id, - schedule, - claimed_balance, - termination_config, - total_balance, - unclaimed_balance, - timestamp, - } - } -} - -#[derive(Serialize)] -#[serde(crate = "near_sdk::serde")] -#[cfg_attr(not(target_arch = "wasm32"), derive(Debug, PartialEq, Deserialize))] -pub struct LockupCreateView { - pub account_id: ValidAccountId, - pub schedule: Schedule, - pub vesting_schedule: Option, - - #[serde(with = "u128_dec_format")] - pub claimed_balance: Balance, - #[serde(with = "u128_dec_format")] - pub total_balance: Balance, - #[serde(with = "u128_dec_format")] - pub unclaimed_balance: Balance, - /// The current timestamp - pub timestamp: TimestampSec, -} - -impl From for LockupCreateView { - fn from(lockup_create: LockupCreate) -> Self { - let total_balance = lockup_create.schedule.total_balance(); - let timestamp = current_timestamp_sec(); - let unclaimed_balance = lockup_create.schedule.unlocked_balance(timestamp); - let LockupCreate { - account_id, - schedule, - vesting_schedule, - } = lockup_create; - Self { - account_id, - schedule, - vesting_schedule, - claimed_balance: 0, - total_balance, - unclaimed_balance, - timestamp, - } - } -} - -#[derive(Serialize)] -#[serde(crate = "near_sdk::serde")] -#[cfg_attr(not(target_arch = "wasm32"), derive(Debug, PartialEq, Deserialize))] -pub struct DraftGroupView { - #[serde(with = "u128_dec_format")] - pub total_amount: Balance, - pub payer_id: Option, - pub draft_indices: Vec, - pub discarded: bool, - pub funded: bool, -} - -impl From for DraftGroupView { - fn from(draft_group: DraftGroup) -> Self { - Self { - total_amount: draft_group.total_amount, - payer_id: draft_group.payer_id.clone(), - draft_indices: draft_group.draft_indices.into_iter().collect(), - discarded: draft_group.discarded, - funded: draft_group.payer_id.is_some(), - } - } -} - -#[derive(Serialize)] -#[serde(crate = "near_sdk::serde")] -#[cfg_attr(not(target_arch = "wasm32"), derive(Debug, PartialEq, Deserialize))] -pub struct DraftView { - pub draft_group_id: DraftGroupIndex, - pub lockup_create: LockupCreateView, -} - -impl From for DraftView { - fn from(draft: Draft) -> Self { - Self { - draft_group_id: draft.draft_group_id, - lockup_create: draft.lockup_create.into(), - } - } -} - -#[near_bindgen] -impl Contract { - pub fn get_token_account_id(&self) -> ValidAccountId { - self.token_account_id.clone().try_into().unwrap() - } - - pub fn get_account_lockups( - &self, - account_id: ValidAccountId, - ) -> Vec<(LockupIndex, LockupView)> { - self.internal_get_account_lockups(account_id.as_ref()) - .into_iter() - .map(|(lockup_index, lockup)| (lockup_index, lockup.into())) - .collect() - } - - pub fn get_lockup(&self, index: LockupIndex) -> Option { - self.lockups.get(index as _).map(|lockup| lockup.into()) - } - - pub fn get_lockups(&self, indices: Vec) -> Vec<(LockupIndex, LockupView)> { - indices - .into_iter() - .filter_map(|index| self.get_lockup(index).map(|lockup| (index, lockup))) - .collect() - } - - pub fn get_num_lockups(&self) -> u32 { - self.lockups.len() as _ - } - - pub fn get_lockups_paged( - &self, - from_index: Option, - limit: Option, - ) -> Vec<(LockupIndex, LockupView)> { - let from_index = from_index.unwrap_or(0); - let limit = limit.unwrap_or(self.get_num_lockups()); - (from_index..std::cmp::min(self.get_num_lockups(), limit)) - .filter_map(|index| self.get_lockup(index).map(|lockup| (index, lockup))) - .collect() - } - - pub fn get_deposit_whitelist(&self) -> Vec { - self.deposit_whitelist.to_vec() - } - - pub fn get_draft_operators_whitelist(&self) -> Vec { - self.draft_operators_whitelist.to_vec() - } - - pub fn hash_schedule(&self, schedule: Schedule) -> Base58CryptoHash { - schedule.hash().into() - } - - pub fn validate_schedule( - &self, - schedule: Schedule, - total_balance: WrappedBalance, - termination_schedule: Option, - ) { - schedule.assert_valid(total_balance.0); - if let Some(termination_schedule) = termination_schedule { - termination_schedule.assert_valid(total_balance.0); - schedule.assert_valid_termination_schedule(&termination_schedule); - } - } - - pub fn get_next_draft_group_id(&self) -> DraftGroupIndex { - self.next_draft_group_id - } - - pub fn get_next_draft_id(&self) -> DraftGroupIndex { - self.next_draft_id - } - - pub fn get_num_draft_groups(&self) -> u32 { - self.draft_groups.len() as _ - } - - pub fn get_draft_group(&self, index: DraftGroupIndex) -> Option { - self.draft_groups.get(&index as _).map(|group| group.into()) - } - - pub fn get_draft_groups_paged( - &self, - // not the draft_id, but internal index used inside the LookupMap struct - from_index: Option, - to_index: Option, - ) -> Vec<(DraftGroupIndex, DraftGroupView)> { - let from_index = from_index.unwrap_or(0); - let to_index = to_index.unwrap_or(self.draft_groups.len() as _); - let keys = self.draft_groups.keys_as_vector(); - let values = self.draft_groups.values_as_vector(); - (from_index..std::cmp::min(self.next_draft_group_id as _, to_index)) - .map(|index| { - ( - keys.get(index as _).unwrap(), - values.get(index as _).unwrap().into(), - ) - }) - .collect() - } - - pub fn get_draft(&self, index: DraftIndex) -> Option { - self.drafts.get(&index as _).map(|draft| draft.into()) - } - - pub fn get_drafts(&self, indices: Vec) -> Vec<(DraftIndex, DraftView)> { - indices - .into_iter() - .filter_map(|index| self.get_draft(index).map(|draft| (index, draft))) - .collect() - } - - pub fn get_version(&self) -> String { - VERSION.into() - } -}