├── .cargo └── config ├── .circleci └── config.yml ├── .editorconfig ├── .github └── workflows │ └── Basic.yml ├── .gitignore ├── Cargo.lock ├── Cargo.toml ├── Developing.md ├── Importing.md ├── LICENSE ├── Makefile ├── NOTICE ├── Publishing.md ├── README.md ├── examples └── schema.rs ├── rustfmt.toml ├── schema ├── handle_answer.json ├── handle_msg.json ├── init_msg.json ├── query_answer.json └── query_msg.json ├── src ├── contract.rs ├── lib.rs ├── msg.rs ├── receiver.rs ├── state.rs ├── utils.rs └── viewing_key.rs └── tests ├── example-receiver ├── .gitignore ├── Cargo.lock ├── Cargo.toml ├── Makefile ├── README.md ├── rustfmt.toml └── src │ ├── contract.rs │ ├── lib.rs │ ├── msg.rs │ └── state.rs ├── integration.rs └── integration.sh /.cargo/config: -------------------------------------------------------------------------------- 1 | [alias] 2 | wasm = "build --release --target wasm32-unknown-unknown" 3 | unit-test = "test --lib --features backtraces" 4 | integration-test = "test --test integration" 5 | schema = "run --example schema" 6 | -------------------------------------------------------------------------------- /.circleci/config.yml: -------------------------------------------------------------------------------- 1 | version: 2.1 2 | 3 | jobs: 4 | build: 5 | docker: 6 | - image: rust:1.46 7 | steps: 8 | - checkout 9 | - run: 10 | name: Version information 11 | command: rustc --version; cargo --version; rustup --version 12 | - restore_cache: 13 | keys: 14 | - v4-cargo-cache-{{ arch }}-{{ checksum "Cargo.lock" }} 15 | - run: 16 | name: Add wasm32 target 17 | command: rustup target add wasm32-unknown-unknown 18 | - run: 19 | name: Build 20 | command: cargo wasm --locked 21 | - run: 22 | name: Unit tests 23 | env: RUST_BACKTRACE=1 24 | command: cargo unit-test --locked 25 | - run: 26 | name: Integration tests 27 | command: cargo integration-test --locked 28 | - run: 29 | name: Format source code 30 | command: cargo fmt 31 | - run: 32 | name: Build and run schema generator 33 | command: cargo schema --locked 34 | - run: 35 | name: Ensure checked-in source code and schemas are up-to-date 36 | command: | 37 | CHANGES_IN_REPO=$(git status --porcelain) 38 | if [[ -n "$CHANGES_IN_REPO" ]]; then 39 | echo "Repository is dirty. Showing 'git status' and 'git --no-pager diff' for debugging now:" 40 | git status && git --no-pager diff 41 | exit 1 42 | fi 43 | - save_cache: 44 | paths: 45 | - /usr/local/cargo/registry 46 | - target/debug/.fingerprint 47 | - target/debug/build 48 | - target/debug/deps 49 | - target/wasm32-unknown-unknown/release/.fingerprint 50 | - target/wasm32-unknown-unknown/release/build 51 | - target/wasm32-unknown-unknown/release/deps 52 | key: v4-cargo-cache-{{ arch }}-{{ checksum "Cargo.lock" }} 53 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | indent_style = space 5 | indent_size = 2 6 | charset = utf-8 7 | trim_trailing_whitespace = true 8 | insert_final_newline = true 9 | 10 | [*.rs] 11 | indent_size = 4 12 | -------------------------------------------------------------------------------- /.github/workflows/Basic.yml: -------------------------------------------------------------------------------- 1 | # Based on https://github.com/actions-rs/example/blob/master/.github/workflows/quickstart.yml 2 | 3 | on: [push, pull_request] 4 | 5 | name: Basic 6 | 7 | jobs: 8 | 9 | test: 10 | name: Test Suite 11 | runs-on: ubuntu-latest 12 | steps: 13 | - name: Checkout sources 14 | uses: actions/checkout@v2 15 | 16 | - name: Install stable toolchain 17 | uses: actions-rs/toolchain@v1 18 | with: 19 | profile: minimal 20 | toolchain: 1.46.0 21 | target: wasm32-unknown-unknown 22 | override: true 23 | 24 | - name: Run unit tests 25 | uses: actions-rs/cargo@v1 26 | with: 27 | command: unit-test 28 | args: --locked 29 | env: 30 | RUST_BACKTRACE: 1 31 | 32 | - name: Compile WASM contract 33 | uses: actions-rs/cargo@v1 34 | with: 35 | command: wasm 36 | args: --locked 37 | env: 38 | RUSTFLAGS: "-C link-arg=-s" 39 | 40 | - name: Run integration tests 41 | uses: actions-rs/cargo@v1 42 | with: 43 | command: integration-test 44 | args: --locked 45 | 46 | 47 | lints: 48 | name: Lints 49 | runs-on: ubuntu-latest 50 | steps: 51 | - name: Checkout sources 52 | uses: actions/checkout@v2 53 | 54 | - name: Install stable toolchain 55 | uses: actions-rs/toolchain@v1 56 | with: 57 | profile: minimal 58 | toolchain: 1.46.0 59 | override: true 60 | components: rustfmt, clippy 61 | 62 | - name: Run cargo fmt 63 | uses: actions-rs/cargo@v1 64 | with: 65 | command: fmt 66 | args: --all -- --check 67 | 68 | - name: Run cargo clippy 69 | uses: actions-rs/cargo@v1 70 | with: 71 | command: clippy 72 | args: -- -D warnings 73 | 74 | # TODO: we should check 75 | # CHANGES_IN_REPO=$(git status --porcelain) 76 | # after this, but I don't know how 77 | - name: Generate Schema 78 | uses: actions-rs/cargo@v1 79 | with: 80 | command: schema 81 | args: --locked 82 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Build results 2 | /target 3 | contract.wasm 4 | contract.wasm.gz 5 | 6 | # Binaries 7 | *.wasm 8 | *.wasm.gz 9 | 10 | # Cargo+Git helper file (https://github.com/rust-lang/cargo/blob/0.44.1/src/cargo/sources/git/utils.rs#L320-L327) 11 | .cargo-ok 12 | 13 | # Text file backups 14 | **/*.rs.bk 15 | 16 | # macOS 17 | .DS_Store 18 | 19 | # IDEs 20 | *.iml 21 | .idea 22 | -------------------------------------------------------------------------------- /Cargo.lock: -------------------------------------------------------------------------------- 1 | # This file is automatically @generated by Cargo. 2 | # It is not intended for manual editing. 3 | [[package]] 4 | name = "addr2line" 5 | version = "0.13.0" 6 | source = "registry+https://github.com/rust-lang/crates.io-index" 7 | checksum = "1b6a2d3371669ab3ca9797670853d61402b03d0b4b9ebf33d677dfa720203072" 8 | dependencies = [ 9 | "gimli", 10 | ] 11 | 12 | [[package]] 13 | name = "adler" 14 | version = "0.2.2" 15 | source = "registry+https://github.com/rust-lang/crates.io-index" 16 | checksum = "ccc9a9dd069569f212bc4330af9f17c4afb5e8ce185e83dbb14f1349dda18b10" 17 | 18 | [[package]] 19 | name = "ansi_term" 20 | version = "0.11.0" 21 | source = "registry+https://github.com/rust-lang/crates.io-index" 22 | checksum = "ee49baf6cb617b853aa8d93bf420db2383fab46d314482ca2803b40d5fde979b" 23 | dependencies = [ 24 | "winapi", 25 | ] 26 | 27 | [[package]] 28 | name = "arrayref" 29 | version = "0.3.6" 30 | source = "registry+https://github.com/rust-lang/crates.io-index" 31 | checksum = "a4c527152e37cf757a3f78aae5a06fbeefdb07ccc535c980a3208ee3060dd544" 32 | 33 | [[package]] 34 | name = "atty" 35 | version = "0.2.14" 36 | source = "registry+https://github.com/rust-lang/crates.io-index" 37 | checksum = "d9b39be18770d11421cdb1b9947a45dd3f37e93092cbf377614828a319d5fee8" 38 | dependencies = [ 39 | "hermit-abi", 40 | "libc", 41 | "winapi", 42 | ] 43 | 44 | [[package]] 45 | name = "backtrace" 46 | version = "0.3.50" 47 | source = "registry+https://github.com/rust-lang/crates.io-index" 48 | checksum = "46254cf2fdcdf1badb5934448c1bcbe046a56537b3987d96c51a7afc5d03f293" 49 | dependencies = [ 50 | "addr2line", 51 | "cfg-if", 52 | "libc", 53 | "miniz_oxide", 54 | "object", 55 | "rustc-demangle", 56 | ] 57 | 58 | [[package]] 59 | name = "base64" 60 | version = "0.11.0" 61 | source = "registry+https://github.com/rust-lang/crates.io-index" 62 | checksum = "b41b7ea54a0c9d92199de89e20e58d49f02f8e699814ef3fdf266f6f748d15c7" 63 | 64 | [[package]] 65 | name = "base64" 66 | version = "0.12.3" 67 | source = "registry+https://github.com/rust-lang/crates.io-index" 68 | checksum = "3441f0f7b02788e948e47f457ca01f1d7e6d92c693bc132c22b087d3141c03ff" 69 | 70 | [[package]] 71 | name = "bincode2" 72 | version = "2.0.1" 73 | source = "registry+https://github.com/rust-lang/crates.io-index" 74 | checksum = "f49f6183038e081170ebbbadee6678966c7d54728938a3e7de7f4e780770318f" 75 | dependencies = [ 76 | "byteorder", 77 | "serde", 78 | ] 79 | 80 | [[package]] 81 | name = "bitflags" 82 | version = "1.2.1" 83 | source = "registry+https://github.com/rust-lang/crates.io-index" 84 | checksum = "cf1de2fe8c75bc145a2f577add951f8134889b4795d47466a54a5c846d691693" 85 | 86 | [[package]] 87 | name = "block-buffer" 88 | version = "0.7.3" 89 | source = "registry+https://github.com/rust-lang/crates.io-index" 90 | checksum = "c0940dc441f31689269e10ac70eb1002a3a1d3ad1390e030043662eb7fe4688b" 91 | dependencies = [ 92 | "block-padding", 93 | "byte-tools", 94 | "byteorder", 95 | "generic-array 0.12.3", 96 | ] 97 | 98 | [[package]] 99 | name = "block-buffer" 100 | version = "0.9.0" 101 | source = "registry+https://github.com/rust-lang/crates.io-index" 102 | checksum = "4152116fd6e9dadb291ae18fc1ec3575ed6d84c29642d97890f4b4a3417297e4" 103 | dependencies = [ 104 | "generic-array 0.14.3", 105 | ] 106 | 107 | [[package]] 108 | name = "block-padding" 109 | version = "0.1.5" 110 | source = "registry+https://github.com/rust-lang/crates.io-index" 111 | checksum = "fa79dedbb091f449f1f39e53edf88d5dbe95f895dae6135a8d7b881fb5af73f5" 112 | dependencies = [ 113 | "byte-tools", 114 | ] 115 | 116 | [[package]] 117 | name = "byte-tools" 118 | version = "0.3.1" 119 | source = "registry+https://github.com/rust-lang/crates.io-index" 120 | checksum = "e3b5ca7a04898ad4bcd41c90c5285445ff5b791899bb1b0abdd2a2aa791211d7" 121 | 122 | [[package]] 123 | name = "byteorder" 124 | version = "1.3.4" 125 | source = "registry+https://github.com/rust-lang/crates.io-index" 126 | checksum = "08c48aae112d48ed9f069b33538ea9e3e90aa263cfa3d1c24309612b1f7472de" 127 | 128 | [[package]] 129 | name = "cbindgen" 130 | version = "0.13.2" 131 | source = "registry+https://github.com/rust-lang/crates.io-index" 132 | checksum = "2db2df1ebc842c41fd2c4ae5b5a577faf63bd5151b953db752fc686812bee318" 133 | dependencies = [ 134 | "clap", 135 | "log", 136 | "proc-macro2", 137 | "quote", 138 | "serde", 139 | "serde_json", 140 | "syn", 141 | "tempfile", 142 | "toml", 143 | ] 144 | 145 | [[package]] 146 | name = "cfg-if" 147 | version = "0.1.10" 148 | source = "registry+https://github.com/rust-lang/crates.io-index" 149 | checksum = "4785bdd1c96b2a846b2bd7cc02e86b6b3dbf14e7e53446c4f54c92a361040822" 150 | 151 | [[package]] 152 | name = "clap" 153 | version = "2.33.1" 154 | source = "registry+https://github.com/rust-lang/crates.io-index" 155 | checksum = "bdfa80d47f954d53a35a64987ca1422f495b8d6483c0fe9f7117b36c2a792129" 156 | dependencies = [ 157 | "ansi_term", 158 | "atty", 159 | "bitflags", 160 | "strsim", 161 | "textwrap", 162 | "unicode-width", 163 | "vec_map", 164 | ] 165 | 166 | [[package]] 167 | name = "cosmwasm-schema" 168 | version = "0.9.3" 169 | source = "registry+https://github.com/rust-lang/crates.io-index" 170 | checksum = "7db3d55bfec47df9a54b16dfa87153685e4341459c0b60490a159cfa172f88d3" 171 | dependencies = [ 172 | "schemars", 173 | "serde_json", 174 | ] 175 | 176 | [[package]] 177 | name = "cosmwasm-sgx-vm" 178 | version = "0.10.0" 179 | source = "git+https://github.com/enigmampc/SecretNetwork?tag=v1.0.0#490fba9243e6cb291462e9d3c1bcbd1975c0df1e" 180 | dependencies = [ 181 | "base64 0.12.3", 182 | "cosmwasm-std", 183 | "enclave-ffi-types", 184 | "hex", 185 | "lazy_static", 186 | "log", 187 | "memmap", 188 | "parity-wasm", 189 | "schemars", 190 | "serde", 191 | "serde_json", 192 | "sgx_types", 193 | "sgx_urts", 194 | "sha2 0.9.1", 195 | "snafu", 196 | ] 197 | 198 | [[package]] 199 | name = "cosmwasm-std" 200 | version = "0.10.0" 201 | source = "git+https://github.com/enigmampc/SecretNetwork?tag=v1.0.0#490fba9243e6cb291462e9d3c1bcbd1975c0df1e" 202 | dependencies = [ 203 | "base64 0.11.0", 204 | "schemars", 205 | "serde", 206 | "serde-json-wasm", 207 | "snafu", 208 | ] 209 | 210 | [[package]] 211 | name = "cosmwasm-storage" 212 | version = "0.10.0" 213 | source = "git+https://github.com/enigmampc/SecretNetwork?tag=v1.0.0#490fba9243e6cb291462e9d3c1bcbd1975c0df1e" 214 | dependencies = [ 215 | "cosmwasm-std", 216 | "serde", 217 | ] 218 | 219 | [[package]] 220 | name = "cpuid-bool" 221 | version = "0.1.2" 222 | source = "registry+https://github.com/rust-lang/crates.io-index" 223 | checksum = "8aebca1129a03dc6dc2b127edd729435bbc4a37e1d5f4d7513165089ceb02634" 224 | 225 | [[package]] 226 | name = "crunchy" 227 | version = "0.2.2" 228 | source = "registry+https://github.com/rust-lang/crates.io-index" 229 | checksum = "7a81dae078cea95a014a339291cec439d2f232ebe854a9d672b796c6afafa9b7" 230 | 231 | [[package]] 232 | name = "crypto-mac" 233 | version = "0.7.0" 234 | source = "registry+https://github.com/rust-lang/crates.io-index" 235 | checksum = "4434400df11d95d556bac068ddfedd482915eb18fe8bea89bc80b6e4b1c179e5" 236 | dependencies = [ 237 | "generic-array 0.12.3", 238 | "subtle 1.0.0", 239 | ] 240 | 241 | [[package]] 242 | name = "derive_more" 243 | version = "0.99.9" 244 | source = "registry+https://github.com/rust-lang/crates.io-index" 245 | checksum = "298998b1cf6b5b2c8a7b023dfd45821825ce3ba8a8af55c921a0e734e4653f76" 246 | dependencies = [ 247 | "proc-macro2", 248 | "quote", 249 | "syn", 250 | ] 251 | 252 | [[package]] 253 | name = "digest" 254 | version = "0.8.1" 255 | source = "registry+https://github.com/rust-lang/crates.io-index" 256 | checksum = "f3d0c8c8752312f9713efd397ff63acb9f85585afbf179282e720e7704954dd5" 257 | dependencies = [ 258 | "generic-array 0.12.3", 259 | ] 260 | 261 | [[package]] 262 | name = "digest" 263 | version = "0.9.0" 264 | source = "registry+https://github.com/rust-lang/crates.io-index" 265 | checksum = "d3dd60d1080a57a05ab032377049e0591415d2b31afd7028356dbf3cc6dcb066" 266 | dependencies = [ 267 | "generic-array 0.14.3", 268 | ] 269 | 270 | [[package]] 271 | name = "doc-comment" 272 | version = "0.3.3" 273 | source = "registry+https://github.com/rust-lang/crates.io-index" 274 | checksum = "fea41bba32d969b513997752735605054bc0dfa92b4c56bf1189f2e174be7a10" 275 | 276 | [[package]] 277 | name = "enclave-ffi-types" 278 | version = "0.1.0" 279 | source = "git+https://github.com/enigmampc/SecretNetwork?tag=v1.0.0#490fba9243e6cb291462e9d3c1bcbd1975c0df1e" 280 | dependencies = [ 281 | "cbindgen", 282 | "derive_more", 283 | "thiserror", 284 | ] 285 | 286 | [[package]] 287 | name = "fake-simd" 288 | version = "0.1.2" 289 | source = "registry+https://github.com/rust-lang/crates.io-index" 290 | checksum = "e88a8acf291dafb59c2d96e8f59828f3838bb1a70398823ade51a84de6a6deed" 291 | 292 | [[package]] 293 | name = "generic-array" 294 | version = "0.12.3" 295 | source = "registry+https://github.com/rust-lang/crates.io-index" 296 | checksum = "c68f0274ae0e023facc3c97b2e00f076be70e254bc851d972503b328db79b2ec" 297 | dependencies = [ 298 | "typenum", 299 | ] 300 | 301 | [[package]] 302 | name = "generic-array" 303 | version = "0.14.3" 304 | source = "registry+https://github.com/rust-lang/crates.io-index" 305 | checksum = "60fb4bb6bba52f78a471264d9a3b7d026cc0af47b22cd2cffbc0b787ca003e63" 306 | dependencies = [ 307 | "typenum", 308 | "version_check", 309 | ] 310 | 311 | [[package]] 312 | name = "getrandom" 313 | version = "0.1.14" 314 | source = "registry+https://github.com/rust-lang/crates.io-index" 315 | checksum = "7abc8dd8451921606d809ba32e95b6111925cd2906060d2dcc29c070220503eb" 316 | dependencies = [ 317 | "cfg-if", 318 | "libc", 319 | "wasi", 320 | ] 321 | 322 | [[package]] 323 | name = "gimli" 324 | version = "0.22.0" 325 | source = "registry+https://github.com/rust-lang/crates.io-index" 326 | checksum = "aaf91faf136cb47367fa430cd46e37a788775e7fa104f8b4bcb3861dc389b724" 327 | 328 | [[package]] 329 | name = "hermit-abi" 330 | version = "0.1.15" 331 | source = "registry+https://github.com/rust-lang/crates.io-index" 332 | checksum = "3deed196b6e7f9e44a2ae8d94225d80302d81208b1bb673fd21fe634645c85a9" 333 | dependencies = [ 334 | "libc", 335 | ] 336 | 337 | [[package]] 338 | name = "hex" 339 | version = "0.4.2" 340 | source = "registry+https://github.com/rust-lang/crates.io-index" 341 | checksum = "644f9158b2f133fd50f5fb3242878846d9eb792e445c893805ff0e3824006e35" 342 | 343 | [[package]] 344 | name = "hmac" 345 | version = "0.7.1" 346 | source = "registry+https://github.com/rust-lang/crates.io-index" 347 | checksum = "5dcb5e64cda4c23119ab41ba960d1e170a774c8e4b9d9e6a9bc18aabf5e59695" 348 | dependencies = [ 349 | "crypto-mac", 350 | "digest 0.8.1", 351 | ] 352 | 353 | [[package]] 354 | name = "hmac-drbg" 355 | version = "0.2.0" 356 | source = "registry+https://github.com/rust-lang/crates.io-index" 357 | checksum = "c6e570451493f10f6581b48cdd530413b63ea9e780f544bfd3bdcaa0d89d1a7b" 358 | dependencies = [ 359 | "digest 0.8.1", 360 | "generic-array 0.12.3", 361 | "hmac", 362 | ] 363 | 364 | [[package]] 365 | name = "itoa" 366 | version = "0.4.6" 367 | source = "registry+https://github.com/rust-lang/crates.io-index" 368 | checksum = "dc6f3ad7b9d11a0c00842ff8de1b60ee58661048eb8049ed33c73594f359d7e6" 369 | 370 | [[package]] 371 | name = "lazy_static" 372 | version = "1.4.0" 373 | source = "registry+https://github.com/rust-lang/crates.io-index" 374 | checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" 375 | 376 | [[package]] 377 | name = "libc" 378 | version = "0.2.72" 379 | source = "registry+https://github.com/rust-lang/crates.io-index" 380 | checksum = "a9f8082297d534141b30c8d39e9b1773713ab50fdbe4ff30f750d063b3bfd701" 381 | 382 | [[package]] 383 | name = "libsecp256k1" 384 | version = "0.3.5" 385 | source = "registry+https://github.com/rust-lang/crates.io-index" 386 | checksum = "1fc1e2c808481a63dc6da2074752fdd4336a3c8fcc68b83db6f1fd5224ae7962" 387 | dependencies = [ 388 | "arrayref", 389 | "crunchy", 390 | "digest 0.8.1", 391 | "hmac-drbg", 392 | "rand", 393 | "sha2 0.8.2", 394 | "subtle 2.2.3", 395 | "typenum", 396 | ] 397 | 398 | [[package]] 399 | name = "log" 400 | version = "0.4.8" 401 | source = "registry+https://github.com/rust-lang/crates.io-index" 402 | checksum = "14b6052be84e6b71ab17edffc2eeabf5c2c3ae1fdb464aae35ac50c67a44e1f7" 403 | dependencies = [ 404 | "cfg-if", 405 | ] 406 | 407 | [[package]] 408 | name = "memmap" 409 | version = "0.7.0" 410 | source = "registry+https://github.com/rust-lang/crates.io-index" 411 | checksum = "6585fd95e7bb50d6cc31e20d4cf9afb4e2ba16c5846fc76793f11218da9c475b" 412 | dependencies = [ 413 | "libc", 414 | "winapi", 415 | ] 416 | 417 | [[package]] 418 | name = "miniz_oxide" 419 | version = "0.4.0" 420 | source = "registry+https://github.com/rust-lang/crates.io-index" 421 | checksum = "be0f75932c1f6cfae3c04000e40114adf955636e19040f9c0a2c380702aa1c7f" 422 | dependencies = [ 423 | "adler", 424 | ] 425 | 426 | [[package]] 427 | name = "object" 428 | version = "0.20.0" 429 | source = "registry+https://github.com/rust-lang/crates.io-index" 430 | checksum = "1ab52be62400ca80aa00285d25253d7f7c437b7375c4de678f5405d3afe82ca5" 431 | 432 | [[package]] 433 | name = "opaque-debug" 434 | version = "0.2.3" 435 | source = "registry+https://github.com/rust-lang/crates.io-index" 436 | checksum = "2839e79665f131bdb5782e51f2c6c9599c133c6098982a54c794358bf432529c" 437 | 438 | [[package]] 439 | name = "opaque-debug" 440 | version = "0.3.0" 441 | source = "registry+https://github.com/rust-lang/crates.io-index" 442 | checksum = "624a8340c38c1b80fd549087862da4ba43e08858af025b236e509b6649fc13d5" 443 | 444 | [[package]] 445 | name = "parity-wasm" 446 | version = "0.41.0" 447 | source = "registry+https://github.com/rust-lang/crates.io-index" 448 | checksum = "ddfc878dac00da22f8f61e7af3157988424567ab01d9920b962ef7dcbd7cd865" 449 | 450 | [[package]] 451 | name = "ppv-lite86" 452 | version = "0.2.8" 453 | source = "registry+https://github.com/rust-lang/crates.io-index" 454 | checksum = "237a5ed80e274dbc66f86bd59c1e25edc039660be53194b5fe0a482e0f2612ea" 455 | 456 | [[package]] 457 | name = "proc-macro2" 458 | version = "1.0.18" 459 | source = "registry+https://github.com/rust-lang/crates.io-index" 460 | checksum = "beae6331a816b1f65d04c45b078fd8e6c93e8071771f41b8163255bbd8d7c8fa" 461 | dependencies = [ 462 | "unicode-xid", 463 | ] 464 | 465 | [[package]] 466 | name = "quote" 467 | version = "1.0.7" 468 | source = "registry+https://github.com/rust-lang/crates.io-index" 469 | checksum = "aa563d17ecb180e500da1cfd2b028310ac758de548efdd203e18f283af693f37" 470 | dependencies = [ 471 | "proc-macro2", 472 | ] 473 | 474 | [[package]] 475 | name = "rand" 476 | version = "0.7.3" 477 | source = "registry+https://github.com/rust-lang/crates.io-index" 478 | checksum = "6a6b1679d49b24bbfe0c803429aa1874472f50d9b363131f0e89fc356b544d03" 479 | dependencies = [ 480 | "getrandom", 481 | "libc", 482 | "rand_chacha", 483 | "rand_core", 484 | "rand_hc", 485 | ] 486 | 487 | [[package]] 488 | name = "rand_chacha" 489 | version = "0.2.2" 490 | source = "registry+https://github.com/rust-lang/crates.io-index" 491 | checksum = "f4c8ed856279c9737206bf725bf36935d8666ead7aa69b52be55af369d193402" 492 | dependencies = [ 493 | "ppv-lite86", 494 | "rand_core", 495 | ] 496 | 497 | [[package]] 498 | name = "rand_core" 499 | version = "0.5.1" 500 | source = "registry+https://github.com/rust-lang/crates.io-index" 501 | checksum = "90bde5296fc891b0cef12a6d03ddccc162ce7b2aff54160af9338f8d40df6d19" 502 | dependencies = [ 503 | "getrandom", 504 | ] 505 | 506 | [[package]] 507 | name = "rand_hc" 508 | version = "0.2.0" 509 | source = "registry+https://github.com/rust-lang/crates.io-index" 510 | checksum = "ca3129af7b92a17112d59ad498c6f81eaf463253766b90396d39ea7a39d6613c" 511 | dependencies = [ 512 | "rand_core", 513 | ] 514 | 515 | [[package]] 516 | name = "redox_syscall" 517 | version = "0.1.56" 518 | source = "registry+https://github.com/rust-lang/crates.io-index" 519 | checksum = "2439c63f3f6139d1b57529d16bc3b8bb855230c8efcc5d3a896c8bea7c3b1e84" 520 | 521 | [[package]] 522 | name = "remove_dir_all" 523 | version = "0.5.3" 524 | source = "registry+https://github.com/rust-lang/crates.io-index" 525 | checksum = "3acd125665422973a33ac9d3dd2df85edad0f4ae9b00dafb1a05e43a9f5ef8e7" 526 | dependencies = [ 527 | "winapi", 528 | ] 529 | 530 | [[package]] 531 | name = "rustc-demangle" 532 | version = "0.1.16" 533 | source = "registry+https://github.com/rust-lang/crates.io-index" 534 | checksum = "4c691c0e608126e00913e33f0ccf3727d5fc84573623b8d65b2df340b5201783" 535 | 536 | [[package]] 537 | name = "ryu" 538 | version = "1.0.5" 539 | source = "registry+https://github.com/rust-lang/crates.io-index" 540 | checksum = "71d301d4193d031abdd79ff7e3dd721168a9572ef3fe51a1517aba235bd8f86e" 541 | 542 | [[package]] 543 | name = "schemars" 544 | version = "0.7.6" 545 | source = "registry+https://github.com/rust-lang/crates.io-index" 546 | checksum = "be77ed66abed6954aabf6a3e31a84706bedbf93750d267e92ef4a6d90bbd6a61" 547 | dependencies = [ 548 | "schemars_derive", 549 | "serde", 550 | "serde_json", 551 | ] 552 | 553 | [[package]] 554 | name = "schemars_derive" 555 | version = "0.7.6" 556 | source = "registry+https://github.com/rust-lang/crates.io-index" 557 | checksum = "11af7a475c9ee266cfaa9e303a47c830ebe072bf3101ab907a7b7b9d816fa01d" 558 | dependencies = [ 559 | "proc-macro2", 560 | "quote", 561 | "serde_derive_internals", 562 | "syn", 563 | ] 564 | 565 | [[package]] 566 | name = "secret-secret" 567 | version = "0.1.0" 568 | dependencies = [ 569 | "base64 0.12.3", 570 | "bincode2", 571 | "cosmwasm-schema", 572 | "cosmwasm-sgx-vm", 573 | "cosmwasm-std", 574 | "cosmwasm-storage", 575 | "hex", 576 | "schemars", 577 | "secret-toolkit", 578 | "serde", 579 | "sha2 0.9.1", 580 | "snafu", 581 | "subtle 2.2.3", 582 | ] 583 | 584 | [[package]] 585 | name = "secret-toolkit" 586 | version = "0.1.0" 587 | source = "git+https://github.com/enigmampc/secret-toolkit?rev=444d918#444d91818411a908425ef3bbf3e0051920b4ce48" 588 | dependencies = [ 589 | "secret-toolkit-crypto", 590 | "secret-toolkit-serialization", 591 | "secret-toolkit-storage", 592 | "secret-toolkit-utils", 593 | ] 594 | 595 | [[package]] 596 | name = "secret-toolkit-crypto" 597 | version = "0.1.0" 598 | source = "git+https://github.com/enigmampc/secret-toolkit?rev=444d918#444d91818411a908425ef3bbf3e0051920b4ce48" 599 | dependencies = [ 600 | "cosmwasm-std", 601 | "libsecp256k1", 602 | "rand_chacha", 603 | "rand_core", 604 | "sha2 0.9.1", 605 | ] 606 | 607 | [[package]] 608 | name = "secret-toolkit-serialization" 609 | version = "0.1.0" 610 | source = "git+https://github.com/enigmampc/secret-toolkit?rev=444d918#444d91818411a908425ef3bbf3e0051920b4ce48" 611 | dependencies = [ 612 | "bincode2", 613 | "cosmwasm-std", 614 | "serde", 615 | ] 616 | 617 | [[package]] 618 | name = "secret-toolkit-storage" 619 | version = "0.1.0" 620 | source = "git+https://github.com/enigmampc/secret-toolkit?rev=444d918#444d91818411a908425ef3bbf3e0051920b4ce48" 621 | dependencies = [ 622 | "cosmwasm-std", 623 | "cosmwasm-storage", 624 | "secret-toolkit-serialization", 625 | "serde", 626 | ] 627 | 628 | [[package]] 629 | name = "secret-toolkit-utils" 630 | version = "0.1.0" 631 | source = "git+https://github.com/enigmampc/secret-toolkit?rev=444d918#444d91818411a908425ef3bbf3e0051920b4ce48" 632 | dependencies = [ 633 | "cosmwasm-std", 634 | "schemars", 635 | "serde", 636 | ] 637 | 638 | [[package]] 639 | name = "serde" 640 | version = "1.0.114" 641 | source = "registry+https://github.com/rust-lang/crates.io-index" 642 | checksum = "5317f7588f0a5078ee60ef675ef96735a1442132dc645eb1d12c018620ed8cd3" 643 | dependencies = [ 644 | "serde_derive", 645 | ] 646 | 647 | [[package]] 648 | name = "serde-json-wasm" 649 | version = "0.2.1" 650 | source = "registry+https://github.com/rust-lang/crates.io-index" 651 | checksum = "7294d94d390f1d2334697c065ea591d7074c676e2d20aa6f1df752fced29823f" 652 | dependencies = [ 653 | "serde", 654 | ] 655 | 656 | [[package]] 657 | name = "serde_derive" 658 | version = "1.0.114" 659 | source = "registry+https://github.com/rust-lang/crates.io-index" 660 | checksum = "2a0be94b04690fbaed37cddffc5c134bf537c8e3329d53e982fe04c374978f8e" 661 | dependencies = [ 662 | "proc-macro2", 663 | "quote", 664 | "syn", 665 | ] 666 | 667 | [[package]] 668 | name = "serde_derive_internals" 669 | version = "0.25.0" 670 | source = "registry+https://github.com/rust-lang/crates.io-index" 671 | checksum = "1dbab34ca63057a1f15280bdf3c39f2b1eb1b54c17e98360e511637aef7418c6" 672 | dependencies = [ 673 | "proc-macro2", 674 | "quote", 675 | "syn", 676 | ] 677 | 678 | [[package]] 679 | name = "serde_json" 680 | version = "1.0.57" 681 | source = "registry+https://github.com/rust-lang/crates.io-index" 682 | checksum = "164eacbdb13512ec2745fb09d51fd5b22b0d65ed294a1dcf7285a360c80a675c" 683 | dependencies = [ 684 | "itoa", 685 | "ryu", 686 | "serde", 687 | ] 688 | 689 | [[package]] 690 | name = "sgx_types" 691 | version = "1.1.2" 692 | source = "git+https://github.com/apache/teaclave-sgx-sdk.git?rev=v1.1.2#8f065be7a442157bf16dc7106feb795ea1c578eb" 693 | 694 | [[package]] 695 | name = "sgx_urts" 696 | version = "1.1.2" 697 | source = "git+https://github.com/apache/teaclave-sgx-sdk.git?rev=v1.1.2#8f065be7a442157bf16dc7106feb795ea1c578eb" 698 | dependencies = [ 699 | "libc", 700 | "sgx_types", 701 | ] 702 | 703 | [[package]] 704 | name = "sha2" 705 | version = "0.8.2" 706 | source = "registry+https://github.com/rust-lang/crates.io-index" 707 | checksum = "a256f46ea78a0c0d9ff00077504903ac881a1dafdc20da66545699e7776b3e69" 708 | dependencies = [ 709 | "block-buffer 0.7.3", 710 | "digest 0.8.1", 711 | "fake-simd", 712 | "opaque-debug 0.2.3", 713 | ] 714 | 715 | [[package]] 716 | name = "sha2" 717 | version = "0.9.1" 718 | source = "registry+https://github.com/rust-lang/crates.io-index" 719 | checksum = "2933378ddfeda7ea26f48c555bdad8bb446bf8a3d17832dc83e380d444cfb8c1" 720 | dependencies = [ 721 | "block-buffer 0.9.0", 722 | "cfg-if", 723 | "cpuid-bool", 724 | "digest 0.9.0", 725 | "opaque-debug 0.3.0", 726 | ] 727 | 728 | [[package]] 729 | name = "snafu" 730 | version = "0.6.8" 731 | source = "registry+https://github.com/rust-lang/crates.io-index" 732 | checksum = "c7f5aed652511f5c9123cf2afbe9c244c29db6effa2abb05c866e965c82405ce" 733 | dependencies = [ 734 | "backtrace", 735 | "doc-comment", 736 | "snafu-derive", 737 | ] 738 | 739 | [[package]] 740 | name = "snafu-derive" 741 | version = "0.6.8" 742 | source = "registry+https://github.com/rust-lang/crates.io-index" 743 | checksum = "ebf8f7d5720104a9df0f7076a8682024e958bba0fe9848767bb44f251f3648e9" 744 | dependencies = [ 745 | "proc-macro2", 746 | "quote", 747 | "syn", 748 | ] 749 | 750 | [[package]] 751 | name = "strsim" 752 | version = "0.8.0" 753 | source = "registry+https://github.com/rust-lang/crates.io-index" 754 | checksum = "8ea5119cdb4c55b55d432abb513a0429384878c15dde60cc77b1c99de1a95a6a" 755 | 756 | [[package]] 757 | name = "subtle" 758 | version = "1.0.0" 759 | source = "registry+https://github.com/rust-lang/crates.io-index" 760 | checksum = "2d67a5a62ba6e01cb2192ff309324cb4875d0c451d55fe2319433abe7a05a8ee" 761 | 762 | [[package]] 763 | name = "subtle" 764 | version = "2.2.3" 765 | source = "registry+https://github.com/rust-lang/crates.io-index" 766 | checksum = "502d53007c02d7605a05df1c1a73ee436952781653da5d0bf57ad608f66932c1" 767 | 768 | [[package]] 769 | name = "syn" 770 | version = "1.0.33" 771 | source = "registry+https://github.com/rust-lang/crates.io-index" 772 | checksum = "e8d5d96e8cbb005d6959f119f773bfaebb5684296108fb32600c00cde305b2cd" 773 | dependencies = [ 774 | "proc-macro2", 775 | "quote", 776 | "unicode-xid", 777 | ] 778 | 779 | [[package]] 780 | name = "tempfile" 781 | version = "3.1.0" 782 | source = "registry+https://github.com/rust-lang/crates.io-index" 783 | checksum = "7a6e24d9338a0a5be79593e2fa15a648add6138caa803e2d5bc782c371732ca9" 784 | dependencies = [ 785 | "cfg-if", 786 | "libc", 787 | "rand", 788 | "redox_syscall", 789 | "remove_dir_all", 790 | "winapi", 791 | ] 792 | 793 | [[package]] 794 | name = "textwrap" 795 | version = "0.11.0" 796 | source = "registry+https://github.com/rust-lang/crates.io-index" 797 | checksum = "d326610f408c7a4eb6f51c37c330e496b08506c9457c9d34287ecc38809fb060" 798 | dependencies = [ 799 | "unicode-width", 800 | ] 801 | 802 | [[package]] 803 | name = "thiserror" 804 | version = "1.0.20" 805 | source = "registry+https://github.com/rust-lang/crates.io-index" 806 | checksum = "7dfdd070ccd8ccb78f4ad66bf1982dc37f620ef696c6b5028fe2ed83dd3d0d08" 807 | dependencies = [ 808 | "thiserror-impl", 809 | ] 810 | 811 | [[package]] 812 | name = "thiserror-impl" 813 | version = "1.0.20" 814 | source = "registry+https://github.com/rust-lang/crates.io-index" 815 | checksum = "bd80fc12f73063ac132ac92aceea36734f04a1d93c1240c6944e23a3b8841793" 816 | dependencies = [ 817 | "proc-macro2", 818 | "quote", 819 | "syn", 820 | ] 821 | 822 | [[package]] 823 | name = "toml" 824 | version = "0.5.6" 825 | source = "registry+https://github.com/rust-lang/crates.io-index" 826 | checksum = "ffc92d160b1eef40665be3a05630d003936a3bc7da7421277846c2613e92c71a" 827 | dependencies = [ 828 | "serde", 829 | ] 830 | 831 | [[package]] 832 | name = "typenum" 833 | version = "1.12.0" 834 | source = "registry+https://github.com/rust-lang/crates.io-index" 835 | checksum = "373c8a200f9e67a0c95e62a4f52fbf80c23b4381c05a17845531982fa99e6b33" 836 | 837 | [[package]] 838 | name = "unicode-width" 839 | version = "0.1.8" 840 | source = "registry+https://github.com/rust-lang/crates.io-index" 841 | checksum = "9337591893a19b88d8d87f2cec1e73fad5cdfd10e5a6f349f498ad6ea2ffb1e3" 842 | 843 | [[package]] 844 | name = "unicode-xid" 845 | version = "0.2.1" 846 | source = "registry+https://github.com/rust-lang/crates.io-index" 847 | checksum = "f7fe0bb3479651439c9112f72b6c505038574c9fbb575ed1bf3b797fa39dd564" 848 | 849 | [[package]] 850 | name = "vec_map" 851 | version = "0.8.2" 852 | source = "registry+https://github.com/rust-lang/crates.io-index" 853 | checksum = "f1bddf1187be692e79c5ffeab891132dfb0f236ed36a43c7ed39f1165ee20191" 854 | 855 | [[package]] 856 | name = "version_check" 857 | version = "0.9.2" 858 | source = "registry+https://github.com/rust-lang/crates.io-index" 859 | checksum = "b5a972e5669d67ba988ce3dc826706fb0a8b01471c088cb0b6110b805cc36aed" 860 | 861 | [[package]] 862 | name = "wasi" 863 | version = "0.9.0+wasi-snapshot-preview1" 864 | source = "registry+https://github.com/rust-lang/crates.io-index" 865 | checksum = "cccddf32554fecc6acb585f82a32a72e28b48f8c4c1883ddfeeeaa96f7d8e519" 866 | 867 | [[package]] 868 | name = "winapi" 869 | version = "0.3.9" 870 | source = "registry+https://github.com/rust-lang/crates.io-index" 871 | checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" 872 | dependencies = [ 873 | "winapi-i686-pc-windows-gnu", 874 | "winapi-x86_64-pc-windows-gnu", 875 | ] 876 | 877 | [[package]] 878 | name = "winapi-i686-pc-windows-gnu" 879 | version = "0.4.0" 880 | source = "registry+https://github.com/rust-lang/crates.io-index" 881 | checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" 882 | 883 | [[package]] 884 | name = "winapi-x86_64-pc-windows-gnu" 885 | version = "0.4.0" 886 | source = "registry+https://github.com/rust-lang/crates.io-index" 887 | checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" 888 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "secret-secret" 3 | version = "0.1.0" 4 | authors = ["Itzik "] 5 | edition = "2018" 6 | 7 | exclude = [ 8 | # Those files are rust-optimizer artifacts. You might want to commit them for convenience but they should not be part of the source code publication. 9 | "contract.wasm", 10 | "hash.txt", 11 | ] 12 | 13 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 14 | 15 | [lib] 16 | crate-type = ["cdylib", "rlib"] 17 | 18 | [profile.release] 19 | opt-level = 3 20 | debug = false 21 | rpath = false 22 | lto = true 23 | debug-assertions = false 24 | codegen-units = 1 25 | panic = 'abort' 26 | incremental = false 27 | overflow-checks = true 28 | 29 | [features] 30 | # for quicker tests, cargo test --lib 31 | # for more explicit tests, cargo test --features=backtraces 32 | backtraces = ["cosmwasm-std/backtraces", "cosmwasm-vm/backtraces"] 33 | 34 | [dependencies] 35 | cosmwasm-std = { git = "https://github.com/enigmampc/SecretNetwork", tag = "v1.0.0" } 36 | cosmwasm-storage = { git = "https://github.com/enigmampc/SecretNetwork", tag = "v1.0.0" } 37 | secret-toolkit = { git = "https://github.com/enigmampc/secret-toolkit", rev = "444d918" } 38 | schemars = "0.7" 39 | serde = { version = "1.0.103", default-features = false, features = ["derive"] } 40 | snafu = { version = "0.6.3" } 41 | bincode2 = "2.0.1" 42 | subtle = { version = "2.2.3", default-features = false } 43 | base64 = "0.12.3" 44 | hex = "0.4.2" 45 | 46 | sha2 = { version = "0.9.1", default-features = false } 47 | 48 | [dev-dependencies] 49 | cosmwasm-vm = { package = "cosmwasm-sgx-vm", git = "https://github.com/enigmampc/SecretNetwork", tag = "v1.0.0" } 50 | cosmwasm-schema = { version = "0.9.2" } 51 | -------------------------------------------------------------------------------- /Developing.md: -------------------------------------------------------------------------------- 1 | # Developing 2 | 3 | If you have recently created a contract with this template, you probably could use some 4 | help on how to build and test the contract, as well as prepare it for production. This 5 | file attempts to provide a brief overview, assuming you have installed a recent 6 | version of Rust already (eg. 1.41+). 7 | 8 | ## Prerequisites 9 | 10 | Before starting, make sure you have [rustup](https://rustup.rs/) along with a 11 | recent `rustc` and `cargo` version installed. Currently, we are testing on 1.41+. 12 | 13 | And you need to have the `wasm32-unknown-unknown` target installed as well. 14 | 15 | You can check that via: 16 | 17 | ```sh 18 | rustc --version 19 | cargo --version 20 | rustup target list --installed 21 | # if wasm32 is not listed above, run this 22 | rustup target add wasm32-unknown-unknown 23 | ``` 24 | 25 | ## Compiling and running tests 26 | 27 | Now that you created your custom contract, make sure you can compile and run it before 28 | making any changes. Go into the 29 | 30 | ```sh 31 | # this will produce a wasm build in ./target/wasm32-unknown-unknown/release/YOUR_NAME_HERE.wasm 32 | cargo wasm 33 | 34 | # this runs unit tests with helpful backtraces 35 | RUST_BACKTRACE=1 cargo unit-test 36 | 37 | # this runs integration tests with cranelift backend (uses rust stable) 38 | cargo integration-test 39 | 40 | # this runs integration tests with singlepass backend (needs rust nightly) 41 | cargo integration-test --no-default-features --features singlepass 42 | 43 | # auto-generate json schema 44 | cargo schema 45 | ``` 46 | 47 | The wasmer engine, embedded in `cosmwasm-vm` supports multiple backends: 48 | singlepass and cranelift. Singlepass has fast compile times and slower run times, 49 | and supportes gas metering. It also requires rust `nightly`. This is used as default 50 | when embedding `cosmwasm-vm` in `go-cosmwasm` and is needed to use if you want to 51 | check the gas usage. 52 | 53 | However, when just building contacts, if you don't want to worry about installing 54 | two rust toolchains, you can run all tests with cranelift. The integration tests 55 | may take a small bit longer, but the results will be the same. The only difference 56 | is that you can not check gas usage here, so if you wish to optimize gas, you must 57 | switch to nightly and run with cranelift. 58 | 59 | ### Understanding the tests 60 | 61 | The main code is in `src/contract.rs` and the unit tests there run in pure rust, 62 | which makes them very quick to execute and give nice output on failures, especially 63 | if you do `RUST_BACKTRACE=1 cargo unit-test`. 64 | 65 | However, we don't just want to test the logic rust, but also the compiled Wasm artifact 66 | inside a VM. You can look in `tests/integration.rs` to see some examples there. They 67 | load the Wasm binary into the vm and call the contract externally. Effort has been 68 | made that the syntax is very similar to the calls in the native rust contract and 69 | quite easy to code. In fact, usually you can just copy a few unit tests and modify 70 | a few lines to make an integration test (this should get even easier in a future release). 71 | 72 | To run the latest integration tests, you need to explicitely rebuild the Wasm file with 73 | `cargo wasm` and then run `cargo integration-test`. 74 | 75 | We consider testing critical for anything on a blockchain, and recommend to always keep 76 | the tests up to date. While doing active development, it is often simplest to disable 77 | the integration tests completely and iterate rapidly on the code in `contract.rs`, 78 | both the logic and the tests. Once the code is finalized, you can copy over some unit 79 | tests into the integration.rs and make the needed changes. This ensures the compiled 80 | Wasm also behaves as desired in the real system. 81 | 82 | ## Generating JSON Schema 83 | 84 | While the Wasm calls (`init`, `handle`, `query`) accept JSON, this is not enough 85 | information to use it. We need to expose the schema for the expected messages to the 86 | clients. You can generate this schema by calling `cargo schema`, which will output 87 | 4 files in `./schema`, corresponding to the 3 message types the contract accepts, 88 | as well as the internal `State`. 89 | 90 | These files are in standard json-schema format, which should be usable by various 91 | client side tools, either to auto-generate codecs, or just to validate incoming 92 | json wrt. the defined schema. 93 | 94 | ## Preparing the Wasm bytecode for production 95 | 96 | Before we upload it to a chain, we need to ensure the smallest output size possible, 97 | as this will be included in the body of a transaction. We also want to have a 98 | reproducible build process, so third parties can verify that the uploaded Wasm 99 | code did indeed come from the claimed rust code. 100 | 101 | To solve both these issues, we have produced `rust-optimizer`, a docker image to 102 | produce an extremely small build output in a consistent manner. The suggest way 103 | to run it is this: 104 | 105 | ```sh 106 | docker run --rm -v "$(pwd)":/code \ 107 | --mount type=volume,source="$(basename "$(pwd)")_cache",target=/code/target \ 108 | --mount type=volume,source=registry_cache,target=/usr/local/cargo/registry \ 109 | cosmwasm/rust-optimizer:0.8.0 110 | ``` 111 | 112 | We must mount the contract code to `/code`. You can use a absolute path instead 113 | of `$(pwd)` if you don't want to `cd` to the directory first. The other two 114 | volumes are nice for speedup. Mounting `/code/target` in particular is useful 115 | to avoid docker overwriting your local dev files with root permissions. 116 | Note the `/code/target` cache is unique for each contract being compiled to limit 117 | interference, while the registry cache is global. 118 | 119 | This is rather slow compared to local compilations, especially the first compile 120 | of a given contract. The use of the two volume caches is very useful to speed up 121 | following compiles of the same contract. 122 | 123 | This produces a `contract.wasm` file in the current directory (which must be the root 124 | directory of your rust project, the one with `Cargo.toml` inside). As well as 125 | `hash.txt` containing the Sha256 hash of `contract.wasm`, and it will rebuild 126 | your schema files as well. 127 | 128 | ### Testing production build 129 | 130 | Once we have this compressed `contract.wasm`, we may want to ensure it is actually 131 | doing everything it is supposed to (as it is about 4% of the original size). 132 | If you update the "WASM" line in `tests/integration.rs`, it will run the integration 133 | steps on the optimized build, not just the normal build. I have never seen a different 134 | behavior, but it is nice to verify sometimes. 135 | 136 | ```rust 137 | static WASM: &[u8] = include_bytes!("../contract.wasm"); 138 | ``` 139 | 140 | Note that this is the same (deterministic) code you will be uploading to 141 | a blockchain to test it out, as we need to shrink the size and produce a 142 | clear mapping from wasm hash back to the source code. 143 | -------------------------------------------------------------------------------- /Importing.md: -------------------------------------------------------------------------------- 1 | # Importing 2 | 3 | In [Publishing](./Publishing.md), we discussed how you can publish your contract to the world. 4 | This looks at the flip-side, how can you use someone else's contract (which is the same 5 | question as how they will use your contract). Let's go through the various stages. 6 | 7 | ## Verifying Artifacts 8 | 9 | Before using remote code, you most certainly want to verify it is honest. 10 | 11 | The simplest audit of the repo is to simply check that the artifacts in the repo 12 | are correct. This involves recompiling the claimed source with the claimed builder 13 | and validating that the locally compiled code (hash) matches the code hash that was 14 | uploaded. This will verify that the source code is the correct preimage. Which allows 15 | one to audit the original (Rust) source code, rather than looking at wasm bytecode. 16 | 17 | We have a script to do this automatic verification steps that can 18 | easily be run by many individuals. Please check out 19 | [`cosmwasm-verify`](https://github.com/CosmWasm/cosmwasm-verify/blob/master/README.md) 20 | to see a simple shell script that does all these steps and easily allows you to verify 21 | any uploaded contract. 22 | 23 | ## Reviewing 24 | 25 | Once you have done the quick programatic checks, it is good to give at least a quick 26 | look through the code. A glance at `examples/schema.rs` to make sure it is outputing 27 | all relevant structs from `contract.rs`, and also ensure `src/lib.rs` is just the 28 | default wrapper (nothing funny going on there). After this point, we can dive into 29 | the contract code itself. Check the flows for the handle methods, any invariants and 30 | permission checks that should be there, and a reasonable data storage format. 31 | 32 | You can dig into the contract as far as you want, but it is important to make sure there 33 | are no obvious backdoors at least. 34 | 35 | ## Decentralized Verification 36 | 37 | It's not very practical to do a deep code review on every dependency you want to use, 38 | which is a big reason for the popularity of code audits in the blockchain world. We trust 39 | some experts review in lieu of doing the work ourselves. But wouldn't it be nice to do this 40 | in a decentralized manner and peer-review each other's contracts? Bringing in deeper domain 41 | knowledge and saving fees. 42 | 43 | Luckily, there is an amazing project called [crev](https://github.com/crev-dev/cargo-crev/blob/master/cargo-crev/README.md) 44 | that provides `A cryptographically verifiable code review system for the cargo (Rust) package manager`. 45 | 46 | I highly recommend that CosmWasm contract developers get set up with this. At minimum, we 47 | can all add a review on a package that programmatically checked out that the json schemas 48 | and wasm bytecode do match the code, and publish our claim, so we don't all rely on some 49 | central server to say it validated this. As we go on, we can add deeper reviews on standard 50 | packages. 51 | 52 | If you want to use `cargo-crev`, please follow their 53 | [getting started guide](https://github.com/crev-dev/cargo-crev/blob/master/cargo-crev/src/doc/getting_started.md) 54 | and once you have made your own *proof repository* with at least one *trust proof*, 55 | please make a PR to the [`cawesome-wasm`]() repo with a link to your repo and 56 | some public name or pseudonym that people know you by. This allows people who trust you 57 | to also reuse your proofs. 58 | 59 | There is a [standard list of proof repos](https://github.com/crev-dev/cargo-crev/wiki/List-of-Proof-Repositories) 60 | with some strong rust developers in there. This may cover dependencies like `serde` and `snafu` 61 | but will not hit any CosmWasm-related modules, so we look to bootstrap a very focused 62 | review community. 63 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | 2 | Apache License 3 | Version 2.0, January 2004 4 | http://www.apache.org/licenses/ 5 | 6 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 7 | 8 | 1. Definitions. 9 | 10 | "License" shall mean the terms and conditions for use, reproduction, 11 | and distribution as defined by Sections 1 through 9 of this document. 12 | 13 | "Licensor" shall mean the copyright owner or entity authorized by 14 | the copyright owner that is granting the License. 15 | 16 | "Legal Entity" shall mean the union of the acting entity and all 17 | other entities that control, are controlled by, or are under common 18 | control with that entity. For the purposes of this definition, 19 | "control" means (i) the power, direct or indirect, to cause the 20 | direction or management of such entity, whether by contract or 21 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 22 | outstanding shares, or (iii) beneficial ownership of such entity. 23 | 24 | "You" (or "Your") shall mean an individual or Legal Entity 25 | exercising permissions granted by this License. 26 | 27 | "Source" form shall mean the preferred form for making modifications, 28 | including but not limited to software source code, documentation 29 | source, and configuration files. 30 | 31 | "Object" form shall mean any form resulting from mechanical 32 | transformation or translation of a Source form, including but 33 | not limited to compiled object code, generated documentation, 34 | and conversions to other media types. 35 | 36 | "Work" shall mean the work of authorship, whether in Source or 37 | Object form, made available under the License, as indicated by a 38 | copyright notice that is included in or attached to the work 39 | (an example is provided in the Appendix below). 40 | 41 | "Derivative Works" shall mean any work, whether in Source or Object 42 | form, that is based on (or derived from) the Work and for which the 43 | editorial revisions, annotations, elaborations, or other modifications 44 | represent, as a whole, an original work of authorship. For the purposes 45 | of this License, Derivative Works shall not include works that remain 46 | separable from, or merely link (or bind by name) to the interfaces of, 47 | the Work and Derivative Works thereof. 48 | 49 | "Contribution" shall mean any work of authorship, including 50 | the original version of the Work and any modifications or additions 51 | to that Work or Derivative Works thereof, that is intentionally 52 | submitted to Licensor for inclusion in the Work by the copyright owner 53 | or by an individual or Legal Entity authorized to submit on behalf of 54 | the copyright owner. For the purposes of this definition, "submitted" 55 | means any form of electronic, verbal, or written communication sent 56 | to the Licensor or its representatives, including but not limited to 57 | communication on electronic mailing lists, source code control systems, 58 | and issue tracking systems that are managed by, or on behalf of, the 59 | Licensor for the purpose of discussing and improving the Work, but 60 | excluding communication that is conspicuously marked or otherwise 61 | designated in writing by the copyright owner as "Not a Contribution." 62 | 63 | "Contributor" shall mean Licensor and any individual or Legal Entity 64 | on behalf of whom a Contribution has been received by Licensor and 65 | subsequently incorporated within the Work. 66 | 67 | 2. Grant of Copyright License. Subject to the terms and conditions of 68 | this License, each Contributor hereby grants to You a perpetual, 69 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 70 | copyright license to reproduce, prepare Derivative Works of, 71 | publicly display, publicly perform, sublicense, and distribute the 72 | Work and such Derivative Works in Source or Object form. 73 | 74 | 3. Grant of Patent License. Subject to the terms and conditions of 75 | this License, each Contributor hereby grants to You a perpetual, 76 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 77 | (except as stated in this section) patent license to make, have made, 78 | use, offer to sell, sell, import, and otherwise transfer the Work, 79 | where such license applies only to those patent claims licensable 80 | by such Contributor that are necessarily infringed by their 81 | Contribution(s) alone or by combination of their Contribution(s) 82 | with the Work to which such Contribution(s) was submitted. If You 83 | institute patent litigation against any entity (including a 84 | cross-claim or counterclaim in a lawsuit) alleging that the Work 85 | or a Contribution incorporated within the Work constitutes direct 86 | or contributory patent infringement, then any patent licenses 87 | granted to You under this License for that Work shall terminate 88 | as of the date such litigation is filed. 89 | 90 | 4. Redistribution. You may reproduce and distribute copies of the 91 | Work or Derivative Works thereof in any medium, with or without 92 | modifications, and in Source or Object form, provided that You 93 | meet the following conditions: 94 | 95 | (a) You must give any other recipients of the Work or 96 | Derivative Works a copy of this License; and 97 | 98 | (b) You must cause any modified files to carry prominent notices 99 | stating that You changed the files; and 100 | 101 | (c) You must retain, in the Source form of any Derivative Works 102 | that You distribute, all copyright, patent, trademark, and 103 | attribution notices from the Source form of the Work, 104 | excluding those notices that do not pertain to any part of 105 | the Derivative Works; and 106 | 107 | (d) If the Work includes a "NOTICE" text file as part of its 108 | distribution, then any Derivative Works that You distribute must 109 | include a readable copy of the attribution notices contained 110 | within such NOTICE file, excluding those notices that do not 111 | pertain to any part of the Derivative Works, in at least one 112 | of the following places: within a NOTICE text file distributed 113 | as part of the Derivative Works; within the Source form or 114 | documentation, if provided along with the Derivative Works; or, 115 | within a display generated by the Derivative Works, if and 116 | wherever such third-party notices normally appear. The contents 117 | of the NOTICE file are for informational purposes only and 118 | do not modify the License. You may add Your own attribution 119 | notices within Derivative Works that You distribute, alongside 120 | or as an addendum to the NOTICE text from the Work, provided 121 | that such additional attribution notices cannot be construed 122 | as modifying the License. 123 | 124 | You may add Your own copyright statement to Your modifications and 125 | may provide additional or different license terms and conditions 126 | for use, reproduction, or distribution of Your modifications, or 127 | for any such Derivative Works as a whole, provided Your use, 128 | reproduction, and distribution of the Work otherwise complies with 129 | the conditions stated in this License. 130 | 131 | 5. Submission of Contributions. Unless You explicitly state otherwise, 132 | any Contribution intentionally submitted for inclusion in the Work 133 | by You to the Licensor shall be under the terms and conditions of 134 | this License, without any additional terms or conditions. 135 | Notwithstanding the above, nothing herein shall supersede or modify 136 | the terms of any separate license agreement you may have executed 137 | with Licensor regarding such Contributions. 138 | 139 | 6. Trademarks. This License does not grant permission to use the trade 140 | names, trademarks, service marks, or product names of the Licensor, 141 | except as required for reasonable and customary use in describing the 142 | origin of the Work and reproducing the content of the NOTICE file. 143 | 144 | 7. Disclaimer of Warranty. Unless required by applicable law or 145 | agreed to in writing, Licensor provides the Work (and each 146 | Contributor provides its Contributions) on an "AS IS" BASIS, 147 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 148 | implied, including, without limitation, any warranties or conditions 149 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 150 | PARTICULAR PURPOSE. You are solely responsible for determining the 151 | appropriateness of using or redistributing the Work and assume any 152 | risks associated with Your exercise of permissions under this License. 153 | 154 | 8. Limitation of Liability. In no event and under no legal theory, 155 | whether in tort (including negligence), contract, or otherwise, 156 | unless required by applicable law (such as deliberate and grossly 157 | negligent acts) or agreed to in writing, shall any Contributor be 158 | liable to You for damages, including any direct, indirect, special, 159 | incidental, or consequential damages of any character arising as a 160 | result of this License or out of the use or inability to use the 161 | Work (including but not limited to damages for loss of goodwill, 162 | work stoppage, computer failure or malfunction, or any and all 163 | other commercial damages or losses), even if such Contributor 164 | has been advised of the possibility of such damages. 165 | 166 | 9. Accepting Warranty or Additional Liability. While redistributing 167 | the Work or Derivative Works thereof, You may choose to offer, 168 | and charge a fee for, acceptance of support, warranty, indemnity, 169 | or other liability obligations and/or rights consistent with this 170 | License. However, in accepting such obligations, You may act only 171 | on Your own behalf and on Your sole responsibility, not on behalf 172 | of any other Contributor, and only if You agree to indemnify, 173 | defend, and hold each Contributor harmless for any liability 174 | incurred by, or claims asserted against, such Contributor by reason 175 | of your accepting any such warranty or additional liability. 176 | 177 | END OF TERMS AND CONDITIONS 178 | 179 | APPENDIX: How to apply the Apache License to your work. 180 | 181 | To apply the Apache License to your work, attach the following 182 | boilerplate notice, with the fields enclosed by brackets "[]" 183 | replaced with your own identifying information. (Don't include 184 | the brackets!) The text should be enclosed in the appropriate 185 | comment syntax for the file format. We also recommend that a 186 | file or class name and description of purpose be included on the 187 | same "printed page" as the copyright notice for easier 188 | identification within third-party archives. 189 | 190 | Copyright [yyyy] [name of copyright owner] 191 | 192 | Licensed under the Apache License, Version 2.0 (the "License"); 193 | you may not use this file except in compliance with the License. 194 | You may obtain a copy of the License at 195 | 196 | http://www.apache.org/licenses/LICENSE-2.0 197 | 198 | Unless required by applicable law or agreed to in writing, software 199 | distributed under the License is distributed on an "AS IS" BASIS, 200 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 201 | See the License for the specific language governing permissions and 202 | limitations under the License. 203 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | SECRETCLI = docker exec -it secretdev /usr/bin/secretcli 2 | 3 | .PHONY: all 4 | all: clippy test 5 | 6 | .PHONY: check 7 | check: 8 | cargo check 9 | 10 | .PHONY: check-receiver 11 | check-receiver: 12 | $(MAKE) -C tests/example-receiver check 13 | 14 | .PHONY: clippy 15 | clippy: 16 | cargo clippy 17 | 18 | .PHONY: clippy-receiver 19 | clippy-receiver: 20 | $(MAKE) -C tests/example-receiver clippy 21 | 22 | .PHONY: test 23 | test: unit-test unit-test-receiver integration-test 24 | 25 | .PHONY: unit-test 26 | unit-test: 27 | cargo test 28 | 29 | .PHONY: unit-test-receiver 30 | unit-test-receiver: 31 | $(MAKE) -C tests/example-receiver unit-test 32 | 33 | .PHONY: integration-test 34 | integration-test: compile-optimized compile-optimized-receiver 35 | tests/integration.sh 36 | 37 | compile-optimized-receiver: 38 | $(MAKE) -C tests/example-receiver compile-optimized 39 | 40 | .PHONY: list-code 41 | list-code: 42 | $(SECRETCLI) query compute list-code 43 | 44 | .PHONY: compile _compile 45 | compile: _compile contract.wasm.gz 46 | _compile: 47 | cargo build --target wasm32-unknown-unknown --locked 48 | cp ./target/wasm32-unknown-unknown/debug/*.wasm ./contract.wasm 49 | 50 | .PHONY: compile-optimized _compile-optimized 51 | compile-optimized: _compile-optimized contract.wasm.gz 52 | _compile-optimized: 53 | RUSTFLAGS='-C link-arg=-s' cargo build --release --target wasm32-unknown-unknown --locked 54 | @# The following line is not necessary, may work only on linux (extra size optimization) 55 | wasm-opt -Os ./target/wasm32-unknown-unknown/release/*.wasm -o ./contract.wasm 56 | 57 | .PHONY: compile-optimized-reproducible 58 | compile-optimized-reproducible: 59 | docker run --rm -v "$$(pwd)":/contract \ 60 | --mount type=volume,source="$$(basename "$$(pwd)")_cache",target=/code/target \ 61 | --mount type=volume,source=registry_cache,target=/usr/local/cargo/registry \ 62 | enigmampc/secret-contract-optimizer:1.0.4 63 | 64 | contract.wasm.gz: contract.wasm 65 | cat ./contract.wasm | gzip -9 > ./contract.wasm.gz 66 | 67 | .PHONY: start-server 68 | start-server: # CTRL+C to stop 69 | docker run -it --rm \ 70 | -p 26657:26657 -p 26656:26656 -p 1317:1317 \ 71 | -v $$(pwd):/root/code \ 72 | --name secretdev enigmampc/secret-network-sw-dev:v1.0.2 73 | 74 | .PHONY: schema 75 | schema: 76 | cargo run --example schema 77 | 78 | .PHONY: clean 79 | clean: 80 | cargo clean 81 | rm -f ./contract.wasm ./contract.wasm.gz 82 | $(MAKE) -C tests/example-receiver clean 83 | -------------------------------------------------------------------------------- /NOTICE: -------------------------------------------------------------------------------- 1 | Copyright 2020 Itzik 2 | 3 | Licensed under the Apache License, Version 2.0 (the "License"); 4 | you may not use this file except in compliance with the License. 5 | You may obtain a copy of the License at 6 | 7 | http://www.apache.org/licenses/LICENSE-2.0 8 | 9 | Unless required by applicable law or agreed to in writing, software 10 | distributed under the License is distributed on an "AS IS" BASIS, 11 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | See the License for the specific language governing permissions and 13 | limitations under the License. 14 | -------------------------------------------------------------------------------- /Publishing.md: -------------------------------------------------------------------------------- 1 | # Publishing Contracts 2 | 3 | This is an overview of how to publish the contract's source code in this repo. 4 | We use Cargo's default registry [crates.io](https://crates.io/) for publishing contracts written in Rust. 5 | 6 | ## Preparation 7 | 8 | Ensure the `Cargo.toml` file in the repo is properly configured. In particular, you want to 9 | choose a name starting with `cw-`, which will help a lot finding CosmWasm contracts when 10 | searching on crates.io. For the first publication, you will probably want version `0.1.0`. 11 | If you have tested this on a public net already and/or had an audit on the code, 12 | you can start with `1.0.0`, but that should imply some level of stability and confidence. 13 | You will want entries like the following in `Cargo.toml`: 14 | 15 | ```toml 16 | name = "cw-escrow" 17 | version = "0.1.0" 18 | description = "Simple CosmWasm contract for an escrow with arbiter and timeout" 19 | repository = "https://github.com/confio/cosmwasm-examples" 20 | ``` 21 | 22 | You will also want to add a valid [SPDX license statement](https://spdx.org/licenses/), 23 | so others know the rules for using this crate. You can use any license you wish, 24 | even a commercial license, but we recommend choosing one of the following, unless you have 25 | specific requirements. 26 | 27 | * Permissive: [`Apache-2.0`](https://spdx.org/licenses/Apache-2.0.html#licenseText) or [`MIT`](https://spdx.org/licenses/MIT.html#licenseText) 28 | * Copyleft: [`GPL-3.0-or-later`](https://spdx.org/licenses/GPL-3.0-or-later.html#licenseText) or [`AGPL-3.0-or-later`](https://spdx.org/licenses/AGPL-3.0-or-later.html#licenseText) 29 | * Commercial license: `Commercial` (not sure if this works, I cannot find examples) 30 | 31 | It is also helpful to download the LICENSE text (linked to above) and store this 32 | in a LICENSE file in your repo. Now, you have properly configured your crate for use 33 | in a larger ecosystem. 34 | 35 | ### Updating schema 36 | 37 | To allow easy use of the contract, we can publish the schema (`schema/*.json`) together 38 | with the source code. 39 | 40 | ```sh 41 | cargo schema 42 | ``` 43 | 44 | Ensure you check in all the schema files, and make a git commit with the final state. 45 | This commit will be published and should be tagged. Generally, you will want to 46 | tag with the version (eg. `v0.1.0`), but in the `cosmwasm-examples` repo, we have 47 | multiple contracts and label it like `escrow-0.1.0`. Don't forget a 48 | `git push && git push --tags` 49 | 50 | ### Note on build results 51 | 52 | Build results like Wasm bytecode or expected hash don't need to be updated since 53 | the don't belong to the source publication. However, they are excluded from packaging 54 | in `Cargo.toml` which allows you to commit them to your git repository if you like. 55 | 56 | ```toml 57 | exclude = ["contract.wasm", "hash.txt"] 58 | ``` 59 | 60 | A single source code can be built with multiple different optimizers, so 61 | we should not make any strict assumptions on the tooling that will be used. 62 | 63 | ## Publishing 64 | 65 | Now that your package is properly configured and all artifacts are committed, it 66 | is time to share it with the world. 67 | Please refer to the [complete instructions for any questions](https://rurust.github.io/cargo-docs-ru/crates-io.html), 68 | but I will try to give a quick overview of the happy path here. 69 | 70 | ### Registry 71 | 72 | You will need an account on [crates.io](https://crates.io) to publish a rust crate. 73 | If you don't have one already, just click on "Log in with GitHub" in the top-right 74 | to quickly set up a free account. Once inside, click on your username (top-right), 75 | then "Account Settings". On the bottom, there is a section called "API Access". 76 | If you don't have this set up already, create a new token and use `cargo login` 77 | to set it up. This will now authenticate you with the `cargo` cli tool and allow 78 | you to publish. 79 | 80 | ### Uploading 81 | 82 | Once this is set up, make sure you commit the current state you want to publish. 83 | Then try `cargo publish --dry-run`. If that works well, review the files that 84 | will be published via `cargo package --list`. If you are satisfied, you can now 85 | officially publish it via `cargo publish`. 86 | 87 | Congratulations, your package is public to the world. 88 | 89 | ### Sharing 90 | 91 | Once you have published your package, people can now find it by 92 | [searching for "cw-" on crates.io](https://crates.io/search?q=cw). 93 | But that isn't exactly the simplest way. To make things easier and help 94 | keep the ecosystem together, we suggest making a PR to add your package 95 | to the [`cawesome-wasm`](https://github.com/cosmwasm/cawesome-wasm) list. 96 | 97 | ### Organizations 98 | 99 | Many times you are writing a contract not as a solo developer, but rather as 100 | part of an organization. You will want to allow colleagues to upload new 101 | versions of the contract to crates.io when you are on holiday. 102 | [These instructions show how]() you can set up your crate to allow multiple maintainers. 103 | 104 | You can add another owner to the crate by specifying their github user. Note, you will 105 | now both have complete control of the crate, and they can remove you: 106 | 107 | `cargo owner --add ethanfrey` 108 | 109 | You can also add an existing github team inside your organization: 110 | 111 | `cargo owner --add github:confio:developers` 112 | 113 | The team will allow anyone who is currently in the team to publish new versions of the crate. 114 | And this is automatically updated when you make changes on github. However, it will not allow 115 | anyone in the team to add or remove other owners. 116 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Secret-SCRT - Privacy coin backed by SCRT 2 | 3 | This is a privacy token implementation on the Secret Network. It is backed by 4 | the native coin of the network (SCRT) and has a fixed 1-to-1 exchange ratio 5 | with it. 6 | 7 | Version 1.0.0 of this contract is deployed to mainnet at the address 8 | `secret1k0jntykt7e4g3y88ltc60czgjuqdy4c9e8fzek`. The deployed binary can be 9 | reproduced by checking out the commit tagged `v1.0.0` of this repository and 10 | running the command `make compile-optimized-reproducible`. 11 | See [Verifying build](#verifying-build) for full instructions of how to 12 | verify the authenticity of the deployed binary. 13 | 14 | Usage is pretty simple - you deposit SCRT into the contract, and you get SSCRT 15 | (or Secret-SCRT), which you can then use with the ERC-20-like functionality that 16 | the contract provides including: sending/receiving/allowance and withdrawing 17 | back to SCRT. 18 | 19 | In terms of privacy the deposit & withdrawals are public, as they are 20 | transactions on-chain. The rest of the functionality is private (so no one can 21 | see if you send SSCRT and to whom, and receiving SSCRT is also hidden). 22 | 23 | ## Usage examples: 24 | 25 | Usage examples here assume `v1.0.3` of the CLI is installed. 26 | Users using `v1.0.2` of the CLI can instead send raw compute transactions 27 | and queries based on the schema that the contract expects. 28 | 29 | For full documentation see: 30 | ``` 31 | secretcli tx snip20 --help 32 | secretcli q snip20 --help 33 | ``` 34 | 35 | To deposit: ***(This is public)*** 36 | ``` 37 | secretcli tx snip20 deposit sscrt --amount 1000000uscrt --from 38 | ``` 39 | 40 | To redeem: ***(This is public)*** 41 | ``` 42 | secretcli tx snip20 redeem sscrt --from 43 | ``` 44 | 45 | To send SSCRT: ***(Only you will be able to see the parameters you send here)*** 46 | `amount-to-send` should just be an integer number equal to the amount of 47 | `uscrt` to send. 48 | ``` 49 | secretcli tx snip20 transfer sscrt --from 50 | ``` 51 | 52 | To create your viewing key: 53 | ``` 54 | secretcli tx snip20 create-viewing-key sscrt --from 55 | ``` 56 | This transaction will be expensive, so set your gas limit to about 3M 57 | with `--gas 3000000`. The key will start with the prefix `api_key_....`. 58 | 59 | To check your balance: ***(Only you will be able to see the response)*** 60 | ``` 61 | secretcli q snip20 balance sscrt 62 | ``` 63 | 64 | To view your transaction history: 65 | ``` 66 | secretcli q snip20 history sscrt [optional: page, default: 0] [optional: page_size, default: 10] 67 | ``` 68 | 69 | ## Play with it on testnet 70 | 71 | The deployed SSCRT contract address on the testnet is 72 | `secret1umwqjum7f4zmp9alr2kpmq4y5j4hyxlam896r3` and label `sscrt` 73 | 74 | ## Troubleshooting 75 | 76 | All transactions are encrypted, so if you want to see the error returned by a 77 | failed transaction, you need to use the command 78 | 79 | ``` 80 | secretcli q compute tx 81 | ``` 82 | 83 | ## Notes on SNIP-20 compliance 84 | 85 | The secret-secret contract is fully compatible with the 86 | [SNIP20 specification](https://github.com/SecretFoundation/SNIPs/blob/master/SNIP-20.md), 87 | but it does not implement all the specified functions. Namely, it omits burning 88 | and minting of new coins, and all related functionality. 89 | 90 | This contract maks the following decisions which the specification left open 91 | for specific contracts to make: 92 | 93 | * Messages should be padded to a multiple of 256 bytes by convention to maximize 94 | privacy. 95 | * Addresses of secret-secret accounts are the same as of their respective secret 96 | account. 97 | * The exchange ratio is fixed at 1-to-1. 98 | * The total supply is never reported, but it will always equal the amount of 99 | SCRT locked in the contract, which can be seen in the explorer. 100 | 101 | For more information about the various messages that the contract supports, 102 | you can find all the message types under the file `src/msg.rs`. 103 | 104 | ## Verifying build 105 | 106 | Given the address of a contract, you can query its code hash (sha256) by running: 107 | ``` 108 | secretcli q compute contract-hash 109 | ``` 110 | 111 | You can verify that this hash is correct by comparing it to the decompressed 112 | contract binary. 113 | 114 | To get the contract binary for a specific tag or commit and calculate its hash, 115 | run: 116 | ``` 117 | git checkout 118 | make compile-optimized-reproducible 119 | gunzip -c contract.wasm.gz >contract.wasm 120 | sha256sum contract.wasm 121 | ``` 122 | 123 | Now compare the result with the hash returned by `secretcli`. 124 | If you compiled the same code that was used to build the deployed binary, 125 | they should match :) 126 | -------------------------------------------------------------------------------- /examples/schema.rs: -------------------------------------------------------------------------------- 1 | use std::env::current_dir; 2 | use std::fs::create_dir_all; 3 | 4 | use cosmwasm_schema::{export_schema, remove_schemas, schema_for}; 5 | 6 | use secret_secret::msg::{HandleAnswer, HandleMsg, InitMsg, QueryAnswer, QueryMsg}; 7 | 8 | fn main() { 9 | let mut out_dir = current_dir().unwrap(); 10 | out_dir.push("schema"); 11 | create_dir_all(&out_dir).unwrap(); 12 | remove_schemas(&out_dir).unwrap(); 13 | 14 | export_schema(&schema_for!(InitMsg), &out_dir); 15 | export_schema(&schema_for!(HandleMsg), &out_dir); 16 | export_schema(&schema_for!(HandleAnswer), &out_dir); 17 | export_schema(&schema_for!(QueryMsg), &out_dir); 18 | export_schema(&schema_for!(QueryAnswer), &out_dir); 19 | } 20 | -------------------------------------------------------------------------------- /rustfmt.toml: -------------------------------------------------------------------------------- 1 | # stable 2 | newline_style = "unix" 3 | hard_tabs = false 4 | tab_spaces = 4 5 | 6 | # unstable... should we require `rustup run nightly cargo fmt` ? 7 | # or just update the style guide when they are stable? 8 | #fn_single_line = true 9 | #format_code_in_doc_comments = true 10 | #overflow_delimited_expr = true 11 | #reorder_impl_items = true 12 | #struct_field_align_threshold = 20 13 | #struct_lit_single_line = true 14 | #report_todo = "Always" 15 | 16 | -------------------------------------------------------------------------------- /schema/handle_answer.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "http://json-schema.org/draft-07/schema#", 3 | "title": "HandleAnswer", 4 | "anyOf": [ 5 | { 6 | "type": "object", 7 | "required": [ 8 | "deposit" 9 | ], 10 | "properties": { 11 | "deposit": { 12 | "type": "object", 13 | "required": [ 14 | "status" 15 | ], 16 | "properties": { 17 | "status": { 18 | "$ref": "#/definitions/ResponseStatus" 19 | } 20 | } 21 | } 22 | } 23 | }, 24 | { 25 | "type": "object", 26 | "required": [ 27 | "redeem" 28 | ], 29 | "properties": { 30 | "redeem": { 31 | "type": "object", 32 | "required": [ 33 | "status" 34 | ], 35 | "properties": { 36 | "status": { 37 | "$ref": "#/definitions/ResponseStatus" 38 | } 39 | } 40 | } 41 | } 42 | }, 43 | { 44 | "type": "object", 45 | "required": [ 46 | "balance" 47 | ], 48 | "properties": { 49 | "balance": { 50 | "type": "object", 51 | "required": [ 52 | "amount" 53 | ], 54 | "properties": { 55 | "amount": { 56 | "$ref": "#/definitions/Uint128" 57 | } 58 | } 59 | } 60 | } 61 | }, 62 | { 63 | "type": "object", 64 | "required": [ 65 | "transfer" 66 | ], 67 | "properties": { 68 | "transfer": { 69 | "type": "object", 70 | "required": [ 71 | "status" 72 | ], 73 | "properties": { 74 | "status": { 75 | "$ref": "#/definitions/ResponseStatus" 76 | } 77 | } 78 | } 79 | } 80 | }, 81 | { 82 | "type": "object", 83 | "required": [ 84 | "send" 85 | ], 86 | "properties": { 87 | "send": { 88 | "type": "object", 89 | "required": [ 90 | "status" 91 | ], 92 | "properties": { 93 | "status": { 94 | "$ref": "#/definitions/ResponseStatus" 95 | } 96 | } 97 | } 98 | } 99 | }, 100 | { 101 | "type": "object", 102 | "required": [ 103 | "burn" 104 | ], 105 | "properties": { 106 | "burn": { 107 | "type": "object", 108 | "required": [ 109 | "status" 110 | ], 111 | "properties": { 112 | "status": { 113 | "$ref": "#/definitions/ResponseStatus" 114 | } 115 | } 116 | } 117 | } 118 | }, 119 | { 120 | "type": "object", 121 | "required": [ 122 | "register_receive" 123 | ], 124 | "properties": { 125 | "register_receive": { 126 | "type": "object", 127 | "required": [ 128 | "status" 129 | ], 130 | "properties": { 131 | "status": { 132 | "$ref": "#/definitions/ResponseStatus" 133 | } 134 | } 135 | } 136 | } 137 | }, 138 | { 139 | "type": "object", 140 | "required": [ 141 | "create_viewing_key" 142 | ], 143 | "properties": { 144 | "create_viewing_key": { 145 | "type": "object", 146 | "required": [ 147 | "key" 148 | ], 149 | "properties": { 150 | "key": { 151 | "$ref": "#/definitions/ViewingKey" 152 | } 153 | } 154 | } 155 | } 156 | }, 157 | { 158 | "type": "object", 159 | "required": [ 160 | "set_viewing_key" 161 | ], 162 | "properties": { 163 | "set_viewing_key": { 164 | "type": "object", 165 | "required": [ 166 | "status" 167 | ], 168 | "properties": { 169 | "status": { 170 | "$ref": "#/definitions/ResponseStatus" 171 | } 172 | } 173 | } 174 | } 175 | }, 176 | { 177 | "type": "object", 178 | "required": [ 179 | "increase_allowance" 180 | ], 181 | "properties": { 182 | "increase_allowance": { 183 | "type": "object", 184 | "required": [ 185 | "allowance", 186 | "owner", 187 | "spender" 188 | ], 189 | "properties": { 190 | "allowance": { 191 | "$ref": "#/definitions/Uint128" 192 | }, 193 | "owner": { 194 | "$ref": "#/definitions/HumanAddr" 195 | }, 196 | "spender": { 197 | "$ref": "#/definitions/HumanAddr" 198 | } 199 | } 200 | } 201 | } 202 | }, 203 | { 204 | "type": "object", 205 | "required": [ 206 | "decrease_allowance" 207 | ], 208 | "properties": { 209 | "decrease_allowance": { 210 | "type": "object", 211 | "required": [ 212 | "allowance", 213 | "owner", 214 | "spender" 215 | ], 216 | "properties": { 217 | "allowance": { 218 | "$ref": "#/definitions/Uint128" 219 | }, 220 | "owner": { 221 | "$ref": "#/definitions/HumanAddr" 222 | }, 223 | "spender": { 224 | "$ref": "#/definitions/HumanAddr" 225 | } 226 | } 227 | } 228 | } 229 | }, 230 | { 231 | "type": "object", 232 | "required": [ 233 | "transfer_from" 234 | ], 235 | "properties": { 236 | "transfer_from": { 237 | "type": "object", 238 | "required": [ 239 | "status" 240 | ], 241 | "properties": { 242 | "status": { 243 | "$ref": "#/definitions/ResponseStatus" 244 | } 245 | } 246 | } 247 | } 248 | }, 249 | { 250 | "type": "object", 251 | "required": [ 252 | "send_from" 253 | ], 254 | "properties": { 255 | "send_from": { 256 | "type": "object", 257 | "required": [ 258 | "status" 259 | ], 260 | "properties": { 261 | "status": { 262 | "$ref": "#/definitions/ResponseStatus" 263 | } 264 | } 265 | } 266 | } 267 | }, 268 | { 269 | "type": "object", 270 | "required": [ 271 | "burn_from" 272 | ], 273 | "properties": { 274 | "burn_from": { 275 | "type": "object", 276 | "required": [ 277 | "status" 278 | ], 279 | "properties": { 280 | "status": { 281 | "$ref": "#/definitions/ResponseStatus" 282 | } 283 | } 284 | } 285 | } 286 | }, 287 | { 288 | "type": "object", 289 | "required": [ 290 | "mint" 291 | ], 292 | "properties": { 293 | "mint": { 294 | "type": "object", 295 | "required": [ 296 | "status" 297 | ], 298 | "properties": { 299 | "status": { 300 | "$ref": "#/definitions/ResponseStatus" 301 | } 302 | } 303 | } 304 | } 305 | }, 306 | { 307 | "type": "object", 308 | "required": [ 309 | "swap" 310 | ], 311 | "properties": { 312 | "swap": { 313 | "type": "object", 314 | "required": [ 315 | "nonce", 316 | "status" 317 | ], 318 | "properties": { 319 | "nonce": { 320 | "type": "integer", 321 | "format": "uint32", 322 | "minimum": 0.0 323 | }, 324 | "status": { 325 | "$ref": "#/definitions/ResponseStatus" 326 | } 327 | } 328 | } 329 | } 330 | }, 331 | { 332 | "type": "object", 333 | "required": [ 334 | "change_admin" 335 | ], 336 | "properties": { 337 | "change_admin": { 338 | "type": "object", 339 | "required": [ 340 | "status" 341 | ], 342 | "properties": { 343 | "status": { 344 | "$ref": "#/definitions/ResponseStatus" 345 | } 346 | } 347 | } 348 | } 349 | } 350 | ], 351 | "definitions": { 352 | "HumanAddr": { 353 | "type": "string" 354 | }, 355 | "ResponseStatus": { 356 | "type": "string", 357 | "enum": [ 358 | "success", 359 | "failure" 360 | ] 361 | }, 362 | "Uint128": { 363 | "type": "string" 364 | }, 365 | "ViewingKey": { 366 | "type": "string" 367 | } 368 | } 369 | } 370 | -------------------------------------------------------------------------------- /schema/handle_msg.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "http://json-schema.org/draft-07/schema#", 3 | "title": "HandleMsg", 4 | "anyOf": [ 5 | { 6 | "type": "object", 7 | "required": [ 8 | "redeem" 9 | ], 10 | "properties": { 11 | "redeem": { 12 | "type": "object", 13 | "required": [ 14 | "amount" 15 | ], 16 | "properties": { 17 | "amount": { 18 | "$ref": "#/definitions/Uint128" 19 | }, 20 | "padding": { 21 | "type": [ 22 | "string", 23 | "null" 24 | ] 25 | } 26 | } 27 | } 28 | } 29 | }, 30 | { 31 | "type": "object", 32 | "required": [ 33 | "deposit" 34 | ], 35 | "properties": { 36 | "deposit": { 37 | "type": "object", 38 | "properties": { 39 | "padding": { 40 | "type": [ 41 | "string", 42 | "null" 43 | ] 44 | } 45 | } 46 | } 47 | } 48 | }, 49 | { 50 | "type": "object", 51 | "required": [ 52 | "balance" 53 | ], 54 | "properties": { 55 | "balance": { 56 | "type": "object", 57 | "properties": { 58 | "padding": { 59 | "type": [ 60 | "string", 61 | "null" 62 | ] 63 | } 64 | } 65 | } 66 | } 67 | }, 68 | { 69 | "type": "object", 70 | "required": [ 71 | "transfer" 72 | ], 73 | "properties": { 74 | "transfer": { 75 | "type": "object", 76 | "required": [ 77 | "amount", 78 | "recipient" 79 | ], 80 | "properties": { 81 | "amount": { 82 | "$ref": "#/definitions/Uint128" 83 | }, 84 | "padding": { 85 | "type": [ 86 | "string", 87 | "null" 88 | ] 89 | }, 90 | "recipient": { 91 | "$ref": "#/definitions/HumanAddr" 92 | } 93 | } 94 | } 95 | } 96 | }, 97 | { 98 | "type": "object", 99 | "required": [ 100 | "send" 101 | ], 102 | "properties": { 103 | "send": { 104 | "type": "object", 105 | "required": [ 106 | "amount", 107 | "recipient" 108 | ], 109 | "properties": { 110 | "amount": { 111 | "$ref": "#/definitions/Uint128" 112 | }, 113 | "msg": { 114 | "anyOf": [ 115 | { 116 | "$ref": "#/definitions/Binary" 117 | }, 118 | { 119 | "type": "null" 120 | } 121 | ] 122 | }, 123 | "padding": { 124 | "type": [ 125 | "string", 126 | "null" 127 | ] 128 | }, 129 | "recipient": { 130 | "$ref": "#/definitions/HumanAddr" 131 | } 132 | } 133 | } 134 | } 135 | }, 136 | { 137 | "type": "object", 138 | "required": [ 139 | "burn" 140 | ], 141 | "properties": { 142 | "burn": { 143 | "type": "object", 144 | "required": [ 145 | "amount" 146 | ], 147 | "properties": { 148 | "amount": { 149 | "$ref": "#/definitions/Uint128" 150 | }, 151 | "padding": { 152 | "type": [ 153 | "string", 154 | "null" 155 | ] 156 | } 157 | } 158 | } 159 | } 160 | }, 161 | { 162 | "type": "object", 163 | "required": [ 164 | "register_receive" 165 | ], 166 | "properties": { 167 | "register_receive": { 168 | "type": "object", 169 | "required": [ 170 | "code_hash" 171 | ], 172 | "properties": { 173 | "code_hash": { 174 | "type": "string" 175 | }, 176 | "padding": { 177 | "type": [ 178 | "string", 179 | "null" 180 | ] 181 | } 182 | } 183 | } 184 | } 185 | }, 186 | { 187 | "type": "object", 188 | "required": [ 189 | "create_viewing_key" 190 | ], 191 | "properties": { 192 | "create_viewing_key": { 193 | "type": "object", 194 | "required": [ 195 | "entropy" 196 | ], 197 | "properties": { 198 | "entropy": { 199 | "type": "string" 200 | }, 201 | "padding": { 202 | "type": [ 203 | "string", 204 | "null" 205 | ] 206 | } 207 | } 208 | } 209 | } 210 | }, 211 | { 212 | "type": "object", 213 | "required": [ 214 | "set_viewing_key" 215 | ], 216 | "properties": { 217 | "set_viewing_key": { 218 | "type": "object", 219 | "required": [ 220 | "key" 221 | ], 222 | "properties": { 223 | "key": { 224 | "type": "string" 225 | }, 226 | "padding": { 227 | "type": [ 228 | "string", 229 | "null" 230 | ] 231 | } 232 | } 233 | } 234 | } 235 | }, 236 | { 237 | "type": "object", 238 | "required": [ 239 | "increase_allowance" 240 | ], 241 | "properties": { 242 | "increase_allowance": { 243 | "type": "object", 244 | "required": [ 245 | "amount", 246 | "spender" 247 | ], 248 | "properties": { 249 | "amount": { 250 | "$ref": "#/definitions/Uint128" 251 | }, 252 | "expiration": { 253 | "type": [ 254 | "integer", 255 | "null" 256 | ], 257 | "format": "uint64", 258 | "minimum": 0.0 259 | }, 260 | "padding": { 261 | "type": [ 262 | "string", 263 | "null" 264 | ] 265 | }, 266 | "spender": { 267 | "$ref": "#/definitions/HumanAddr" 268 | } 269 | } 270 | } 271 | } 272 | }, 273 | { 274 | "type": "object", 275 | "required": [ 276 | "decrease_allowance" 277 | ], 278 | "properties": { 279 | "decrease_allowance": { 280 | "type": "object", 281 | "required": [ 282 | "amount", 283 | "spender" 284 | ], 285 | "properties": { 286 | "amount": { 287 | "$ref": "#/definitions/Uint128" 288 | }, 289 | "expiration": { 290 | "type": [ 291 | "integer", 292 | "null" 293 | ], 294 | "format": "uint64", 295 | "minimum": 0.0 296 | }, 297 | "padding": { 298 | "type": [ 299 | "string", 300 | "null" 301 | ] 302 | }, 303 | "spender": { 304 | "$ref": "#/definitions/HumanAddr" 305 | } 306 | } 307 | } 308 | } 309 | }, 310 | { 311 | "type": "object", 312 | "required": [ 313 | "transfer_from" 314 | ], 315 | "properties": { 316 | "transfer_from": { 317 | "type": "object", 318 | "required": [ 319 | "amount", 320 | "owner", 321 | "recipient" 322 | ], 323 | "properties": { 324 | "amount": { 325 | "$ref": "#/definitions/Uint128" 326 | }, 327 | "owner": { 328 | "$ref": "#/definitions/HumanAddr" 329 | }, 330 | "padding": { 331 | "type": [ 332 | "string", 333 | "null" 334 | ] 335 | }, 336 | "recipient": { 337 | "$ref": "#/definitions/HumanAddr" 338 | } 339 | } 340 | } 341 | } 342 | }, 343 | { 344 | "type": "object", 345 | "required": [ 346 | "send_from" 347 | ], 348 | "properties": { 349 | "send_from": { 350 | "type": "object", 351 | "required": [ 352 | "amount", 353 | "owner", 354 | "recipient" 355 | ], 356 | "properties": { 357 | "amount": { 358 | "$ref": "#/definitions/Uint128" 359 | }, 360 | "msg": { 361 | "anyOf": [ 362 | { 363 | "$ref": "#/definitions/Binary" 364 | }, 365 | { 366 | "type": "null" 367 | } 368 | ] 369 | }, 370 | "owner": { 371 | "$ref": "#/definitions/HumanAddr" 372 | }, 373 | "padding": { 374 | "type": [ 375 | "string", 376 | "null" 377 | ] 378 | }, 379 | "recipient": { 380 | "$ref": "#/definitions/HumanAddr" 381 | } 382 | } 383 | } 384 | } 385 | }, 386 | { 387 | "type": "object", 388 | "required": [ 389 | "burn_from" 390 | ], 391 | "properties": { 392 | "burn_from": { 393 | "type": "object", 394 | "required": [ 395 | "amount", 396 | "owner" 397 | ], 398 | "properties": { 399 | "amount": { 400 | "$ref": "#/definitions/Uint128" 401 | }, 402 | "owner": { 403 | "$ref": "#/definitions/HumanAddr" 404 | }, 405 | "padding": { 406 | "type": [ 407 | "string", 408 | "null" 409 | ] 410 | } 411 | } 412 | } 413 | } 414 | }, 415 | { 416 | "type": "object", 417 | "required": [ 418 | "mint" 419 | ], 420 | "properties": { 421 | "mint": { 422 | "type": "object", 423 | "required": [ 424 | "address", 425 | "amount" 426 | ], 427 | "properties": { 428 | "address": { 429 | "$ref": "#/definitions/HumanAddr" 430 | }, 431 | "amount": { 432 | "$ref": "#/definitions/Uint128" 433 | } 434 | } 435 | } 436 | } 437 | }, 438 | { 439 | "type": "object", 440 | "required": [ 441 | "swap" 442 | ], 443 | "properties": { 444 | "swap": { 445 | "type": "object", 446 | "required": [ 447 | "amount", 448 | "destination", 449 | "network" 450 | ], 451 | "properties": { 452 | "amount": { 453 | "$ref": "#/definitions/Uint128" 454 | }, 455 | "destination": { 456 | "type": "string" 457 | }, 458 | "network": { 459 | "type": "string" 460 | }, 461 | "padding": { 462 | "type": [ 463 | "string", 464 | "null" 465 | ] 466 | } 467 | } 468 | } 469 | } 470 | }, 471 | { 472 | "type": "object", 473 | "required": [ 474 | "change_admin" 475 | ], 476 | "properties": { 477 | "change_admin": { 478 | "type": "object", 479 | "required": [ 480 | "address" 481 | ], 482 | "properties": { 483 | "address": { 484 | "$ref": "#/definitions/HumanAddr" 485 | } 486 | } 487 | } 488 | } 489 | } 490 | ], 491 | "definitions": { 492 | "Binary": { 493 | "description": "Binary is a wrapper around Vec to add base64 de/serialization with serde. It also adds some helper methods to help encode inline.\n\nThis is only needed as serde-json-{core,wasm} has a horrible encoding for Vec", 494 | "type": "string" 495 | }, 496 | "HumanAddr": { 497 | "type": "string" 498 | }, 499 | "Uint128": { 500 | "type": "string" 501 | } 502 | } 503 | } 504 | -------------------------------------------------------------------------------- /schema/init_msg.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "http://json-schema.org/draft-07/schema#", 3 | "title": "InitMsg", 4 | "type": "object", 5 | "required": [ 6 | "admin", 7 | "config", 8 | "decimals", 9 | "initial_balances", 10 | "name", 11 | "symbol" 12 | ], 13 | "properties": { 14 | "admin": { 15 | "$ref": "#/definitions/HumanAddr" 16 | }, 17 | "config": { 18 | "$ref": "#/definitions/InitConfig" 19 | }, 20 | "decimals": { 21 | "type": "integer", 22 | "format": "uint8", 23 | "minimum": 0.0 24 | }, 25 | "initial_balances": { 26 | "type": "array", 27 | "items": { 28 | "$ref": "#/definitions/InitialBalance" 29 | } 30 | }, 31 | "name": { 32 | "type": "string" 33 | }, 34 | "symbol": { 35 | "type": "string" 36 | } 37 | }, 38 | "definitions": { 39 | "HumanAddr": { 40 | "type": "string" 41 | }, 42 | "InitConfig": { 43 | "description": "This type represents optional configuration values which can be overridden. All values are optional and have defaults which are more private by default, but can be overridden if necessary", 44 | "type": "object", 45 | "properties": { 46 | "public_total_supply": { 47 | "description": "Indicates whether the total supply is public or should be kept secret. default: False", 48 | "type": [ 49 | "boolean", 50 | "null" 51 | ] 52 | } 53 | } 54 | }, 55 | "InitialBalance": { 56 | "type": "object", 57 | "required": [ 58 | "address", 59 | "amount" 60 | ], 61 | "properties": { 62 | "address": { 63 | "$ref": "#/definitions/HumanAddr" 64 | }, 65 | "amount": { 66 | "$ref": "#/definitions/Uint128" 67 | } 68 | } 69 | }, 70 | "Uint128": { 71 | "type": "string" 72 | } 73 | } 74 | } 75 | -------------------------------------------------------------------------------- /schema/query_answer.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "http://json-schema.org/draft-07/schema#", 3 | "title": "QueryAnswer", 4 | "anyOf": [ 5 | { 6 | "type": "object", 7 | "required": [ 8 | "token_info" 9 | ], 10 | "properties": { 11 | "token_info": { 12 | "type": "object", 13 | "required": [ 14 | "decimals", 15 | "name", 16 | "symbol" 17 | ], 18 | "properties": { 19 | "decimals": { 20 | "type": "integer", 21 | "format": "uint8", 22 | "minimum": 0.0 23 | }, 24 | "name": { 25 | "type": "string" 26 | }, 27 | "symbol": { 28 | "type": "string" 29 | }, 30 | "total_supply": { 31 | "type": [ 32 | "integer", 33 | "null" 34 | ], 35 | "format": "uint128", 36 | "minimum": 0.0 37 | } 38 | } 39 | } 40 | } 41 | }, 42 | { 43 | "type": "object", 44 | "required": [ 45 | "exchange_rate" 46 | ], 47 | "properties": { 48 | "exchange_rate": { 49 | "type": "object", 50 | "required": [ 51 | "denom", 52 | "rate" 53 | ], 54 | "properties": { 55 | "denom": { 56 | "type": "string" 57 | }, 58 | "rate": { 59 | "$ref": "#/definitions/Uint128" 60 | } 61 | } 62 | } 63 | } 64 | }, 65 | { 66 | "type": "object", 67 | "required": [ 68 | "swap" 69 | ], 70 | "properties": { 71 | "swap": { 72 | "type": "object", 73 | "required": [ 74 | "result" 75 | ], 76 | "properties": { 77 | "result": { 78 | "$ref": "#/definitions/Swap" 79 | } 80 | } 81 | } 82 | } 83 | }, 84 | { 85 | "type": "object", 86 | "required": [ 87 | "allowance" 88 | ], 89 | "properties": { 90 | "allowance": { 91 | "type": "object", 92 | "required": [ 93 | "allowance", 94 | "owner", 95 | "spender" 96 | ], 97 | "properties": { 98 | "allowance": { 99 | "$ref": "#/definitions/Uint128" 100 | }, 101 | "expiration": { 102 | "type": [ 103 | "integer", 104 | "null" 105 | ], 106 | "format": "uint64", 107 | "minimum": 0.0 108 | }, 109 | "owner": { 110 | "$ref": "#/definitions/HumanAddr" 111 | }, 112 | "spender": { 113 | "$ref": "#/definitions/HumanAddr" 114 | } 115 | } 116 | } 117 | } 118 | }, 119 | { 120 | "type": "object", 121 | "required": [ 122 | "balance" 123 | ], 124 | "properties": { 125 | "balance": { 126 | "type": "object", 127 | "required": [ 128 | "amount" 129 | ], 130 | "properties": { 131 | "amount": { 132 | "$ref": "#/definitions/Uint128" 133 | } 134 | } 135 | } 136 | } 137 | }, 138 | { 139 | "type": "object", 140 | "required": [ 141 | "transfer_history" 142 | ], 143 | "properties": { 144 | "transfer_history": { 145 | "type": "object", 146 | "required": [ 147 | "txs" 148 | ], 149 | "properties": { 150 | "txs": { 151 | "type": "array", 152 | "items": { 153 | "$ref": "#/definitions/Tx" 154 | } 155 | } 156 | } 157 | } 158 | } 159 | }, 160 | { 161 | "type": "object", 162 | "required": [ 163 | "viewing_key_error" 164 | ], 165 | "properties": { 166 | "viewing_key_error": { 167 | "type": "object", 168 | "required": [ 169 | "msg" 170 | ], 171 | "properties": { 172 | "msg": { 173 | "type": "string" 174 | } 175 | } 176 | } 177 | } 178 | } 179 | ], 180 | "definitions": { 181 | "Coin": { 182 | "type": "object", 183 | "required": [ 184 | "amount", 185 | "denom" 186 | ], 187 | "properties": { 188 | "amount": { 189 | "$ref": "#/definitions/Uint128" 190 | }, 191 | "denom": { 192 | "type": "string" 193 | } 194 | } 195 | }, 196 | "HumanAddr": { 197 | "type": "string" 198 | }, 199 | "Swap": { 200 | "type": "object", 201 | "required": [ 202 | "amount", 203 | "destination", 204 | "nonce" 205 | ], 206 | "properties": { 207 | "amount": { 208 | "$ref": "#/definitions/Uint128" 209 | }, 210 | "destination": { 211 | "type": "string" 212 | }, 213 | "nonce": { 214 | "type": "integer", 215 | "format": "uint32", 216 | "minimum": 0.0 217 | } 218 | } 219 | }, 220 | "Tx": { 221 | "type": "object", 222 | "required": [ 223 | "coins", 224 | "receiver", 225 | "sender" 226 | ], 227 | "properties": { 228 | "coins": { 229 | "$ref": "#/definitions/Coin" 230 | }, 231 | "receiver": { 232 | "$ref": "#/definitions/HumanAddr" 233 | }, 234 | "sender": { 235 | "$ref": "#/definitions/HumanAddr" 236 | } 237 | } 238 | }, 239 | "Uint128": { 240 | "type": "string" 241 | } 242 | } 243 | } 244 | -------------------------------------------------------------------------------- /schema/query_msg.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "http://json-schema.org/draft-07/schema#", 3 | "title": "QueryMsg", 4 | "anyOf": [ 5 | { 6 | "type": "object", 7 | "required": [ 8 | "token_info" 9 | ], 10 | "properties": { 11 | "token_info": { 12 | "type": "object" 13 | } 14 | } 15 | }, 16 | { 17 | "type": "object", 18 | "required": [ 19 | "exchange_rate" 20 | ], 21 | "properties": { 22 | "exchange_rate": { 23 | "type": "object" 24 | } 25 | } 26 | }, 27 | { 28 | "type": "object", 29 | "required": [ 30 | "swap" 31 | ], 32 | "properties": { 33 | "swap": { 34 | "type": "object", 35 | "required": [ 36 | "nonce" 37 | ], 38 | "properties": { 39 | "nonce": { 40 | "type": "integer", 41 | "format": "uint32", 42 | "minimum": 0.0 43 | } 44 | } 45 | } 46 | } 47 | }, 48 | { 49 | "type": "object", 50 | "required": [ 51 | "allowance" 52 | ], 53 | "properties": { 54 | "allowance": { 55 | "type": "object", 56 | "required": [ 57 | "owner", 58 | "spender" 59 | ], 60 | "properties": { 61 | "owner": { 62 | "$ref": "#/definitions/HumanAddr" 63 | }, 64 | "padding": { 65 | "type": [ 66 | "string", 67 | "null" 68 | ] 69 | }, 70 | "spender": { 71 | "$ref": "#/definitions/HumanAddr" 72 | } 73 | } 74 | } 75 | } 76 | }, 77 | { 78 | "type": "object", 79 | "required": [ 80 | "balance" 81 | ], 82 | "properties": { 83 | "balance": { 84 | "type": "object", 85 | "required": [ 86 | "address", 87 | "key" 88 | ], 89 | "properties": { 90 | "address": { 91 | "$ref": "#/definitions/HumanAddr" 92 | }, 93 | "key": { 94 | "type": "string" 95 | } 96 | } 97 | } 98 | } 99 | }, 100 | { 101 | "type": "object", 102 | "required": [ 103 | "transfer_history" 104 | ], 105 | "properties": { 106 | "transfer_history": { 107 | "type": "object", 108 | "required": [ 109 | "address", 110 | "key", 111 | "page_size" 112 | ], 113 | "properties": { 114 | "address": { 115 | "$ref": "#/definitions/HumanAddr" 116 | }, 117 | "key": { 118 | "type": "string" 119 | }, 120 | "page": { 121 | "type": [ 122 | "integer", 123 | "null" 124 | ], 125 | "format": "uint32", 126 | "minimum": 0.0 127 | }, 128 | "page_size": { 129 | "type": "integer", 130 | "format": "uint32", 131 | "minimum": 0.0 132 | } 133 | } 134 | } 135 | } 136 | }, 137 | { 138 | "type": "object", 139 | "required": [ 140 | "test" 141 | ], 142 | "properties": { 143 | "test": { 144 | "type": "object" 145 | } 146 | } 147 | } 148 | ], 149 | "definitions": { 150 | "HumanAddr": { 151 | "type": "string" 152 | } 153 | } 154 | } 155 | -------------------------------------------------------------------------------- /src/lib.rs: -------------------------------------------------------------------------------- 1 | pub mod contract; 2 | pub mod msg; 3 | pub mod receiver; 4 | pub mod state; 5 | mod utils; 6 | mod viewing_key; 7 | 8 | #[cfg(target_arch = "wasm32")] 9 | mod wasm { 10 | use super::contract; 11 | use cosmwasm_std::{ 12 | do_handle, do_init, do_query, ExternalApi, ExternalQuerier, ExternalStorage, 13 | }; 14 | 15 | #[no_mangle] 16 | extern "C" fn init(env_ptr: u32, msg_ptr: u32) -> u32 { 17 | do_init( 18 | &contract::init::, 19 | env_ptr, 20 | msg_ptr, 21 | ) 22 | } 23 | 24 | #[no_mangle] 25 | extern "C" fn handle(env_ptr: u32, msg_ptr: u32) -> u32 { 26 | do_handle( 27 | &contract::handle::, 28 | env_ptr, 29 | msg_ptr, 30 | ) 31 | } 32 | 33 | #[no_mangle] 34 | extern "C" fn query(msg_ptr: u32) -> u32 { 35 | do_query( 36 | &contract::query::, 37 | msg_ptr, 38 | ) 39 | } 40 | 41 | // Other C externs like cosmwasm_vm_version_1, allocate, deallocate are available 42 | // automatically because we `use cosmwasm_std`. 43 | } 44 | -------------------------------------------------------------------------------- /src/msg.rs: -------------------------------------------------------------------------------- 1 | use schemars::JsonSchema; 2 | use serde::{Deserialize, Serialize}; 3 | 4 | use cosmwasm_std::{Binary, HumanAddr, StdError, StdResult, Uint128}; 5 | 6 | use crate::state::Tx; 7 | use crate::viewing_key::ViewingKey; 8 | 9 | #[derive(Serialize, Deserialize, Clone, PartialEq, JsonSchema)] 10 | pub struct InitialBalance { 11 | pub address: HumanAddr, 12 | pub amount: Uint128, 13 | } 14 | 15 | #[derive(Serialize, Deserialize, JsonSchema)] 16 | pub struct InitMsg { 17 | pub name: String, 18 | pub admin: Option, 19 | pub symbol: String, 20 | pub decimals: u8, 21 | pub initial_balances: Option>, 22 | pub prng_seed: Binary, 23 | pub config: Option, 24 | } 25 | 26 | impl InitMsg { 27 | pub fn config(&self) -> InitConfig { 28 | self.config.clone().unwrap_or_default() 29 | } 30 | } 31 | 32 | /// This type represents optional configuration values which can be overridden. 33 | /// All values are optional and have defaults which are more private by default, 34 | /// but can be overridden if necessary 35 | #[derive(Serialize, Deserialize, JsonSchema, Clone, Default, Debug)] 36 | #[serde(rename_all = "snake_case")] 37 | pub struct InitConfig { 38 | /// Indicates whether the total supply is public or should be kept secret. 39 | /// default: False 40 | public_total_supply: Option, 41 | } 42 | 43 | impl InitConfig { 44 | pub fn public_total_supply(&self) -> bool { 45 | self.public_total_supply.unwrap_or(false) 46 | } 47 | } 48 | 49 | #[derive(Serialize, Deserialize, JsonSchema)] 50 | #[serde(rename_all = "snake_case")] 51 | pub enum HandleMsg { 52 | // Native coin interactions 53 | Redeem { 54 | amount: Uint128, 55 | denom: Option, 56 | padding: Option, 57 | }, 58 | Deposit { 59 | padding: Option, 60 | }, 61 | 62 | // Base ERC-20 stuff 63 | Transfer { 64 | recipient: HumanAddr, 65 | amount: Uint128, 66 | padding: Option, 67 | }, 68 | Send { 69 | recipient: HumanAddr, 70 | amount: Uint128, 71 | msg: Option, 72 | padding: Option, 73 | }, 74 | RegisterReceive { 75 | code_hash: String, 76 | padding: Option, 77 | }, 78 | CreateViewingKey { 79 | entropy: String, 80 | padding: Option, 81 | }, 82 | SetViewingKey { 83 | key: String, 84 | padding: Option, 85 | }, 86 | 87 | // Allowance 88 | IncreaseAllowance { 89 | spender: HumanAddr, 90 | amount: Uint128, 91 | expiration: Option, 92 | padding: Option, 93 | }, 94 | DecreaseAllowance { 95 | spender: HumanAddr, 96 | amount: Uint128, 97 | expiration: Option, 98 | padding: Option, 99 | }, 100 | TransferFrom { 101 | owner: HumanAddr, 102 | recipient: HumanAddr, 103 | amount: Uint128, 104 | padding: Option, 105 | }, 106 | SendFrom { 107 | owner: HumanAddr, 108 | recipient: HumanAddr, 109 | amount: Uint128, 110 | msg: Option, 111 | padding: Option, 112 | }, 113 | 114 | // Admin 115 | ChangeAdmin { 116 | address: HumanAddr, 117 | padding: Option, 118 | }, 119 | SetContractStatus { 120 | level: ContractStatusLevel, 121 | padding: Option, 122 | }, 123 | } 124 | 125 | #[derive(Serialize, Deserialize, JsonSchema, Debug)] 126 | #[serde(rename_all = "snake_case")] 127 | pub enum HandleAnswer { 128 | // Native 129 | Deposit { 130 | status: ResponseStatus, 131 | }, 132 | Redeem { 133 | status: ResponseStatus, 134 | }, 135 | 136 | // Base 137 | Transfer { 138 | status: ResponseStatus, 139 | }, 140 | Send { 141 | status: ResponseStatus, 142 | }, 143 | RegisterReceive { 144 | status: ResponseStatus, 145 | }, 146 | CreateViewingKey { 147 | key: ViewingKey, 148 | }, 149 | SetViewingKey { 150 | status: ResponseStatus, 151 | }, 152 | 153 | // Allowance 154 | IncreaseAllowance { 155 | spender: HumanAddr, 156 | owner: HumanAddr, 157 | allowance: Uint128, 158 | }, 159 | DecreaseAllowance { 160 | spender: HumanAddr, 161 | owner: HumanAddr, 162 | allowance: Uint128, 163 | }, 164 | TransferFrom { 165 | status: ResponseStatus, 166 | }, 167 | SendFrom { 168 | status: ResponseStatus, 169 | }, 170 | 171 | // Other 172 | ChangeAdmin { 173 | status: ResponseStatus, 174 | }, 175 | SetContractStatus { 176 | status: ResponseStatus, 177 | }, 178 | } 179 | 180 | #[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)] 181 | #[serde(rename_all = "snake_case")] 182 | pub enum QueryMsg { 183 | TokenInfo {}, 184 | ExchangeRate {}, 185 | Allowance { 186 | owner: HumanAddr, 187 | spender: HumanAddr, 188 | key: String, 189 | }, 190 | Balance { 191 | address: HumanAddr, 192 | key: String, 193 | }, 194 | TransferHistory { 195 | address: HumanAddr, 196 | key: String, 197 | page: Option, 198 | page_size: u32, 199 | }, 200 | } 201 | 202 | impl QueryMsg { 203 | pub fn get_validation_params(&self) -> (Vec<&HumanAddr>, ViewingKey) { 204 | match self { 205 | Self::Balance { address, key } => (vec![address], ViewingKey(key.clone())), 206 | Self::TransferHistory { address, key, .. } => (vec![address], ViewingKey(key.clone())), 207 | Self::Allowance { 208 | owner, 209 | spender, 210 | key, 211 | .. 212 | } => (vec![owner, spender], ViewingKey(key.clone())), 213 | _ => panic!("This query type does not require authentication"), 214 | } 215 | } 216 | } 217 | 218 | #[derive(Serialize, Deserialize, JsonSchema, Debug)] 219 | #[serde(rename_all = "snake_case")] 220 | pub enum QueryAnswer { 221 | TokenInfo { 222 | name: String, 223 | symbol: String, 224 | decimals: u8, 225 | total_supply: Option, 226 | }, 227 | ExchangeRate { 228 | rate: Uint128, 229 | denom: String, 230 | }, 231 | Allowance { 232 | spender: HumanAddr, 233 | owner: HumanAddr, 234 | allowance: Uint128, 235 | expiration: Option, 236 | }, 237 | Balance { 238 | amount: Uint128, 239 | }, 240 | TransferHistory { 241 | txs: Vec, 242 | }, 243 | 244 | ViewingKeyError { 245 | msg: String, 246 | }, 247 | } 248 | 249 | #[derive(Serialize, Deserialize, Clone, PartialEq, JsonSchema)] 250 | pub struct CreateViewingKeyResponse { 251 | pub key: String, 252 | } 253 | 254 | #[derive(Serialize, Deserialize, Clone, PartialEq, JsonSchema, Debug)] 255 | #[serde(rename_all = "snake_case")] 256 | pub enum ResponseStatus { 257 | Success, 258 | Failure, 259 | } 260 | 261 | #[derive(Serialize, Deserialize, Clone, PartialEq, JsonSchema, Debug)] 262 | #[serde(rename_all = "snake_case")] 263 | pub enum ContractStatusLevel { 264 | NormalRun, 265 | StopAllButRedeems, 266 | StopAll, 267 | } 268 | 269 | pub fn status_level_to_u8(status_level: ContractStatusLevel) -> u8 { 270 | match status_level { 271 | ContractStatusLevel::NormalRun => 0, 272 | ContractStatusLevel::StopAllButRedeems => 1, 273 | ContractStatusLevel::StopAll => 2, 274 | } 275 | } 276 | 277 | pub fn u8_to_status_level(status_level: u8) -> StdResult { 278 | match status_level { 279 | 0 => Ok(ContractStatusLevel::NormalRun), 280 | 1 => Ok(ContractStatusLevel::StopAllButRedeems), 281 | 2 => Ok(ContractStatusLevel::StopAll), 282 | _ => Err(StdError::generic_err("Invalid state level")), 283 | } 284 | } 285 | 286 | // Take a Vec and pad it up to a multiple of `block_size`, using spaces at the end. 287 | pub fn space_pad(block_size: usize, message: &mut Vec) -> &mut Vec { 288 | let len = message.len(); 289 | let surplus = len % block_size; 290 | if surplus == 0 { 291 | return message; 292 | } 293 | 294 | let missing = block_size - surplus; 295 | message.reserve(missing); 296 | message.extend(std::iter::repeat(b' ').take(missing)); 297 | message 298 | } 299 | 300 | #[cfg(test)] 301 | mod tests { 302 | use super::*; 303 | use cosmwasm_std::{from_slice, StdResult}; 304 | 305 | #[derive(Serialize, Deserialize, JsonSchema, Debug, PartialEq)] 306 | #[serde(rename_all = "snake_case")] 307 | pub enum Something { 308 | Var { padding: Option }, 309 | } 310 | 311 | #[test] 312 | fn test_deserialization_of_missing_option_fields() -> StdResult<()> { 313 | let input = b"{ \"var\": {} }"; 314 | let obj: Something = from_slice(input)?; 315 | assert_eq!( 316 | obj, 317 | Something::Var { padding: None }, 318 | "unexpected value: {:?}", 319 | obj 320 | ); 321 | Ok(()) 322 | } 323 | } 324 | -------------------------------------------------------------------------------- /src/receiver.rs: -------------------------------------------------------------------------------- 1 | use schemars::JsonSchema; 2 | use serde::{Deserialize, Serialize}; 3 | 4 | use cosmwasm_std::{to_binary, Binary, CosmosMsg, HumanAddr, StdResult, Uint128, WasmMsg}; 5 | 6 | use crate::{contract::RESPONSE_BLOCK_SIZE, msg::space_pad}; 7 | 8 | /// Snip20ReceiveMsg should be de/serialized under `Receive()` variant in a HandleMsg 9 | #[derive(Serialize, Deserialize, Clone, PartialEq, JsonSchema, Debug)] 10 | #[serde(rename_all = "snake_case")] 11 | pub struct Snip20ReceiveMsg { 12 | pub sender: HumanAddr, 13 | pub from: HumanAddr, 14 | pub amount: Uint128, 15 | pub msg: Option, 16 | } 17 | 18 | impl Snip20ReceiveMsg { 19 | pub fn new(sender: HumanAddr, from: HumanAddr, amount: Uint128, msg: Option) -> Self { 20 | Self { 21 | sender, 22 | from, 23 | amount, 24 | msg, 25 | } 26 | } 27 | 28 | /// serializes the message, and pads it to 256 bytes 29 | pub fn into_binary(self) -> StdResult { 30 | let msg = ReceiverHandleMsg::Receive(self); 31 | let mut data = to_binary(&msg)?; 32 | space_pad(RESPONSE_BLOCK_SIZE, &mut data.0); 33 | Ok(data) 34 | } 35 | 36 | /// creates a cosmos_msg sending this struct to the named contract 37 | pub fn into_cosmos_msg( 38 | self, 39 | callback_code_hash: String, 40 | contract_addr: HumanAddr, 41 | ) -> StdResult { 42 | let msg = self.into_binary()?; 43 | let execute = WasmMsg::Execute { 44 | msg, 45 | callback_code_hash, 46 | contract_addr, 47 | send: vec![], 48 | }; 49 | Ok(execute.into()) 50 | } 51 | } 52 | 53 | // This is just a helper to properly serialize the above message 54 | #[derive(Serialize, Deserialize, Clone, PartialEq, JsonSchema, Debug)] 55 | #[serde(rename_all = "snake_case")] 56 | enum ReceiverHandleMsg { 57 | Receive(Snip20ReceiveMsg), 58 | } 59 | -------------------------------------------------------------------------------- /src/state.rs: -------------------------------------------------------------------------------- 1 | use std::any::type_name; 2 | use std::convert::TryFrom; 3 | 4 | use cosmwasm_std::{ 5 | Api, CanonicalAddr, Coin, HumanAddr, ReadonlyStorage, StdError, StdResult, Storage, Uint128, 6 | }; 7 | use cosmwasm_storage::{PrefixedStorage, ReadonlyPrefixedStorage}; 8 | 9 | use secret_toolkit::storage::{AppendStore, AppendStoreMut, TypedStore, TypedStoreMut}; 10 | 11 | use schemars::JsonSchema; 12 | use serde::{Deserialize, Serialize}; 13 | 14 | use crate::msg::{status_level_to_u8, u8_to_status_level, ContractStatusLevel}; 15 | use crate::viewing_key::ViewingKey; 16 | use serde::de::DeserializeOwned; 17 | 18 | pub static CONFIG_KEY: &[u8] = b"config"; 19 | pub const PREFIX_TXS: &[u8] = b"transfers"; 20 | 21 | pub const KEY_CONSTANTS: &[u8] = b"constants"; 22 | pub const KEY_TOTAL_SUPPLY: &[u8] = b"total_supply"; 23 | pub const KEY_CONTRACT_STATUS: &[u8] = b"contract_status"; 24 | pub const KEY_TX_COUNT: &[u8] = b"tx-count"; 25 | 26 | pub const PREFIX_CONFIG: &[u8] = b"config"; 27 | pub const PREFIX_BALANCES: &[u8] = b"balances"; 28 | pub const PREFIX_ALLOWANCES: &[u8] = b"allowances"; 29 | pub const PREFIX_VIEW_KEY: &[u8] = b"viewingkey"; 30 | pub const PREFIX_RECEIVERS: &[u8] = b"receivers"; 31 | 32 | // Note that id is a globally incrementing counter. 33 | // Since it's 64 bits long, even at 50 tx/s it would take 34 | // over 11 billion years for it to rollback. I'm pretty sure 35 | // we'll have bigger issues by then. 36 | #[derive(Serialize, Deserialize, JsonSchema, Clone, Debug)] 37 | pub struct Tx { 38 | pub id: u64, 39 | pub from: HumanAddr, 40 | pub sender: HumanAddr, 41 | pub receiver: HumanAddr, 42 | pub coins: Coin, 43 | } 44 | 45 | impl Tx { 46 | pub fn into_stored(self, api: &A) -> StdResult { 47 | let tx = StoredTx { 48 | id: self.id, 49 | from: api.canonical_address(&self.from)?, 50 | sender: api.canonical_address(&self.sender)?, 51 | receiver: api.canonical_address(&self.receiver)?, 52 | coins: self.coins, 53 | }; 54 | Ok(tx) 55 | } 56 | } 57 | 58 | #[derive(Serialize, Deserialize, Clone, Debug)] 59 | pub struct StoredTx { 60 | pub id: u64, 61 | pub from: CanonicalAddr, 62 | pub sender: CanonicalAddr, 63 | pub receiver: CanonicalAddr, 64 | pub coins: Coin, 65 | } 66 | 67 | impl StoredTx { 68 | pub fn into_humanized(self, api: &A) -> StdResult { 69 | let tx = Tx { 70 | id: self.id, 71 | from: api.human_address(&self.from)?, 72 | sender: api.human_address(&self.sender)?, 73 | receiver: api.human_address(&self.receiver)?, 74 | coins: self.coins, 75 | }; 76 | Ok(tx) 77 | } 78 | } 79 | 80 | pub fn store_transfer( 81 | store: &mut S, 82 | owner: &CanonicalAddr, 83 | sender: &CanonicalAddr, 84 | receiver: &CanonicalAddr, 85 | amount: Uint128, 86 | denom: String, 87 | ) -> StdResult<()> { 88 | let mut config = Config::from_storage(store); 89 | let id = config.tx_count() + 1; 90 | config.set_tx_count(id)?; 91 | 92 | let coins = Coin { denom, amount }; 93 | let tx = StoredTx { 94 | id, 95 | from: owner.clone(), 96 | sender: sender.clone(), 97 | receiver: receiver.clone(), 98 | coins, 99 | }; 100 | 101 | if owner != sender { 102 | append_tx(store, tx.clone(), &owner)?; 103 | } 104 | append_tx(store, tx.clone(), &sender)?; 105 | append_tx(store, tx, &receiver)?; 106 | 107 | Ok(()) 108 | } 109 | 110 | fn append_tx( 111 | store: &mut S, 112 | tx: StoredTx, 113 | for_address: &CanonicalAddr, 114 | ) -> StdResult<()> { 115 | let mut store = PrefixedStorage::multilevel(&[PREFIX_TXS, for_address.as_slice()], store); 116 | let mut store = AppendStoreMut::attach_or_create(&mut store)?; 117 | store.push(&tx) 118 | } 119 | 120 | pub fn get_transfers( 121 | api: &A, 122 | storage: &S, 123 | for_address: &CanonicalAddr, 124 | page: u32, 125 | page_size: u32, 126 | ) -> StdResult> { 127 | let store = ReadonlyPrefixedStorage::multilevel(&[PREFIX_TXS, for_address.as_slice()], storage); 128 | 129 | // Try to access the storage of txs for the account. 130 | // If it doesn't exist yet, return an empty list of transfers. 131 | let store = if let Some(result) = AppendStore::::attach(&store) { 132 | result? 133 | } else { 134 | return Ok(vec![]); 135 | }; 136 | 137 | // Take `page_size` txs starting from the latest tx, potentially skipping `page * page_size` 138 | // txs from the start. 139 | let tx_iter = store 140 | .iter() 141 | .rev() 142 | .skip((page * page_size) as _) 143 | .take(page_size as _); 144 | // The `and_then` here flattens the `StdResult>` to an `StdResult` 145 | let txs: StdResult> = tx_iter 146 | .map(|tx| tx.map(|tx| tx.into_humanized(api)).and_then(|x| x)) 147 | .collect(); 148 | txs 149 | } 150 | 151 | // Config 152 | 153 | #[derive(Serialize, Debug, Deserialize, Clone, PartialEq, JsonSchema)] 154 | pub struct Constants { 155 | pub name: String, 156 | pub admin: HumanAddr, 157 | pub symbol: String, 158 | pub decimals: u8, 159 | pub prng_seed: Vec, 160 | // privacy configuration 161 | pub total_supply_is_public: bool, 162 | } 163 | 164 | pub struct ReadonlyConfig<'a, S: ReadonlyStorage> { 165 | storage: ReadonlyPrefixedStorage<'a, S>, 166 | } 167 | 168 | impl<'a, S: ReadonlyStorage> ReadonlyConfig<'a, S> { 169 | pub fn from_storage(storage: &'a S) -> Self { 170 | Self { 171 | storage: ReadonlyPrefixedStorage::new(PREFIX_CONFIG, storage), 172 | } 173 | } 174 | 175 | fn as_readonly(&self) -> ReadonlyConfigImpl> { 176 | ReadonlyConfigImpl(&self.storage) 177 | } 178 | 179 | pub fn constants(&self) -> StdResult { 180 | self.as_readonly().constants() 181 | } 182 | 183 | pub fn total_supply(&self) -> u128 { 184 | self.as_readonly().total_supply() 185 | } 186 | 187 | pub fn contract_status(&self) -> ContractStatusLevel { 188 | self.as_readonly().contract_status() 189 | } 190 | 191 | pub fn tx_count(&self) -> u64 { 192 | self.as_readonly().tx_count() 193 | } 194 | } 195 | 196 | fn set_bin_data(storage: &mut S, key: &[u8], data: &T) -> StdResult<()> { 197 | let bin_data = 198 | bincode2::serialize(&data).map_err(|e| StdError::serialize_err(type_name::(), e))?; 199 | 200 | storage.set(key, &bin_data); 201 | Ok(()) 202 | } 203 | 204 | fn get_bin_data(storage: &S, key: &[u8]) -> StdResult { 205 | let bin_data = storage.get(key); 206 | 207 | match bin_data { 208 | None => Err(StdError::not_found("Key not found in storage")), 209 | Some(bin_data) => Ok(bincode2::deserialize::(&bin_data) 210 | .map_err(|e| StdError::serialize_err(type_name::(), e))?), 211 | } 212 | } 213 | 214 | pub struct Config<'a, S: Storage> { 215 | storage: PrefixedStorage<'a, S>, 216 | } 217 | 218 | impl<'a, S: Storage> Config<'a, S> { 219 | pub fn from_storage(storage: &'a mut S) -> Self { 220 | Self { 221 | storage: PrefixedStorage::new(PREFIX_CONFIG, storage), 222 | } 223 | } 224 | 225 | fn as_readonly(&self) -> ReadonlyConfigImpl> { 226 | ReadonlyConfigImpl(&self.storage) 227 | } 228 | 229 | pub fn constants(&self) -> StdResult { 230 | self.as_readonly().constants() 231 | } 232 | 233 | pub fn set_constants(&mut self, constants: &Constants) -> StdResult<()> { 234 | set_bin_data(&mut self.storage, KEY_CONSTANTS, constants) 235 | } 236 | 237 | pub fn total_supply(&self) -> u128 { 238 | self.as_readonly().total_supply() 239 | } 240 | 241 | pub fn set_total_supply(&mut self, supply: u128) { 242 | self.storage.set(KEY_TOTAL_SUPPLY, &supply.to_be_bytes()); 243 | } 244 | 245 | pub fn contract_status(&self) -> ContractStatusLevel { 246 | self.as_readonly().contract_status() 247 | } 248 | 249 | pub fn set_contract_status(&mut self, status: ContractStatusLevel) { 250 | let status_u8 = status_level_to_u8(status); 251 | self.storage 252 | .set(KEY_CONTRACT_STATUS, &status_u8.to_be_bytes()); 253 | } 254 | 255 | pub fn tx_count(&self) -> u64 { 256 | self.as_readonly().tx_count() 257 | } 258 | 259 | pub fn set_tx_count(&mut self, count: u64) -> StdResult<()> { 260 | set_bin_data(&mut self.storage, KEY_TX_COUNT, &count) 261 | } 262 | } 263 | 264 | /// This struct refactors out the readonly methods that we need for `Config` and `ReadonlyConfig` 265 | /// in a way that is generic over their mutability. 266 | /// 267 | /// This was the only way to prevent code duplication of these methods because of the way 268 | /// that `ReadonlyPrefixedStorage` and `PrefixedStorage` are implemented in `cosmwasm-std` 269 | struct ReadonlyConfigImpl<'a, S: ReadonlyStorage>(&'a S); 270 | 271 | impl<'a, S: ReadonlyStorage> ReadonlyConfigImpl<'a, S> { 272 | fn constants(&self) -> StdResult { 273 | let consts_bytes = self 274 | .0 275 | .get(KEY_CONSTANTS) 276 | .ok_or_else(|| StdError::generic_err("no constants stored in configuration"))?; 277 | bincode2::deserialize::(&consts_bytes) 278 | .map_err(|e| StdError::serialize_err(type_name::(), e)) 279 | } 280 | 281 | fn total_supply(&self) -> u128 { 282 | let supply_bytes = self 283 | .0 284 | .get(KEY_TOTAL_SUPPLY) 285 | .expect("no total supply stored in config"); 286 | // This unwrap is ok because we know we stored things correctly 287 | slice_to_u128(&supply_bytes).unwrap() 288 | } 289 | 290 | fn contract_status(&self) -> ContractStatusLevel { 291 | let supply_bytes = self 292 | .0 293 | .get(KEY_CONTRACT_STATUS) 294 | .expect("no contract status stored in config"); 295 | 296 | // These unwraps are ok because we know we stored things correctly 297 | let status = slice_to_u8(&supply_bytes).unwrap(); 298 | u8_to_status_level(status).unwrap() 299 | } 300 | 301 | pub fn tx_count(&self) -> u64 { 302 | get_bin_data(self.0, KEY_TX_COUNT).unwrap_or_default() 303 | } 304 | } 305 | 306 | // Balances 307 | 308 | pub struct ReadonlyBalances<'a, S: ReadonlyStorage> { 309 | storage: ReadonlyPrefixedStorage<'a, S>, 310 | } 311 | 312 | impl<'a, S: ReadonlyStorage> ReadonlyBalances<'a, S> { 313 | pub fn from_storage(storage: &'a S) -> Self { 314 | Self { 315 | storage: ReadonlyPrefixedStorage::new(PREFIX_BALANCES, storage), 316 | } 317 | } 318 | 319 | fn as_readonly(&self) -> ReadonlyBalancesImpl> { 320 | ReadonlyBalancesImpl(&self.storage) 321 | } 322 | 323 | pub fn account_amount(&self, account: &CanonicalAddr) -> u128 { 324 | self.as_readonly().account_amount(account) 325 | } 326 | } 327 | 328 | pub struct Balances<'a, S: Storage> { 329 | storage: PrefixedStorage<'a, S>, 330 | } 331 | 332 | impl<'a, S: Storage> Balances<'a, S> { 333 | pub fn from_storage(storage: &'a mut S) -> Self { 334 | Self { 335 | storage: PrefixedStorage::new(PREFIX_BALANCES, storage), 336 | } 337 | } 338 | 339 | fn as_readonly(&self) -> ReadonlyBalancesImpl> { 340 | ReadonlyBalancesImpl(&self.storage) 341 | } 342 | 343 | pub fn balance(&self, account: &CanonicalAddr) -> u128 { 344 | self.as_readonly().account_amount(account) 345 | } 346 | 347 | pub fn set_account_balance(&mut self, account: &CanonicalAddr, amount: u128) { 348 | self.storage.set(account.as_slice(), &amount.to_be_bytes()) 349 | } 350 | } 351 | 352 | /// This struct refactors out the readonly methods that we need for `Balances` and `ReadonlyBalances` 353 | /// in a way that is generic over their mutability. 354 | /// 355 | /// This was the only way to prevent code duplication of these methods because of the way 356 | /// that `ReadonlyPrefixedStorage` and `PrefixedStorage` are implemented in `cosmwasm-std` 357 | struct ReadonlyBalancesImpl<'a, S: ReadonlyStorage>(&'a S); 358 | 359 | impl<'a, S: ReadonlyStorage> ReadonlyBalancesImpl<'a, S> { 360 | pub fn account_amount(&self, account: &CanonicalAddr) -> u128 { 361 | let account_bytes = account.as_slice(); 362 | let result = self.0.get(account_bytes); 363 | match result { 364 | // This unwrap is ok because we know we stored things correctly 365 | Some(balance_bytes) => slice_to_u128(&balance_bytes).unwrap(), 366 | None => 0, 367 | } 368 | } 369 | } 370 | 371 | // Allowances 372 | 373 | #[derive(Serialize, Debug, Deserialize, Clone, PartialEq, Default, JsonSchema)] 374 | pub struct Allowance { 375 | pub amount: u128, 376 | pub expiration: Option, 377 | } 378 | 379 | pub fn read_allowance( 380 | store: &S, 381 | owner: &CanonicalAddr, 382 | spender: &CanonicalAddr, 383 | ) -> StdResult { 384 | let owner_store = 385 | ReadonlyPrefixedStorage::multilevel(&[PREFIX_ALLOWANCES, owner.as_slice()], store); 386 | let owner_store = TypedStore::attach(&owner_store); 387 | let allowance = owner_store.may_load(spender.as_slice()); 388 | allowance.map(Option::unwrap_or_default) 389 | } 390 | 391 | pub fn write_allowance( 392 | store: &mut S, 393 | owner: &CanonicalAddr, 394 | spender: &CanonicalAddr, 395 | allowance: Allowance, 396 | ) -> StdResult<()> { 397 | let mut owner_store = 398 | PrefixedStorage::multilevel(&[PREFIX_ALLOWANCES, owner.as_slice()], store); 399 | let mut owner_store = TypedStoreMut::attach(&mut owner_store); 400 | 401 | owner_store.store(spender.as_slice(), &allowance) 402 | } 403 | 404 | // Viewing Keys 405 | 406 | pub fn write_viewing_key(store: &mut S, owner: &CanonicalAddr, key: &ViewingKey) { 407 | let mut balance_store = PrefixedStorage::new(PREFIX_VIEW_KEY, store); 408 | balance_store.set(owner.as_slice(), &key.to_hashed()); 409 | } 410 | 411 | pub fn read_viewing_key(store: &S, owner: &CanonicalAddr) -> Option> { 412 | let balance_store = ReadonlyPrefixedStorage::new(PREFIX_VIEW_KEY, store); 413 | balance_store.get(owner.as_slice()) 414 | } 415 | 416 | // Receiver Interface 417 | 418 | pub fn get_receiver_hash( 419 | store: &S, 420 | account: &HumanAddr, 421 | ) -> Option> { 422 | let store = ReadonlyPrefixedStorage::new(PREFIX_RECEIVERS, store); 423 | store.get(account.as_str().as_bytes()).map(|data| { 424 | String::from_utf8(data) 425 | .map_err(|_err| StdError::invalid_utf8("stored code hash was not a valid String")) 426 | }) 427 | } 428 | 429 | pub fn set_receiver_hash(store: &mut S, account: &HumanAddr, code_hash: String) { 430 | let mut store = PrefixedStorage::new(PREFIX_RECEIVERS, store); 431 | store.set(account.as_str().as_bytes(), code_hash.as_bytes()); 432 | } 433 | 434 | // Helpers 435 | 436 | /// Converts 16 bytes value into u128 437 | /// Errors if data found that is not 16 bytes 438 | fn slice_to_u128(data: &[u8]) -> StdResult { 439 | match <[u8; 16]>::try_from(data) { 440 | Ok(bytes) => Ok(u128::from_be_bytes(bytes)), 441 | Err(_) => Err(StdError::generic_err( 442 | "Corrupted data found. 16 byte expected.", 443 | )), 444 | } 445 | } 446 | 447 | /// Converts 1 byte value into u8 448 | /// Errors if data found that is not 1 byte 449 | fn slice_to_u8(data: &[u8]) -> StdResult { 450 | if data.len() == 1 { 451 | Ok(data[0]) 452 | } else { 453 | Err(StdError::generic_err( 454 | "Corrupted data found. 1 byte expected.", 455 | )) 456 | } 457 | } 458 | -------------------------------------------------------------------------------- /src/utils.rs: -------------------------------------------------------------------------------- 1 | use crate::viewing_key::VIEWING_KEY_SIZE; 2 | use sha2::{Digest, Sha256}; 3 | use std::convert::TryInto; 4 | use subtle::ConstantTimeEq; 5 | 6 | pub fn ct_slice_compare(s1: &[u8], s2: &[u8]) -> bool { 7 | bool::from(s1.ct_eq(s2)) 8 | } 9 | 10 | pub fn create_hashed_password(s1: &str) -> [u8; VIEWING_KEY_SIZE] { 11 | Sha256::digest(s1.as_bytes()) 12 | .as_slice() 13 | .try_into() 14 | .expect("Wrong password length") 15 | } 16 | -------------------------------------------------------------------------------- /src/viewing_key.rs: -------------------------------------------------------------------------------- 1 | use std::fmt; 2 | 3 | use schemars::JsonSchema; 4 | use serde::{Deserialize, Serialize}; 5 | 6 | use cosmwasm_std::Env; 7 | use secret_toolkit::crypto::{sha_256, Prng}; 8 | 9 | use crate::utils::{create_hashed_password, ct_slice_compare}; 10 | 11 | pub const VIEWING_KEY_SIZE: usize = 32; 12 | pub const VIEWING_KEY_PREFIX: &str = "api_key_"; 13 | 14 | #[derive(Serialize, Deserialize, JsonSchema, Clone, Debug)] 15 | pub struct ViewingKey(pub String); 16 | 17 | impl ViewingKey { 18 | pub fn check_viewing_key(&self, hashed_pw: &[u8]) -> bool { 19 | let mine_hashed = create_hashed_password(&self.0); 20 | 21 | ct_slice_compare(&mine_hashed, hashed_pw) 22 | } 23 | 24 | pub fn new(env: &Env, seed: &[u8], entropy: &[u8]) -> Self { 25 | // 16 here represents the lengths in bytes of the block height and time. 26 | let entropy_len = 16 + env.message.sender.len() + entropy.len(); 27 | let mut rng_entropy = Vec::with_capacity(entropy_len); 28 | rng_entropy.extend_from_slice(&env.block.height.to_be_bytes()); 29 | rng_entropy.extend_from_slice(&env.block.time.to_be_bytes()); 30 | rng_entropy.extend_from_slice(&env.message.sender.0.as_bytes()); 31 | rng_entropy.extend_from_slice(entropy); 32 | 33 | let mut rng = Prng::new(seed, &rng_entropy); 34 | 35 | let rand_slice = rng.rand_bytes(); 36 | 37 | let key = sha_256(&rand_slice); 38 | 39 | Self(VIEWING_KEY_PREFIX.to_string() + &base64::encode(key)) 40 | } 41 | 42 | pub fn to_hashed(&self) -> [u8; VIEWING_KEY_SIZE] { 43 | create_hashed_password(&self.0) 44 | } 45 | 46 | pub fn as_bytes(&self) -> &[u8] { 47 | self.0.as_bytes() 48 | } 49 | } 50 | 51 | impl fmt::Display for ViewingKey { 52 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 53 | write!(f, "{}", self.0) 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /tests/example-receiver/.gitignore: -------------------------------------------------------------------------------- 1 | # Build results 2 | /target 3 | contract.wasm 4 | contract.wasm.gz 5 | 6 | # Cargo+Git helper file (https://github.com/rust-lang/cargo/blob/0.44.1/src/cargo/sources/git/utils.rs#L320-L327) 7 | .cargo-ok 8 | 9 | # Text file backups 10 | **/*.rs.bk 11 | 12 | # macOS 13 | .DS_Store 14 | 15 | # IDEs 16 | *.iml 17 | .idea 18 | -------------------------------------------------------------------------------- /tests/example-receiver/Cargo.lock: -------------------------------------------------------------------------------- 1 | # This file is automatically @generated by Cargo. 2 | # It is not intended for manual editing. 3 | [[package]] 4 | name = "addr2line" 5 | version = "0.13.0" 6 | source = "registry+https://github.com/rust-lang/crates.io-index" 7 | checksum = "1b6a2d3371669ab3ca9797670853d61402b03d0b4b9ebf33d677dfa720203072" 8 | dependencies = [ 9 | "gimli", 10 | ] 11 | 12 | [[package]] 13 | name = "adler" 14 | version = "0.2.3" 15 | source = "registry+https://github.com/rust-lang/crates.io-index" 16 | checksum = "ee2a4ec343196209d6594e19543ae87a39f96d5534d7174822a3ad825dd6ed7e" 17 | 18 | [[package]] 19 | name = "autocfg" 20 | version = "1.0.1" 21 | source = "registry+https://github.com/rust-lang/crates.io-index" 22 | checksum = "cdb031dd78e28731d87d56cc8ffef4a8f36ca26c38fe2de700543e627f8a464a" 23 | 24 | [[package]] 25 | name = "backtrace" 26 | version = "0.3.51" 27 | source = "registry+https://github.com/rust-lang/crates.io-index" 28 | checksum = "ec1931848a574faa8f7c71a12ea00453ff5effbb5f51afe7f77d7a48cace6ac1" 29 | dependencies = [ 30 | "addr2line", 31 | "cfg-if", 32 | "libc", 33 | "miniz_oxide", 34 | "object", 35 | "rustc-demangle", 36 | ] 37 | 38 | [[package]] 39 | name = "base64" 40 | version = "0.11.0" 41 | source = "registry+https://github.com/rust-lang/crates.io-index" 42 | checksum = "b41b7ea54a0c9d92199de89e20e58d49f02f8e699814ef3fdf266f6f748d15c7" 43 | 44 | [[package]] 45 | name = "cfg-if" 46 | version = "0.1.10" 47 | source = "registry+https://github.com/rust-lang/crates.io-index" 48 | checksum = "4785bdd1c96b2a846b2bd7cc02e86b6b3dbf14e7e53446c4f54c92a361040822" 49 | 50 | [[package]] 51 | name = "cosmwasm-std" 52 | version = "0.10.0" 53 | source = "git+https://github.com/enigmampc/SecretNetwork?tag=v1.0.0#490fba9243e6cb291462e9d3c1bcbd1975c0df1e" 54 | dependencies = [ 55 | "base64", 56 | "schemars", 57 | "serde", 58 | "serde-json-wasm", 59 | "snafu", 60 | ] 61 | 62 | [[package]] 63 | name = "cosmwasm-storage" 64 | version = "0.10.0" 65 | source = "git+https://github.com/enigmampc/SecretNetwork?tag=v1.0.0#490fba9243e6cb291462e9d3c1bcbd1975c0df1e" 66 | dependencies = [ 67 | "cosmwasm-std", 68 | "serde", 69 | ] 70 | 71 | [[package]] 72 | name = "doc-comment" 73 | version = "0.3.3" 74 | source = "registry+https://github.com/rust-lang/crates.io-index" 75 | checksum = "fea41bba32d969b513997752735605054bc0dfa92b4c56bf1189f2e174be7a10" 76 | 77 | [[package]] 78 | name = "gimli" 79 | version = "0.22.0" 80 | source = "registry+https://github.com/rust-lang/crates.io-index" 81 | checksum = "aaf91faf136cb47367fa430cd46e37a788775e7fa104f8b4bcb3861dc389b724" 82 | 83 | [[package]] 84 | name = "itoa" 85 | version = "0.4.6" 86 | source = "registry+https://github.com/rust-lang/crates.io-index" 87 | checksum = "dc6f3ad7b9d11a0c00842ff8de1b60ee58661048eb8049ed33c73594f359d7e6" 88 | 89 | [[package]] 90 | name = "libc" 91 | version = "0.2.78" 92 | source = "registry+https://github.com/rust-lang/crates.io-index" 93 | checksum = "aa7087f49d294270db4e1928fc110c976cd4b9e5a16348e0a1df09afa99e6c98" 94 | 95 | [[package]] 96 | name = "miniz_oxide" 97 | version = "0.4.2" 98 | source = "registry+https://github.com/rust-lang/crates.io-index" 99 | checksum = "c60c0dfe32c10b43a144bad8fc83538c52f58302c92300ea7ec7bf7b38d5a7b9" 100 | dependencies = [ 101 | "adler", 102 | "autocfg", 103 | ] 104 | 105 | [[package]] 106 | name = "object" 107 | version = "0.20.0" 108 | source = "registry+https://github.com/rust-lang/crates.io-index" 109 | checksum = "1ab52be62400ca80aa00285d25253d7f7c437b7375c4de678f5405d3afe82ca5" 110 | 111 | [[package]] 112 | name = "proc-macro2" 113 | version = "1.0.24" 114 | source = "registry+https://github.com/rust-lang/crates.io-index" 115 | checksum = "1e0704ee1a7e00d7bb417d0770ea303c1bccbabf0ef1667dae92b5967f5f8a71" 116 | dependencies = [ 117 | "unicode-xid", 118 | ] 119 | 120 | [[package]] 121 | name = "quote" 122 | version = "1.0.7" 123 | source = "registry+https://github.com/rust-lang/crates.io-index" 124 | checksum = "aa563d17ecb180e500da1cfd2b028310ac758de548efdd203e18f283af693f37" 125 | dependencies = [ 126 | "proc-macro2", 127 | ] 128 | 129 | [[package]] 130 | name = "receiver-contract" 131 | version = "0.1.0" 132 | dependencies = [ 133 | "cosmwasm-std", 134 | "cosmwasm-storage", 135 | "schemars", 136 | "serde", 137 | "snafu", 138 | ] 139 | 140 | [[package]] 141 | name = "rustc-demangle" 142 | version = "0.1.16" 143 | source = "registry+https://github.com/rust-lang/crates.io-index" 144 | checksum = "4c691c0e608126e00913e33f0ccf3727d5fc84573623b8d65b2df340b5201783" 145 | 146 | [[package]] 147 | name = "ryu" 148 | version = "1.0.5" 149 | source = "registry+https://github.com/rust-lang/crates.io-index" 150 | checksum = "71d301d4193d031abdd79ff7e3dd721168a9572ef3fe51a1517aba235bd8f86e" 151 | 152 | [[package]] 153 | name = "schemars" 154 | version = "0.7.6" 155 | source = "registry+https://github.com/rust-lang/crates.io-index" 156 | checksum = "be77ed66abed6954aabf6a3e31a84706bedbf93750d267e92ef4a6d90bbd6a61" 157 | dependencies = [ 158 | "schemars_derive", 159 | "serde", 160 | "serde_json", 161 | ] 162 | 163 | [[package]] 164 | name = "schemars_derive" 165 | version = "0.7.6" 166 | source = "registry+https://github.com/rust-lang/crates.io-index" 167 | checksum = "11af7a475c9ee266cfaa9e303a47c830ebe072bf3101ab907a7b7b9d816fa01d" 168 | dependencies = [ 169 | "proc-macro2", 170 | "quote", 171 | "serde_derive_internals", 172 | "syn", 173 | ] 174 | 175 | [[package]] 176 | name = "serde" 177 | version = "1.0.116" 178 | source = "registry+https://github.com/rust-lang/crates.io-index" 179 | checksum = "96fe57af81d28386a513cbc6858332abc6117cfdb5999647c6444b8f43a370a5" 180 | dependencies = [ 181 | "serde_derive", 182 | ] 183 | 184 | [[package]] 185 | name = "serde-json-wasm" 186 | version = "0.2.1" 187 | source = "registry+https://github.com/rust-lang/crates.io-index" 188 | checksum = "7294d94d390f1d2334697c065ea591d7074c676e2d20aa6f1df752fced29823f" 189 | dependencies = [ 190 | "serde", 191 | ] 192 | 193 | [[package]] 194 | name = "serde_derive" 195 | version = "1.0.116" 196 | source = "registry+https://github.com/rust-lang/crates.io-index" 197 | checksum = "f630a6370fd8e457873b4bd2ffdae75408bc291ba72be773772a4c2a065d9ae8" 198 | dependencies = [ 199 | "proc-macro2", 200 | "quote", 201 | "syn", 202 | ] 203 | 204 | [[package]] 205 | name = "serde_derive_internals" 206 | version = "0.25.0" 207 | source = "registry+https://github.com/rust-lang/crates.io-index" 208 | checksum = "1dbab34ca63057a1f15280bdf3c39f2b1eb1b54c17e98360e511637aef7418c6" 209 | dependencies = [ 210 | "proc-macro2", 211 | "quote", 212 | "syn", 213 | ] 214 | 215 | [[package]] 216 | name = "serde_json" 217 | version = "1.0.58" 218 | source = "registry+https://github.com/rust-lang/crates.io-index" 219 | checksum = "a230ea9107ca2220eea9d46de97eddcb04cd00e92d13dda78e478dd33fa82bd4" 220 | dependencies = [ 221 | "itoa", 222 | "ryu", 223 | "serde", 224 | ] 225 | 226 | [[package]] 227 | name = "snafu" 228 | version = "0.6.9" 229 | source = "registry+https://github.com/rust-lang/crates.io-index" 230 | checksum = "9c4e6046e4691afe918fd1b603fd6e515bcda5388a1092a9edbada307d159f09" 231 | dependencies = [ 232 | "backtrace", 233 | "doc-comment", 234 | "snafu-derive", 235 | ] 236 | 237 | [[package]] 238 | name = "snafu-derive" 239 | version = "0.6.9" 240 | source = "registry+https://github.com/rust-lang/crates.io-index" 241 | checksum = "7073448732a89f2f3e6581989106067f403d378faeafb4a50812eb814170d3e5" 242 | dependencies = [ 243 | "proc-macro2", 244 | "quote", 245 | "syn", 246 | ] 247 | 248 | [[package]] 249 | name = "syn" 250 | version = "1.0.42" 251 | source = "registry+https://github.com/rust-lang/crates.io-index" 252 | checksum = "9c51d92969d209b54a98397e1b91c8ae82d8c87a7bb87df0b29aa2ad81454228" 253 | dependencies = [ 254 | "proc-macro2", 255 | "quote", 256 | "unicode-xid", 257 | ] 258 | 259 | [[package]] 260 | name = "unicode-xid" 261 | version = "0.2.1" 262 | source = "registry+https://github.com/rust-lang/crates.io-index" 263 | checksum = "f7fe0bb3479651439c9112f72b6c505038574c9fbb575ed1bf3b797fa39dd564" 264 | -------------------------------------------------------------------------------- /tests/example-receiver/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "receiver-contract" 3 | version = "0.1.0" 4 | authors = ["TomL94 "] 5 | edition = "2018" 6 | 7 | exclude = [ 8 | # Those files are rust-optimizer artifacts. You might want to commit them for convenience but they should not be part of the source code publication. 9 | "contract.wasm", 10 | "hash.txt", 11 | ] 12 | 13 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 14 | 15 | [lib] 16 | crate-type = ["cdylib", "rlib"] 17 | 18 | [profile.release] 19 | opt-level = 3 20 | debug = false 21 | rpath = false 22 | lto = true 23 | debug-assertions = false 24 | codegen-units = 1 25 | panic = 'abort' 26 | incremental = false 27 | overflow-checks = true 28 | 29 | [features] 30 | default = [] 31 | # for quicker tests, cargo test --lib 32 | # for more explicit tests, cargo test --features=backtraces 33 | backtraces = ["cosmwasm-std/backtraces"] 34 | 35 | [dependencies] 36 | cosmwasm-std = { git = "https://github.com/enigmampc/SecretNetwork", tag = "v1.0.0" } 37 | cosmwasm-storage = { git = "https://github.com/enigmampc/SecretNetwork", tag = "v1.0.0" } 38 | schemars = "0.7" 39 | serde = { version = "1.0.103", default-features = false, features = ["derive"] } 40 | snafu = { version = "0.6.3" } 41 | -------------------------------------------------------------------------------- /tests/example-receiver/Makefile: -------------------------------------------------------------------------------- 1 | .PHONY: all 2 | all: clippy test 3 | 4 | .PHONY: check 5 | check: 6 | cargo check 7 | 8 | .PHONY: clippy 9 | clippy: 10 | cargo clippy 11 | 12 | .PHONY: test 13 | test: unit-test 14 | 15 | .PHONY: unit-test 16 | unit-test: 17 | cargo test 18 | 19 | .PHONY: compile _compile 20 | compile: _compile contract.wasm.gz 21 | _compile: 22 | cargo build --target wasm32-unknown-unknown --locked 23 | cp ./target/wasm32-unknown-unknown/debug/*.wasm ./contract.wasm 24 | 25 | .PHONY: compile-optimized _compile-optimized 26 | compile-optimized: _compile-optimized contract.wasm.gz 27 | _compile-optimized: 28 | RUSTFLAGS='-C link-arg=-s' cargo build --release --target wasm32-unknown-unknown --locked 29 | @# The following line is not necessary, may work only on linux (extra size optimization) 30 | wasm-opt -Os ./target/wasm32-unknown-unknown/release/*.wasm -o ./contract.wasm 31 | 32 | .PHONY: compile-optimized-reproducible 33 | compile-optimized-reproducible: 34 | docker run --rm -v "$$(pwd)":/contract \ 35 | --mount type=volume,source="$$(basename "$$(pwd)")_cache",target=/code/target \ 36 | --mount type=volume,source=registry_cache,target=/usr/local/cargo/registry \ 37 | enigmampc/secret-contract-optimizer 38 | 39 | contract.wasm.gz: contract.wasm 40 | cat ./contract.wasm | gzip -9 > ./contract.wasm.gz 41 | 42 | .PHONY: schema 43 | schema: 44 | cargo run --example schema 45 | 46 | .PHONY: clean 47 | clean: 48 | cargo clean 49 | rm -f ./contract.wasm ./contract.wasm.gz 50 | -------------------------------------------------------------------------------- /tests/example-receiver/README.md: -------------------------------------------------------------------------------- 1 | # Exmaple Receiver Contract 2 | 3 | This contract shows how to correctly implement the receiver contract, in order to interact 4 | with a SNIP-20 contract such as `secret-secret`. 5 | -------------------------------------------------------------------------------- /tests/example-receiver/rustfmt.toml: -------------------------------------------------------------------------------- 1 | # stable 2 | newline_style = "unix" 3 | hard_tabs = false 4 | tab_spaces = 4 5 | 6 | # unstable... should we require `rustup run nightly cargo fmt` ? 7 | # or just update the style guide when they are stable? 8 | #fn_single_line = true 9 | #format_code_in_doc_comments = true 10 | #overflow_delimited_expr = true 11 | #reorder_impl_items = true 12 | #struct_field_align_threshold = 20 13 | #struct_lit_single_line = true 14 | #report_todo = "Always" 15 | 16 | -------------------------------------------------------------------------------- /tests/example-receiver/src/contract.rs: -------------------------------------------------------------------------------- 1 | use cosmwasm_std::{ 2 | from_binary, to_binary, Api, BankMsg, Binary, Coin, Context, CosmosMsg, Env, Extern, 3 | HandleResponse, HumanAddr, InitResponse, Querier, StdError, StdResult, Storage, Uint128, 4 | WasmMsg, 5 | }; 6 | 7 | use crate::msg::{CountResponse, HandleMsg, InitMsg, QueryMsg, Snip20Msg}; 8 | use crate::state::{config, config_read, State}; 9 | 10 | pub fn init( 11 | deps: &mut Extern, 12 | env: Env, 13 | msg: InitMsg, 14 | ) -> StdResult { 15 | let state = State { 16 | count: msg.count, 17 | owner: deps.api.canonical_address(&env.message.sender)?, 18 | known_snip_20: vec![], 19 | }; 20 | 21 | config(&mut deps.storage).save(&state)?; 22 | 23 | Ok(InitResponse::default()) 24 | } 25 | 26 | pub fn handle( 27 | deps: &mut Extern, 28 | env: Env, 29 | msg: HandleMsg, 30 | ) -> StdResult { 31 | match msg { 32 | HandleMsg::Increment {} => try_increment(deps, env), 33 | HandleMsg::Reset { count } => try_reset(deps, env, count), 34 | HandleMsg::Register { reg_addr, reg_hash } => try_register(deps, env, reg_addr, reg_hash), 35 | HandleMsg::Receive { 36 | sender, 37 | from, 38 | amount, 39 | msg, 40 | } => try_receive(deps, env, sender, from, amount, msg), 41 | HandleMsg::Redeem { 42 | addr, 43 | hash, 44 | to, 45 | amount, 46 | } => try_redeem(deps, env, addr, hash, to, amount), 47 | HandleMsg::Fail {} => try_fail(), 48 | } 49 | } 50 | 51 | pub fn try_increment( 52 | deps: &mut Extern, 53 | _env: Env, 54 | ) -> StdResult { 55 | let mut count = 0; 56 | config(&mut deps.storage).update(|mut state| { 57 | state.count += 1; 58 | count = state.count; 59 | Ok(state) 60 | })?; 61 | 62 | let mut context = Context::new(); 63 | context.add_log("count", count.to_string()); 64 | 65 | Ok(context.into()) 66 | } 67 | 68 | pub fn try_reset( 69 | deps: &mut Extern, 70 | env: Env, 71 | count: i32, 72 | ) -> StdResult { 73 | let sender_address_raw = deps.api.canonical_address(&env.message.sender)?; 74 | config(&mut deps.storage).update(|mut state| { 75 | if sender_address_raw != state.owner { 76 | return Err(StdError::Unauthorized { backtrace: None }); 77 | } 78 | state.count = count; 79 | Ok(state) 80 | })?; 81 | Ok(HandleResponse::default()) 82 | } 83 | 84 | pub fn try_register( 85 | deps: &mut Extern, 86 | env: Env, 87 | reg_addr: HumanAddr, 88 | reg_hash: String, 89 | ) -> StdResult { 90 | let mut conf = config(&mut deps.storage); 91 | let mut state = conf.load()?; 92 | if !state.known_snip_20.contains(®_addr) { 93 | state.known_snip_20.push(reg_addr.clone()); 94 | } 95 | conf.save(&state)?; 96 | 97 | let msg = to_binary(&Snip20Msg::register_receive(env.contract_code_hash))?; 98 | let message = CosmosMsg::Wasm(WasmMsg::Execute { 99 | contract_addr: reg_addr, 100 | callback_code_hash: reg_hash, 101 | msg, 102 | send: vec![], 103 | }); 104 | 105 | Ok(HandleResponse { 106 | messages: vec![message], 107 | log: vec![], 108 | data: None, 109 | }) 110 | } 111 | 112 | pub fn try_receive( 113 | deps: &mut Extern, 114 | env: Env, 115 | _sender: HumanAddr, 116 | _from: HumanAddr, 117 | _amount: Uint128, 118 | msg: Binary, 119 | ) -> StdResult { 120 | let msg: HandleMsg = from_binary(&msg)?; 121 | 122 | if matches!(msg, HandleMsg::Receive { .. }) { 123 | return Err(StdError::generic_err( 124 | "Recursive call to receive() is not allowed", 125 | )); 126 | } 127 | 128 | let state = config_read(&deps.storage).load()?; 129 | if !state.known_snip_20.contains(&env.message.sender) { 130 | return Err(StdError::generic_err(format!( 131 | "{} is not a known SNIP-20 coin that this contract registered to", 132 | env.message.sender 133 | ))); 134 | } 135 | 136 | /* use sender & amount */ 137 | handle(deps, env, msg) 138 | } 139 | 140 | fn try_redeem( 141 | deps: &mut Extern, 142 | env: Env, 143 | addr: HumanAddr, 144 | hash: String, 145 | to: HumanAddr, 146 | amount: Uint128, 147 | ) -> StdResult { 148 | let state = config_read(&deps.storage).load()?; 149 | if !state.known_snip_20.contains(&addr) { 150 | return Err(StdError::generic_err(format!( 151 | "{} is not a known SNIP-20 coin that this contract registered to", 152 | addr 153 | ))); 154 | } 155 | 156 | let msg = to_binary(&Snip20Msg::redeem(amount))?; 157 | let secret_redeem = CosmosMsg::Wasm(WasmMsg::Execute { 158 | contract_addr: addr, 159 | callback_code_hash: hash, 160 | msg, 161 | send: vec![], 162 | }); 163 | let redeem = CosmosMsg::Bank(BankMsg::Send { 164 | amount: vec![Coin::new(amount.u128(), "uscrt")], 165 | from_address: env.contract.address, 166 | to_address: to, 167 | }); 168 | 169 | Ok(HandleResponse { 170 | messages: vec![secret_redeem, redeem], 171 | log: vec![], 172 | data: None, 173 | }) 174 | } 175 | 176 | fn try_fail() -> StdResult { 177 | Err(StdError::generic_err("intentional failure")) 178 | } 179 | 180 | pub fn query( 181 | deps: &Extern, 182 | msg: QueryMsg, 183 | ) -> StdResult { 184 | match msg { 185 | QueryMsg::GetCount {} => to_binary(&query_count(deps)?), 186 | } 187 | } 188 | 189 | fn query_count(deps: &Extern) -> StdResult { 190 | let state = config_read(&deps.storage).load()?; 191 | Ok(CountResponse { count: state.count }) 192 | } 193 | -------------------------------------------------------------------------------- /tests/example-receiver/src/lib.rs: -------------------------------------------------------------------------------- 1 | pub mod contract; 2 | pub mod msg; 3 | pub mod state; 4 | 5 | #[cfg(target_arch = "wasm32")] 6 | mod wasm { 7 | use super::contract; 8 | use cosmwasm_std::{ 9 | do_handle, do_init, do_query, ExternalApi, ExternalQuerier, ExternalStorage, 10 | }; 11 | 12 | #[no_mangle] 13 | extern "C" fn init(env_ptr: u32, msg_ptr: u32) -> u32 { 14 | do_init( 15 | &contract::init::, 16 | env_ptr, 17 | msg_ptr, 18 | ) 19 | } 20 | 21 | #[no_mangle] 22 | extern "C" fn handle(env_ptr: u32, msg_ptr: u32) -> u32 { 23 | do_handle( 24 | &contract::handle::, 25 | env_ptr, 26 | msg_ptr, 27 | ) 28 | } 29 | 30 | #[no_mangle] 31 | extern "C" fn query(msg_ptr: u32) -> u32 { 32 | do_query( 33 | &contract::query::, 34 | msg_ptr, 35 | ) 36 | } 37 | 38 | // Other C externs like cosmwasm_vm_version_1, allocate, deallocate are available 39 | // automatically because we `use cosmwasm_std`. 40 | } 41 | -------------------------------------------------------------------------------- /tests/example-receiver/src/msg.rs: -------------------------------------------------------------------------------- 1 | use cosmwasm_std::{Binary, HumanAddr, Uint128}; 2 | use schemars::JsonSchema; 3 | use serde::{Deserialize, Serialize}; 4 | 5 | #[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)] 6 | pub struct InitMsg { 7 | pub count: i32, 8 | } 9 | 10 | #[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)] 11 | #[serde(rename_all = "snake_case")] 12 | pub enum HandleMsg { 13 | Increment {}, 14 | Reset { 15 | count: i32, 16 | }, 17 | Register { 18 | reg_addr: HumanAddr, 19 | reg_hash: String, 20 | }, 21 | Receive { 22 | sender: HumanAddr, 23 | from: HumanAddr, 24 | amount: Uint128, 25 | msg: Binary, 26 | }, 27 | Redeem { 28 | addr: HumanAddr, 29 | hash: String, 30 | to: HumanAddr, 31 | amount: Uint128, 32 | }, 33 | Fail {}, 34 | } 35 | 36 | #[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)] 37 | #[serde(rename_all = "snake_case")] 38 | pub enum QueryMsg { 39 | // GetCount returns the current count as a json-encoded number 40 | GetCount {}, 41 | } 42 | 43 | // We define a custom struct for each query response 44 | #[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)] 45 | pub struct CountResponse { 46 | pub count: i32, 47 | } 48 | 49 | // Messages sent to SNIP-20 contracts 50 | #[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)] 51 | #[serde(rename_all = "snake_case")] 52 | pub enum Snip20Msg { 53 | RegisterReceive { 54 | code_hash: String, 55 | padding: Option, 56 | }, 57 | Redeem { 58 | amount: Uint128, 59 | padding: Option, 60 | }, 61 | } 62 | 63 | impl Snip20Msg { 64 | pub fn register_receive(code_hash: String) -> Self { 65 | Snip20Msg::RegisterReceive { 66 | code_hash, 67 | padding: None, // TODO add padding calculation 68 | } 69 | } 70 | 71 | pub fn redeem(amount: Uint128) -> Self { 72 | Snip20Msg::Redeem { 73 | amount, 74 | padding: None, // TODO add padding calculation 75 | } 76 | } 77 | } 78 | -------------------------------------------------------------------------------- /tests/example-receiver/src/state.rs: -------------------------------------------------------------------------------- 1 | use schemars::JsonSchema; 2 | use serde::{Deserialize, Serialize}; 3 | 4 | use cosmwasm_std::{CanonicalAddr, HumanAddr, Storage}; 5 | use cosmwasm_storage::{singleton, singleton_read, ReadonlySingleton, Singleton}; 6 | 7 | pub static CONFIG_KEY: &[u8] = b"config"; 8 | 9 | #[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)] 10 | pub struct State { 11 | pub count: i32, 12 | pub owner: CanonicalAddr, 13 | pub known_snip_20: Vec, 14 | } 15 | 16 | pub fn config(storage: &mut S) -> Singleton { 17 | singleton(storage, CONFIG_KEY) 18 | } 19 | 20 | pub fn config_read(storage: &S) -> ReadonlySingleton { 21 | singleton_read(storage, CONFIG_KEY) 22 | } 23 | -------------------------------------------------------------------------------- /tests/integration.rs: -------------------------------------------------------------------------------- 1 | #[test] 2 | #[ignore] 3 | fn empty_test() {} 4 | -------------------------------------------------------------------------------- /tests/integration.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | set -eu 4 | set -o pipefail # If anything in a pipeline fails, the pipe's exit status is a failure 5 | #set -x # Print all commands for debugging 6 | 7 | declare -a KEY=(a b c d) 8 | 9 | declare -A FROM=( 10 | [a]='-y --from a' 11 | [b]='-y --from b' 12 | [c]='-y --from c' 13 | [d]='-y --from d' 14 | ) 15 | 16 | # This means we don't need to configure the cli since it uses the preconfigured cli in the docker. 17 | # We define this as a function rather than as an alias because it has more flexible expansion behavior. 18 | # In particular, it's not possible to dynamically expand aliases, but `tx_of` dynamically executes whatever 19 | # we specify in its arguments. 20 | function secretcli() { 21 | docker exec secretdev /usr/bin/secretcli "$@" 22 | } 23 | 24 | # Just like `echo`, but prints to stderr 25 | function log() { 26 | echo "$@" >&2 27 | } 28 | 29 | # suppress all output to stdout and stderr for the command described in the arguments 30 | function silent() { 31 | "$@" >/dev/null 2>&1 32 | } 33 | 34 | # Pad the string in the first argument to 256 bytes, using spaces 35 | function pad_space() { 36 | printf '%-256s' "$1" 37 | } 38 | 39 | function assert_eq() { 40 | local left="$1" 41 | local right="$2" 42 | local message 43 | 44 | if [[ "$left" != "$right" ]]; then 45 | if [ -z ${3+x} ]; then 46 | local lineno="${BASH_LINENO[0]}" 47 | message="assertion failed on line $lineno - both sides differ. left: ${left@Q}, right: ${right@Q}" 48 | else 49 | message="$3" 50 | fi 51 | log "$message" 52 | return 1 53 | fi 54 | 55 | return 0 56 | } 57 | 58 | function assert_ne() { 59 | local left="$1" 60 | local right="$2" 61 | local message 62 | 63 | if [[ "$left" == "$right" ]]; then 64 | if [ -z ${3+x} ]; then 65 | local lineno="${BASH_LINENO[0]}" 66 | message="assertion failed on line $lineno - both sides are equal. left: ${left@Q}, right: ${right@Q}" 67 | else 68 | message="$3" 69 | fi 70 | 71 | log "$message" 72 | return 1 73 | fi 74 | 75 | return 0 76 | } 77 | 78 | declare -A ADDRESS=( 79 | [a]="$(secretcli keys show --address a)" 80 | [b]="$(secretcli keys show --address b)" 81 | [c]="$(secretcli keys show --address c)" 82 | [d]="$(secretcli keys show --address d)" 83 | ) 84 | 85 | declare -A VK=([a]='' [b]='' [c]='' [d]='') 86 | 87 | # Generate a label for a contract with a given code id 88 | # This just adds "contract_" before the code id. 89 | function label_by_id() { 90 | local id="$1" 91 | echo "contract_$id" 92 | } 93 | 94 | # Keep polling the blockchain until the tx completes. 95 | # The first argument is the tx hash. 96 | # The second argument is a message that will be logged after every failed attempt. 97 | # The tx information will be returned. 98 | function wait_for_tx() { 99 | local tx_hash="$1" 100 | local message="$2" 101 | 102 | local result 103 | 104 | log "waiting on tx: $tx_hash" 105 | # secretcli will only print to stdout when it succeeds 106 | until result="$(secretcli query tx "$tx_hash" 2>/dev/null)"; do 107 | log "$message" 108 | sleep 1 109 | done 110 | 111 | # log out-of-gas events 112 | if jq -e '.raw_log | startswith("execute contract failed: Out of gas: ") or startswith("out of gas:")' <<<"$result" >/dev/null; then 113 | log "$(jq -r '.raw_log' <<<"$result")" 114 | fi 115 | 116 | echo "$result" 117 | } 118 | 119 | # This is a wrapper around `wait_for_tx` that also decrypts the response, 120 | # and returns a nonzero status code if the tx failed 121 | function wait_for_compute_tx() { 122 | local tx_hash="$1" 123 | local message="$2" 124 | local return_value=0 125 | local result 126 | local decrypted 127 | 128 | result="$(wait_for_tx "$tx_hash" "$message")" 129 | # log "$result" 130 | if jq -e '.logs == null' <<<"$result" >/dev/null; then 131 | return_value=1 132 | fi 133 | decrypted="$(secretcli query compute tx "$tx_hash")" || return 134 | log "$decrypted" 135 | echo "$decrypted" 136 | 137 | return "$return_value" 138 | } 139 | 140 | # If the tx failed, return a nonzero status code. 141 | # The decrypted error or message will be echoed 142 | function check_tx() { 143 | local tx_hash="$1" 144 | local result 145 | local return_value=0 146 | 147 | result="$(secretcli query tx "$tx_hash")" 148 | if jq -e '.logs == null' <<<"$result" >/dev/null; then 149 | return_value=1 150 | fi 151 | decrypted="$(secretcli query compute tx "$tx_hash")" || return 152 | log "$decrypted" 153 | echo "$decrypted" 154 | 155 | return "$return_value" 156 | } 157 | 158 | # Extract the tx_hash from the output of the command 159 | function tx_of() { 160 | "$@" | jq -r '.txhash' 161 | } 162 | 163 | # Extract the output_data_as_string from the output of the command 164 | function data_of() { 165 | "$@" | jq -r '.output_data_as_string' 166 | } 167 | 168 | function get_generic_err() { 169 | jq -r '.output_error.generic_err.msg' <<<"$1" 170 | } 171 | 172 | # Send a compute transaction and return the tx hash. 173 | # All arguments to this function are passed directly to `secretcli tx compute execute`. 174 | function compute_execute() { 175 | tx_of secretcli tx compute execute "$@" 176 | } 177 | 178 | # Send a query to the contract. 179 | # All arguments to this function are passed directly to `secretcli query compute query`. 180 | function compute_query() { 181 | secretcli query compute query "$@" 182 | } 183 | 184 | function upload_code() { 185 | local directory="$1" 186 | local tx_hash 187 | local code_id 188 | 189 | tx_hash="$(tx_of secretcli tx compute store "code/$directory/contract.wasm.gz" ${FROM[a]} --gas 10000000)" 190 | code_id="$( 191 | wait_for_tx "$tx_hash" 'waiting for contract upload' | 192 | jq -r '.logs[0].events[0].attributes[] | select(.key == "code_id") | .value' 193 | )" 194 | 195 | log "uploaded contract #$code_id" 196 | 197 | echo "$code_id" 198 | } 199 | 200 | function instantiate() { 201 | local code_id="$1" 202 | local init_msg="$2" 203 | 204 | log 'sending init message:' 205 | log "${init_msg@Q}" 206 | 207 | local tx_hash 208 | tx_hash="$(tx_of secretcli tx compute instantiate "$code_id" "$init_msg" --label "$(label_by_id "$code_id")" ${FROM[a]} --gas 10000000)" 209 | wait_for_tx "$tx_hash" 'waiting for init to complete' 210 | } 211 | 212 | # This function uploads and instantiates a contract, and returns the new contract's address 213 | function create_contract() { 214 | local dir="$1" 215 | local init_msg="$2" 216 | 217 | local code_id 218 | code_id="$(upload_code "$dir")" 219 | 220 | local init_result 221 | init_result="$(instantiate "$code_id" "$init_msg")" 222 | 223 | if jq -e '.logs == null' <<<"$init_result" >/dev/null; then 224 | log "$(secretcli query compute tx "$(jq -r '.txhash' <<<"$init_result")")" 225 | return 1 226 | fi 227 | 228 | jq -r '.logs[0].events[0].attributes[] | select(.key == "contract_address") | .value' <<<"$init_result" 229 | } 230 | 231 | function deposit() { 232 | local contract_addr="$1" 233 | local key="$2" 234 | local amount="$3" 235 | 236 | local deposit_message='{"deposit":{"padding":":::::::::::::::::"}}' 237 | local tx_hash 238 | local deposit_response 239 | tx_hash="$(compute_execute "$contract_addr" "$deposit_message" --amount "${amount}uscrt" ${FROM[$key]} --gas 150000)" 240 | deposit_response="$(data_of wait_for_compute_tx "$tx_hash" "waiting for deposit to \"$key\" to process")" 241 | assert_eq "$deposit_response" "$(pad_space '{"deposit":{"status":"success"}}')" 242 | log "deposited ${amount}uscrt to \"$key\" successfully" 243 | } 244 | 245 | function get_balance() { 246 | local contract_addr="$1" 247 | local key="$2" 248 | 249 | log "querying balance for \"$key\"" 250 | local balance_query='{"balance":{"address":"'"${ADDRESS[$key]}"'","key":"'"${VK[$key]}"'"}}' 251 | local balance_response 252 | balance_response="$(compute_query "$contract_addr" "$balance_query")" 253 | log "balance response was: $balance_response" 254 | jq -r '.balance.amount' <<<"$balance_response" 255 | } 256 | 257 | # Redeem some SCRT from an account 258 | # As you can see, verifying this is happening correctly requires a lot of code 259 | # so I separated it to its own function, because it's used several times. 260 | function redeem() { 261 | local contract_addr="$1" 262 | local key="$2" 263 | local amount="$3" 264 | 265 | local redeem_message 266 | local tx_hash 267 | local redeem_tx 268 | local transfer_attributes 269 | local redeem_response 270 | 271 | log "redeeming \"$key\"" 272 | redeem_message='{"redeem":{"amount":"'"$amount"'"}}' 273 | tx_hash="$(compute_execute "$contract_addr" "$redeem_message" ${FROM[$key]} --gas 150000)" 274 | redeem_tx="$(wait_for_tx "$tx_hash" "waiting for redeem from \"$key\" to process")" 275 | transfer_attributes="$(jq -r '.logs[0].events[] | select(.type == "transfer") | .attributes' <<<"$redeem_tx")" 276 | assert_eq "$(jq -r '.[] | select(.key == "recipient") | .value' <<<"$transfer_attributes")" "${ADDRESS[$key]}" 277 | assert_eq "$(jq -r '.[] | select(.key == "amount") | .value' <<<"$transfer_attributes")" "${amount}uscrt" 278 | log "redeem response for \"$key\" returned ${amount}uscrt" 279 | 280 | redeem_response="$(data_of check_tx "$tx_hash")" 281 | assert_eq "$redeem_response" "$(pad_space '{"redeem":{"status":"success"}}')" 282 | log "redeemed ${amount} from \"$key\" successfully" 283 | } 284 | 285 | function get_token_info() { 286 | local contract_addr="$1" 287 | 288 | local token_info_query='{"token_info":{}}' 289 | compute_query "$contract_addr" "$token_info_query" 290 | } 291 | 292 | function increase_allowance() { 293 | local contract_addr="$1" 294 | local owner_key="$2" 295 | local spender_key="$3" 296 | local amount="$4" 297 | 298 | local owner_address="${ADDRESS[$owner_key]}" 299 | local spender_address="${ADDRESS[$spender_key]}" 300 | local allowance_message='{"increase_allowance":{"spender":"'"$spender_address"'","amount":"'"$amount"'"}}' 301 | local allowance_response 302 | 303 | tx_hash="$(compute_execute "$contract_addr" "$allowance_message" ${FROM[$owner_key]} --gas 150000)" 304 | allowance_response="$(data_of wait_for_compute_tx "$tx_hash" "waiting for the increase of \"$spender_key\"'s allowance to \"$owner_key\"'s funds to process")" 305 | assert_eq "$(jq -r '.increase_allowance.spender' <<<"$allowance_response")" "$spender_address" 306 | assert_eq "$(jq -r '.increase_allowance.owner' <<<"$allowance_response")" "$owner_address" 307 | jq -r '.increase_allowance.allowance' <<<"$allowance_response" 308 | log "Increased allowance given to \"$spender_key\" from \"$owner_key\" by ${amount}uscrt successfully" 309 | } 310 | 311 | function decrease_allowance() { 312 | local contract_addr="$1" 313 | local owner_key="$2" 314 | local spender_key="$3" 315 | local amount="$4" 316 | 317 | local owner_address="${ADDRESS[$owner_key]}" 318 | local spender_address="${ADDRESS[$spender_key]}" 319 | local allowance_message='{"decrease_allowance":{"spender":"'"$spender_address"'","amount":"'"$amount"'"}}' 320 | local allowance_response 321 | 322 | tx_hash="$(compute_execute "$contract_addr" "$allowance_message" ${FROM[$owner_key]} --gas 150000)" 323 | allowance_response="$(data_of wait_for_compute_tx "$tx_hash" "waiting for the decrease of \"$spender_key\"'s allowance to \"$owner_key\"'s funds to process")" 324 | assert_eq "$(jq -r '.decrease_allowance.spender' <<<"$allowance_response")" "$spender_address" 325 | assert_eq "$(jq -r '.decrease_allowance.owner' <<<"$allowance_response")" "$owner_address" 326 | jq -r '.decrease_allowance.allowance' <<<"$allowance_response" 327 | log "Decreased allowance given to \"$spender_key\" from \"$owner_key\" by ${amount}uscrt successfully" 328 | } 329 | 330 | function get_allowance() { 331 | local contract_addr="$1" 332 | local owner_key="$2" 333 | local spender_key="$3" 334 | 335 | log "querying allowance given to \"$spender_key\" by \"$owner_key\"" 336 | local owner_address="${ADDRESS[$owner_key]}" 337 | local spender_address="${ADDRESS[$spender_key]}" 338 | local allowance_query='{"allowance":{"spender":"'"$spender_address"'","owner":"'"$owner_address"'","key":"'"${VK[$owner_key]}"'"}}' 339 | local allowance_response 340 | allowance_response="$(compute_query "$contract_addr" "$allowance_query")" 341 | log "allowance response was: $allowance_response" 342 | assert_eq "$(jq -r '.allowance.spender' <<<"$allowance_response")" "$spender_address" 343 | assert_eq "$(jq -r '.allowance.owner' <<<"$allowance_response")" "$owner_address" 344 | jq -r '.allowance.allowance' <<<"$allowance_response" 345 | } 346 | 347 | function log_test_header() { 348 | log " # Starting ${FUNCNAME[1]}" 349 | } 350 | 351 | function test_viewing_key() { 352 | local contract_addr="$1" 353 | 354 | log_test_header 355 | 356 | # common variables 357 | local result 358 | local tx_hash 359 | 360 | # query balance. Should fail. 361 | local wrong_key 362 | wrong_key="$(xxd -ps <<<'wrong-key')" 363 | local balance_query 364 | local expected_error='{"viewing_key_error":{"msg":"Wrong viewing key for this address or viewing key not set"}}' 365 | for key in "${KEY[@]}"; do 366 | log "querying balance for \"$key\" with wrong viewing key" 367 | balance_query='{"balance":{"address":"'"${ADDRESS[$key]}"'","key":"'"$wrong_key"'"}}' 368 | result="$(compute_query "$contract_addr" "$balance_query")" 369 | assert_eq "$result" "$expected_error" 370 | done 371 | 372 | # Create viewing keys 373 | local create_viewing_key_message='{"create_viewing_key":{"entropy":"MyPassword123"}}' 374 | local viewing_key_response 375 | for key in "${KEY[@]}"; do 376 | log "creating viewing key for \"$key\"" 377 | tx_hash="$(compute_execute "$contract_addr" "$create_viewing_key_message" ${FROM[$key]} --gas 1400000)" 378 | viewing_key_response="$(data_of wait_for_compute_tx "$tx_hash" "waiting for viewing key for \"$key\" to be created")" 379 | VK[$key]="$(jq -er '.create_viewing_key.key' <<<"$viewing_key_response")" 380 | log "viewing key for \"$key\" set to ${VK[$key]}" 381 | if [[ "${VK[$key]}" =~ ^api_key_ ]]; then 382 | log "viewing key \"$key\" seems valid" 383 | else 384 | log 'viewing key is invalid' 385 | return 1 386 | fi 387 | done 388 | 389 | # Check that all viewing keys are different despite using the same entropy 390 | assert_ne "${VK[a]}" "${VK[b]}" 391 | assert_ne "${VK[b]}" "${VK[c]}" 392 | assert_ne "${VK[c]}" "${VK[d]}" 393 | 394 | # query balance. Should succeed. 395 | local balance_query 396 | for key in "${KEY[@]}"; do 397 | balance_query='{"balance":{"address":"'"${ADDRESS[$key]}"'","key":"'"${VK[$key]}"'"}}' 398 | log "querying balance for \"$key\" with correct viewing key" 399 | result="$(compute_query "$contract_addr" "$balance_query")" 400 | if ! silent jq -e '.balance.amount | tonumber' <<<"$result"; then 401 | log "Balance query returned unexpected response: ${result@Q}" 402 | return 1 403 | fi 404 | done 405 | 406 | # Change viewing keys 407 | local vk2_a 408 | 409 | log 'creating new viewing key for "a"' 410 | tx_hash="$(compute_execute "$contract_addr" "$create_viewing_key_message" ${FROM[a]} --gas 1400000)" 411 | viewing_key_response="$(data_of wait_for_compute_tx "$tx_hash" 'waiting for viewing key for "a" to be created')" 412 | vk2_a="$(jq -er '.create_viewing_key.key' <<<"$viewing_key_response")" 413 | log "viewing key for \"a\" set to $vk2_a" 414 | assert_ne "${VK[a]}" "$vk2_a" 415 | 416 | # query balance with old keys. Should fail. 417 | log 'querying balance for "a" with old viewing key' 418 | local balance_query_a='{"balance":{"address":"'"${ADDRESS[a]}"'","key":"'"${VK[a]}"'"}}' 419 | result="$(compute_query "$contract_addr" "$balance_query_a")" 420 | assert_eq "$result" "$expected_error" 421 | 422 | # query balance with new keys. Should succeed. 423 | log 'querying balance for "a" with new viewing key' 424 | balance_query_a='{"balance":{"address":"'"${ADDRESS[a]}"'","key":"'"$vk2_a"'"}}' 425 | result="$(compute_query "$contract_addr" "$balance_query_a")" 426 | if ! silent jq -e '.balance.amount | tonumber' <<<"$result"; then 427 | log "Balance query returned unexpected response: ${result@Q}" 428 | return 1 429 | fi 430 | 431 | # Set the vk for "a" to the original vk 432 | log 'setting the viewing key for "a" back to the first one' 433 | local set_viewing_key_message='{"set_viewing_key":{"key":"'"${VK[a]}"'"}}' 434 | tx_hash="$(compute_execute "$contract_addr" "$set_viewing_key_message" ${FROM[a]} --gas 1400000)" 435 | viewing_key_response="$(data_of wait_for_compute_tx "$tx_hash" 'waiting for viewing key for "a" to be set')" 436 | assert_eq "$viewing_key_response" "$(pad_space '{"set_viewing_key":{"status":"success"}}')" 437 | 438 | # try to use the new key - should fail 439 | log 'querying balance for "a" with new viewing key' 440 | balance_query_a='{"balance":{"address":"'"${ADDRESS[a]}"'","key":"'"$vk2_a"'"}}' 441 | result="$(compute_query "$contract_addr" "$balance_query_a")" 442 | assert_eq "$result" "$expected_error" 443 | 444 | # try to use the old key - should succeed 445 | log 'querying balance for "a" with old viewing key' 446 | balance_query_a='{"balance":{"address":"'"${ADDRESS[a]}"'","key":"'"${VK[a]}"'"}}' 447 | result="$(compute_query "$contract_addr" "$balance_query_a")" 448 | if ! silent jq -e '.balance.amount | tonumber' <<<"$result"; then 449 | log "Balance query returned unexpected response: ${result@Q}" 450 | return 1 451 | fi 452 | } 453 | 454 | function test_deposit() { 455 | local contract_addr="$1" 456 | 457 | log_test_header 458 | 459 | local tx_hash 460 | 461 | local -A deposits=([a]=1000000 [b]=2000000 [c]=3000000 [d]=4000000) 462 | for key in "${KEY[@]}"; do 463 | deposit "$contract_addr" "$key" "${deposits[$key]}" 464 | done 465 | 466 | # Query the balances of the accounts and make sure they have the right balances. 467 | for key in "${KEY[@]}"; do 468 | assert_eq "$(get_balance "$contract_addr" "$key")" "${deposits[$key]}" 469 | done 470 | 471 | # Try to overdraft 472 | local redeem_message 473 | local overdraft 474 | local redeem_response 475 | for key in "${KEY[@]}"; do 476 | overdraft="$((deposits[$key] + 1))" 477 | redeem_message='{"redeem":{"amount":"'"$overdraft"'"}}' 478 | tx_hash="$(compute_execute "$contract_addr" "$redeem_message" ${FROM[$key]} --gas 150000)" 479 | # Notice the `!` before the command - it is EXPECTED to fail. 480 | ! redeem_response="$(wait_for_compute_tx "$tx_hash" "waiting for overdraft from \"$key\" to process")" 481 | log "trying to overdraft from \"$key\" was rejected" 482 | assert_eq \ 483 | "$(get_generic_err "$redeem_response")" \ 484 | "insufficient funds to redeem: balance=${deposits[$key]}, required=$overdraft" 485 | done 486 | 487 | # Withdraw Everything 488 | for key in "${KEY[@]}"; do 489 | redeem "$contract_addr" "$key" "${deposits[$key]}" 490 | done 491 | 492 | # Check the balances again. They should all be empty 493 | for key in "${KEY[@]}"; do 494 | assert_eq "$(get_balance "$contract_addr" "$key")" 0 495 | done 496 | } 497 | 498 | function test_transfer() { 499 | local contract_addr="$1" 500 | 501 | log_test_header 502 | 503 | local tx_hash 504 | 505 | # Check "a" and "b" don't have any funds 506 | assert_eq "$(get_balance "$contract_addr" 'a')" 0 507 | assert_eq "$(get_balance "$contract_addr" 'b')" 0 508 | 509 | # Deposit to "a" 510 | deposit "$contract_addr" 'a' 1000000 511 | 512 | # Try to transfer more than "a" has 513 | log 'transferring funds from "a" to "b", but more than "a" has' 514 | local transfer_message='{"transfer":{"recipient":"'"${ADDRESS[b]}"'","amount":"1000001"}}' 515 | local transfer_response 516 | tx_hash="$(compute_execute "$contract_addr" "$transfer_message" ${FROM[a]} --gas 150000)" 517 | # Notice the `!` before the command - it is EXPECTED to fail. 518 | ! transfer_response="$(wait_for_compute_tx "$tx_hash" 'waiting for transfer from "a" to "b" to process')" 519 | log "trying to overdraft from \"a\" to transfer to \"b\" was rejected" 520 | assert_eq "$(get_generic_err "$transfer_response")" "insufficient funds: balance=1000000, required=1000001" 521 | 522 | # Check both a and b, that their last transaction is not for 1000001 uscrt 523 | local transfer_history_query 524 | local transfer_history_response 525 | local txs 526 | for key in a b; do 527 | log "querying the transfer history of \"$key\"" 528 | transfer_history_query='{"transfer_history":{"address":"'"${ADDRESS[$key]}"'","key":"'"${VK[$key]}"'","page_size":1}}' 529 | transfer_history_response="$(compute_query "$contract_addr" "$transfer_history_query")" 530 | txs="$(jq -r '.transfer_history.txs' <<<"$transfer_history_response")" 531 | silent jq -e 'length <= 1' <<<"$txs" # just make sure we're not getting a weird response 532 | if silent jq -e 'length == 1' <<<"$txs"; then 533 | assert_ne "$(jq -r '.[0].coins.amount' <<<"$txs")" 1000001 534 | fi 535 | done 536 | 537 | # Transfer from "a" to "b" 538 | log 'transferring funds from "a" to "b"' 539 | local transfer_message='{"transfer":{"recipient":"'"${ADDRESS[b]}"'","amount":"400000"}}' 540 | local transfer_response 541 | tx_hash="$(compute_execute "$contract_addr" "$transfer_message" ${FROM[a]} --gas 160000)" 542 | transfer_response="$(data_of wait_for_compute_tx "$tx_hash" 'waiting for transfer from "a" to "b" to process')" 543 | assert_eq "$transfer_response" "$(pad_space '{"transfer":{"status":"success"}}')" 544 | 545 | # Check for both "a" and "b" that they recorded the transfer 546 | local tx 547 | local -A tx_ids 548 | for key in a b; do 549 | log "querying the transfer history of \"$key\"" 550 | transfer_history_query='{"transfer_history":{"address":"'"${ADDRESS[$key]}"'","key":"'"${VK[$key]}"'","page_size":1}}' 551 | transfer_history_response="$(compute_query "$contract_addr" "$transfer_history_query")" 552 | txs="$(jq -r '.transfer_history.txs' <<<"$transfer_history_response")" 553 | silent jq -e 'length == 1' <<<"$txs" # just make sure we're not getting a weird response 554 | tx="$(jq -r '.[0]' <<<"$txs")" 555 | assert_eq "$(jq -r '.from' <<<"$tx")" "${ADDRESS[a]}" 556 | assert_eq "$(jq -r '.sender' <<<"$tx")" "${ADDRESS[a]}" 557 | assert_eq "$(jq -r '.receiver' <<<"$tx")" "${ADDRESS[b]}" 558 | assert_eq "$(jq -r '.coins.amount' <<<"$tx")" 400000 559 | assert_eq "$(jq -r '.coins.denom' <<<"$tx")" 'SSCRT' 560 | tx_ids[$key]="$(jq -r '.id' <<<"$tx")" 561 | done 562 | 563 | assert_eq "${tx_ids[a]}" "${tx_ids[b]}" 564 | log 'The transfer was recorded correctly in the transaction history' 565 | 566 | # Check that "a" has fewer funds 567 | assert_eq "$(get_balance "$contract_addr" 'a')" 600000 568 | 569 | # Check that "b" has the funds that "a" deposited 570 | assert_eq "$(get_balance "$contract_addr" 'b')" 400000 571 | 572 | # Redeem both accounts 573 | redeem "$contract_addr" a 600000 574 | redeem "$contract_addr" b 400000 575 | # Send the funds back 576 | secretcli tx send b "${ADDRESS[a]}" 400000uscrt -y -b block >/dev/null 577 | } 578 | 579 | RECEIVER_ADDRESS='' 580 | 581 | function create_receiver_contract() { 582 | local init_msg 583 | 584 | if [[ "$RECEIVER_ADDRESS" != '' ]]; then 585 | log 'Receiver contract already exists' 586 | echo "$RECEIVER_ADDRESS" 587 | return 0 588 | fi 589 | 590 | init_msg='{"count":0}' 591 | RECEIVER_ADDRESS="$(create_contract 'tests/example-receiver' "$init_msg")" 592 | 593 | log "uploaded receiver contract to $RECEIVER_ADDRESS" 594 | echo "$RECEIVER_ADDRESS" 595 | } 596 | 597 | # This function exists so that we can reset the state as much as possible between different tests 598 | function redeem_receiver() { 599 | local receiver_addr="$1" 600 | local snip20_addr="$2" 601 | local to_addr="$3" 602 | local amount="$4" 603 | 604 | local tx_hash 605 | local redeem_tx 606 | local transfer_attributes 607 | 608 | log 'fetching snip20 hash' 609 | local snip20_hash 610 | snip20_hash="$(secretcli query compute contract-hash "$snip20_addr")" 611 | 612 | local redeem_message='{"redeem":{"addr":"'"$snip20_addr"'","hash":"'"${snip20_hash:2}"'","to":"'"$to_addr"'","amount":"'"$amount"'"}}' 613 | tx_hash="$(compute_execute "$receiver_addr" "$redeem_message" ${FROM[a]} --gas 300000)" 614 | redeem_tx="$(wait_for_tx "$tx_hash" "waiting for redeem from receiver at \"$receiver_addr\" to process")" 615 | # log "$redeem_tx" 616 | transfer_attributes="$(jq -r '.logs[0].events[] | select(.type == "transfer") | .attributes' <<<"$redeem_tx")" 617 | assert_eq "$(jq -r '.[] | select(.key == "recipient") | .value' <<<"$transfer_attributes")" "$receiver_addr"$'\n'"$to_addr" 618 | assert_eq "$(jq -r '.[] | select(.key == "amount") | .value' <<<"$transfer_attributes")" "${amount}uscrt"$'\n'"${amount}uscrt" 619 | log "redeem response for \"$receiver_addr\" returned ${amount}uscrt" 620 | } 621 | 622 | function register_receiver() { 623 | local receiver_addr="$1" 624 | local snip20_addr="$2" 625 | 626 | local tx_hash 627 | 628 | log 'fetching snip20 hash' 629 | local snip20_hash 630 | snip20_hash="$(secretcli query compute contract-hash "$snip20_addr")" 631 | 632 | log 'registering with snip20' 633 | local register_message='{"register":{"reg_addr":"'"$snip20_addr"'","reg_hash":"'"${snip20_hash:2}"'"}}' 634 | tx_hash="$(compute_execute "$receiver_addr" "$register_message" ${FROM[a]} --gas 200000)" 635 | # we throw away the output since we know it's empty 636 | local register_tx 637 | register_tx="$(wait_for_compute_tx "$tx_hash" 'Waiting for receiver registration')" 638 | assert_eq \ 639 | "$(jq -r '.output_log[] | select(.type == "wasm") | .attributes[] | select(.key == "register_status") | .value' <<<"$register_tx")" \ 640 | 'success' 641 | log 'receiver registered successfully' 642 | } 643 | 644 | function test_send() { 645 | local contract_addr="$1" 646 | 647 | log_test_header 648 | 649 | local receiver_addr 650 | receiver_addr="$(create_receiver_contract)" 651 | # receiver_addr='secret17k8qt6aqd7eee3fawmtvy4vu6teqx8d7mdm49x' 652 | register_receiver "$receiver_addr" "$contract_addr" 653 | 654 | local tx_hash 655 | 656 | # Check "a" and "b" don't have any funds 657 | assert_eq "$(get_balance "$contract_addr" 'a')" 0 658 | assert_eq "$(get_balance "$contract_addr" 'b')" 0 659 | 660 | # Deposit to "a" 661 | deposit "$contract_addr" 'a' 1000000 662 | 663 | # Try to send more than "a" has 664 | log 'sending funds from "a" to "b", but more than "a" has' 665 | local send_message='{"send":{"recipient":"'"${ADDRESS[b]}"'","amount":"1000001"}}' 666 | local send_response 667 | tx_hash="$(compute_execute "$contract_addr" "$send_message" ${FROM[a]} --gas 150000)" 668 | # Notice the `!` before the command - it is EXPECTED to fail. 669 | ! send_response="$(wait_for_compute_tx "$tx_hash" 'waiting for send from "a" to "b" to process')" 670 | log "trying to overdraft from \"a\" to send to \"b\" was rejected" 671 | assert_eq "$(get_generic_err "$send_response")" "insufficient funds: balance=1000000, required=1000001" 672 | 673 | # Check both a and b, that their last transaction is not for 1000001 uscrt 674 | local transfer_history_query 675 | local transfer_history_response 676 | local txs 677 | for key in a b; do 678 | log "querying the transfer history of \"$key\"" 679 | transfer_history_query='{"transfer_history":{"address":"'"${ADDRESS[$key]}"'","key":"'"${VK[$key]}"'","page_size":1}}' 680 | transfer_history_response="$(compute_query "$contract_addr" "$transfer_history_query")" 681 | txs="$(jq -r '.transfer_history.txs' <<<"$transfer_history_response")" 682 | silent jq -e 'length <= 1' <<<"$txs" # just make sure we're not getting a weird response 683 | if silent jq -e 'length == 1' <<<"$txs"; then 684 | assert_ne "$(jq -r '.[0].coins.amount' <<<"$txs")" 1000001 685 | fi 686 | done 687 | 688 | # Query receiver state before Send 689 | local receiver_state 690 | local receiver_state_query='{"get_count":{}}' 691 | receiver_state="$(compute_query "$receiver_addr" "$receiver_state_query")" 692 | local original_count 693 | original_count="$(jq -r '.count' <<<"$receiver_state")" 694 | 695 | # Send from "a" to the receiver with message to the Receiver 696 | log 'sending funds from "a" to the Receiver, with message to the Receiver' 697 | local receiver_msg='{"increment":{}}' 698 | receiver_msg="$(base64 <<<"$receiver_msg")" 699 | local send_message='{"send":{"recipient":"'"$receiver_addr"'","amount":"400000","msg":"'$receiver_msg'"}}' 700 | local send_response 701 | tx_hash="$(compute_execute "$contract_addr" "$send_message" ${FROM[a]} --gas 300000)" 702 | send_response="$(wait_for_compute_tx "$tx_hash" 'waiting for send from "a" to the Receiver to process')" 703 | assert_eq \ 704 | "$(jq -r '.output_log[0].attributes[] | select(.key == "count") | .value' <<<"$send_response")" \ 705 | "$((original_count + 1))" 706 | log 'received send response' 707 | 708 | # Check that the receiver got the message 709 | log 'checking whether state was updated in the receiver' 710 | receiver_state_query='{"get_count":{}}' 711 | receiver_state="$(compute_query "$receiver_addr" "$receiver_state_query")" 712 | local new_count 713 | new_count="$(jq -r '.count' <<<"$receiver_state")" 714 | assert_eq "$((original_count + 1))" "$new_count" 715 | log 'receiver contract received the message' 716 | 717 | # Check that "a" recorded the transfer 718 | local tx 719 | log 'querying the transfer history of "a"' 720 | transfer_history_query='{"transfer_history":{"address":"'"${ADDRESS[a]}"'","key":"'"${VK[a]}"'","page_size":1}}' 721 | transfer_history_response="$(compute_query "$contract_addr" "$transfer_history_query")" 722 | txs="$(jq -r '.transfer_history.txs' <<<"$transfer_history_response")" 723 | silent jq -e 'length == 1' <<<"$txs" # just make sure we're not getting a weird response 724 | tx="$(jq -r '.[0]' <<<"$txs")" 725 | assert_eq "$(jq -r '.from' <<<"$tx")" "${ADDRESS[a]}" 726 | assert_eq "$(jq -r '.sender' <<<"$tx")" "${ADDRESS[a]}" 727 | assert_eq "$(jq -r '.receiver' <<<"$tx")" "$receiver_addr" 728 | assert_eq "$(jq -r '.coins.amount' <<<"$tx")" 400000 729 | assert_eq "$(jq -r '.coins.denom' <<<"$tx")" 'SSCRT' 730 | 731 | # Check that "a" has fewer funds 732 | assert_eq "$(get_balance "$contract_addr" 'a')" 600000 733 | 734 | # Test that send callback failure also denies the transfer 735 | log 'sending funds from "a" to the Receiver, with a "Fail" message to the Receiver' 736 | receiver_msg='{"fail":{}}' 737 | receiver_msg="$(base64 <<<"$receiver_msg")" 738 | send_message='{"send":{"recipient":"'"$receiver_addr"'","amount":"400000","msg":"'$receiver_msg'"}}' 739 | tx_hash="$(compute_execute "$contract_addr" "$send_message" ${FROM[a]} --gas 300000)" 740 | # Notice the `!` before the command - it is EXPECTED to fail. 741 | ! send_response="$(wait_for_compute_tx "$tx_hash" 'waiting for send from "a" to the Receiver to process')" 742 | assert_eq "$(get_generic_err "$send_response")" 'intentional failure' # This comes from the receiver contract 743 | 744 | # Check that "a" does not have fewer funds 745 | assert_eq "$(get_balance "$contract_addr" 'a')" 600000 # This is the same balance as before 746 | 747 | log 'a failure in the callback caused the transfer to roll back, as expected' 748 | 749 | # redeem both accounts 750 | redeem "$contract_addr" 'a' 600000 751 | redeem_receiver "$receiver_addr" "$contract_addr" "${ADDRESS[a]}" 400000 752 | } 753 | 754 | function test_transfer_from() { 755 | local contract_addr="$1" 756 | 757 | log_test_header 758 | 759 | local tx_hash 760 | 761 | # Check "a", "b", and "c" don't have any funds 762 | assert_eq "$(get_balance "$contract_addr" 'a')" 0 763 | assert_eq "$(get_balance "$contract_addr" 'b')" 0 764 | assert_eq "$(get_balance "$contract_addr" 'c')" 0 765 | 766 | # Check that the allowance given to "b" by "a" is zero 767 | assert_eq "$(get_allowance "$contract_addr" 'a' 'b')" 0 768 | 769 | # Deposit to "a" 770 | deposit "$contract_addr" 'a' 1000000 771 | 772 | # Make "a" give allowance to "b" 773 | assert_eq "$(increase_allowance "$contract_addr" 'a' 'b' 1000000)" 1000000 774 | assert_eq "$(get_allowance "$contract_addr" 'a' 'b')" 1000000 775 | 776 | # Try to transfer from "a", using "b" more than "a" has allowed 777 | log 'transferring funds from "a" to "c" using "b", but more than "a" has allowed' 778 | local transfer_message='{"transfer_from":{"owner":"'"${ADDRESS[a]}"'","recipient":"'"${ADDRESS[c]}"'","amount":"1000001"}}' 779 | local transfer_response 780 | tx_hash="$(compute_execute "$contract_addr" "$transfer_message" ${FROM[b]} --gas 150000)" 781 | # Notice the `!` before the command - it is EXPECTED to fail. 782 | ! transfer_response="$(wait_for_compute_tx "$tx_hash" 'waiting for transfer from "a" to "c" by "b" to process')" 783 | log "trying to overdraft from \"a\" to transfer to \"c\" using \"b\" was rejected" 784 | assert_eq "$(get_generic_err "$transfer_response")" "insufficient allowance: allowance=1000000, required=1000001" 785 | 786 | # Check both "a", "b", and "c", that their last transaction is not for 1000001 uscrt 787 | local transfer_history_query 788 | local transfer_history_response 789 | local txs 790 | for key in a b c; do 791 | log "querying the transfer history of \"$key\"" 792 | transfer_history_query='{"transfer_history":{"address":"'"${ADDRESS[$key]}"'","key":"'"${VK[$key]}"'","page_size":1}}' 793 | transfer_history_response="$(compute_query "$contract_addr" "$transfer_history_query")" 794 | txs="$(jq -r '.transfer_history.txs' <<<"$transfer_history_response")" 795 | silent jq -e 'length <= 1' <<<"$txs" # just make sure we're not getting a weird response 796 | if silent jq -e 'length == 1' <<<"$txs"; then 797 | assert_ne "$(jq -r '.[0].coins.amount' <<<"$txs")" 1000001 798 | fi 799 | done 800 | 801 | # Transfer from "a" to "c" using "b" 802 | log 'transferring funds from "a" to "c" using "b"' 803 | local transfer_message='{"transfer_from":{"owner":"'"${ADDRESS[a]}"'","recipient":"'"${ADDRESS[c]}"'","amount":"400000"}}' 804 | local transfer_response 805 | tx_hash="$(compute_execute "$contract_addr" "$transfer_message" ${FROM[b]} --gas 200000)" 806 | transfer_response="$(data_of wait_for_compute_tx "$tx_hash" 'waiting for transfer from "a" to "c" by "b" to process')" 807 | assert_eq "$transfer_response" "$(pad_space '{"transfer_from":{"status":"success"}}')" 808 | 809 | # Check for both "a", "b", and "c" that they recorded the transfer 810 | local tx 811 | local -A tx_ids 812 | for key in a b c; do 813 | log "querying the transfer history of \"$key\"" 814 | transfer_history_query='{"transfer_history":{"address":"'"${ADDRESS[$key]}"'","key":"'"${VK[$key]}"'","page_size":1}}' 815 | transfer_history_response="$(compute_query "$contract_addr" "$transfer_history_query")" 816 | txs="$(jq -r '.transfer_history.txs' <<<"$transfer_history_response")" 817 | silent jq -e 'length == 1' <<<"$txs" # just make sure we're not getting a weird response 818 | tx="$(jq -r '.[0]' <<<"$txs")" 819 | assert_eq "$(jq -r '.from' <<<"$tx")" "${ADDRESS[a]}" 820 | assert_eq "$(jq -r '.sender' <<<"$tx")" "${ADDRESS[b]}" 821 | assert_eq "$(jq -r '.receiver' <<<"$tx")" "${ADDRESS[c]}" 822 | assert_eq "$(jq -r '.coins.amount' <<<"$tx")" 400000 823 | assert_eq "$(jq -r '.coins.denom' <<<"$tx")" 'SSCRT' 824 | tx_ids[$key]="$(jq -r '.id' <<<"$tx")" 825 | done 826 | 827 | assert_eq "${tx_ids[a]}" "${tx_ids[b]}" 828 | assert_eq "${tx_ids[b]}" "${tx_ids[c]}" 829 | log 'The transfer was recorded correctly in the transaction history' 830 | 831 | # Check that "a" has fewer funds 832 | assert_eq "$(get_balance "$contract_addr" 'a')" 600000 833 | 834 | # Check that "b" has the same funds still, but less allowance 835 | assert_eq "$(get_balance "$contract_addr" 'b')" 0 836 | assert_eq "$(get_allowance "$contract_addr" 'a' 'b')" 600000 837 | 838 | # Check that "c" has the funds that "b" deposited from "a" 839 | assert_eq "$(get_balance "$contract_addr" 'c')" 400000 840 | 841 | # Redeem both accounts 842 | redeem "$contract_addr" a 600000 843 | redeem "$contract_addr" c 400000 844 | # Reset allowance 845 | assert_eq "$(decrease_allowance "$contract_addr" 'a' 'b' 600000)" 0 846 | assert_eq "$(get_allowance "$contract_addr" 'a' 'b')" 0 847 | # Send the funds back 848 | secretcli tx send c "${ADDRESS[a]}" 400000uscrt -y -b block >/dev/null 849 | } 850 | 851 | function test_send_from() { 852 | local contract_addr="$1" 853 | 854 | log_test_header 855 | 856 | local receiver_addr 857 | receiver_addr="$(create_receiver_contract)" 858 | # receiver_addr='secret17k8qt6aqd7eee3fawmtvy4vu6teqx8d7mdm49x' 859 | register_receiver "$receiver_addr" "$contract_addr" 860 | 861 | local tx_hash 862 | 863 | # Check "a" and "b" don't have any funds 864 | assert_eq "$(get_balance "$contract_addr" 'a')" 0 865 | assert_eq "$(get_balance "$contract_addr" 'b')" 0 866 | assert_eq "$(get_balance "$contract_addr" 'c')" 0 867 | 868 | # Check that the allowance given to "b" by "a" is zero 869 | assert_eq "$(get_allowance "$contract_addr" 'a' 'b')" 0 870 | 871 | # Deposit to "a" 872 | deposit "$contract_addr" 'a' 1000000 873 | 874 | # Make "a" give allowance to "b" 875 | assert_eq "$(increase_allowance "$contract_addr" 'a' 'b' 1000000)" 1000000 876 | assert_eq "$(get_allowance "$contract_addr" 'a' 'b')" 1000000 877 | 878 | # TTry to send from "a", using "b" more than "a" has allowed 879 | log 'sending funds from "a" to "c" using "b", but more than "a" has allowed' 880 | local send_message='{"send_from":{"owner":"'"${ADDRESS[a]}"'","recipient":"'"${ADDRESS[c]}"'","amount":"1000001"}}' 881 | local send_response 882 | tx_hash="$(compute_execute "$contract_addr" "$send_message" ${FROM[b]} --gas 150000)" 883 | # Notice the `!` before the command - it is EXPECTED to fail. 884 | ! send_response="$(wait_for_compute_tx "$tx_hash" 'waiting for send from "a" to "c" by "b" to process')" 885 | log "trying to overdraft from \"a\" to send to \"c\" using \"b\" was rejected" 886 | assert_eq "$(get_generic_err "$send_response")" "insufficient allowance: allowance=1000000, required=1000001" 887 | 888 | # Check both a and b, that their last transaction is not for 1000001 uscrt 889 | local transfer_history_query 890 | local transfer_history_response 891 | local txs 892 | for key in a b c; do 893 | log "querying the transfer history of \"$key\"" 894 | transfer_history_query='{"transfer_history":{"address":"'"${ADDRESS[$key]}"'","key":"'"${VK[$key]}"'","page_size":1}}' 895 | transfer_history_response="$(compute_query "$contract_addr" "$transfer_history_query")" 896 | txs="$(jq -r '.transfer_history.txs' <<<"$transfer_history_response")" 897 | silent jq -e 'length <= 1' <<<"$txs" # just make sure we're not getting a weird response 898 | if silent jq -e 'length == 1' <<<"$txs"; then 899 | assert_ne "$(jq -r '.[0].coins.amount' <<<"$txs")" 1000001 900 | fi 901 | done 902 | 903 | # Query receiver state before Send 904 | local receiver_state 905 | local receiver_state_query='{"get_count":{}}' 906 | receiver_state="$(compute_query "$receiver_addr" "$receiver_state_query")" 907 | local original_count 908 | original_count="$(jq -r '.count' <<<"$receiver_state")" 909 | 910 | # Send from "a", using "b", to the receiver with message to the Receiver 911 | log 'sending funds from "a", using "b", to the Receiver, with message to the Receiver' 912 | local receiver_msg='{"increment":{}}' 913 | receiver_msg="$(base64 <<<"$receiver_msg")" 914 | local send_message='{"send_from":{"owner":"'"${ADDRESS[a]}"'","recipient":"'"$receiver_addr"'","amount":"400000","msg":"'$receiver_msg'"}}' 915 | local send_response 916 | tx_hash="$(compute_execute "$contract_addr" "$send_message" ${FROM[b]} --gas 300000)" 917 | send_response="$(wait_for_compute_tx "$tx_hash" 'waiting for send from "a" to the Receiver to process')" 918 | assert_eq \ 919 | "$(jq -r '.output_log[0].attributes[] | select(.key == "count") | .value' <<<"$send_response")" \ 920 | "$((original_count + 1))" 921 | log 'received send response' 922 | 923 | # Check that the receiver got the message 924 | log 'checking whether state was updated in the receiver' 925 | receiver_state_query='{"get_count":{}}' 926 | receiver_state="$(compute_query "$receiver_addr" "$receiver_state_query")" 927 | local new_count 928 | new_count="$(jq -r '.count' <<<"$receiver_state")" 929 | assert_eq "$((original_count + 1))" "$new_count" 930 | log 'receiver contract received the message' 931 | 932 | # Check that "a" recorded the transfer 933 | local tx 934 | local -A tx_ids 935 | for key in a b; do 936 | log "querying the transfer history of \"$key\"" 937 | transfer_history_query='{"transfer_history":{"address":"'"${ADDRESS[$key]}"'","key":"'"${VK[$key]}"'","page_size":1}}' 938 | transfer_history_response="$(compute_query "$contract_addr" "$transfer_history_query")" 939 | txs="$(jq -r '.transfer_history.txs' <<<"$transfer_history_response")" 940 | silent jq -e 'length == 1' <<<"$txs" # just make sure we're not getting a weird response 941 | tx="$(jq -r '.[0]' <<<"$txs")" 942 | assert_eq "$(jq -r '.from' <<<"$tx")" "${ADDRESS[a]}" 943 | assert_eq "$(jq -r '.sender' <<<"$tx")" "${ADDRESS[b]}" 944 | assert_eq "$(jq -r '.receiver' <<<"$tx")" "$receiver_addr" 945 | assert_eq "$(jq -r '.coins.amount' <<<"$tx")" 400000 946 | assert_eq "$(jq -r '.coins.denom' <<<"$tx")" 'SSCRT' 947 | tx_ids[$key]="$(jq -r '.id' <<<"$tx")" 948 | done 949 | 950 | assert_eq "${tx_ids[a]}" "${tx_ids[b]}" 951 | log 'The transfer was recorded correctly in the transaction history' 952 | 953 | # Check that "a" has fewer funds 954 | assert_eq "$(get_balance "$contract_addr" 'a')" 600000 955 | 956 | # Check that "b" has the same funds still, but less allowance 957 | assert_eq "$(get_balance "$contract_addr" 'b')" 0 958 | assert_eq "$(get_allowance "$contract_addr" 'a' 'b')" 600000 959 | 960 | # Test that send callback failure also denies the transfer 961 | log 'sending funds from "a", using "b", to the Receiver, with a "Fail" message to the Receiver' 962 | receiver_msg='{"fail":{}}' 963 | receiver_msg="$(base64 <<<"$receiver_msg")" 964 | send_message='{"send_from":{"owner":"'"${ADDRESS[a]}"'", "recipient":"'"$receiver_addr"'","amount":"400000","msg":"'$receiver_msg'"}}' 965 | tx_hash="$(compute_execute "$contract_addr" "$send_message" ${FROM[b]} --gas 300000)" 966 | # Notice the `!` before the command - it is EXPECTED to fail. 967 | ! send_response="$(wait_for_compute_tx "$tx_hash" 'waiting for send from "a" to the Receiver to process')" 968 | assert_eq "$(get_generic_err "$send_response")" 'intentional failure' # This comes from the receiver contract 969 | 970 | # Check that "a" does not have fewer funds 971 | assert_eq "$(get_balance "$contract_addr" 'a')" 600000 # This is the same balance as before 972 | 973 | log 'a failure in the callback caused the transfer to roll back, as expected' 974 | 975 | # redeem both accounts 976 | redeem "$contract_addr" 'a' 600000 977 | redeem_receiver "$receiver_addr" "$contract_addr" "${ADDRESS[a]}" 400000 978 | # Reset allowance 979 | assert_eq "$(decrease_allowance "$contract_addr" 'a' 'b' 600000)" 0 980 | assert_eq "$(get_allowance "$contract_addr" 'a' 'b')" 0 981 | } 982 | 983 | function main() { 984 | log ' <####> Starting integration tests <####>' 985 | log "secretcli version in the docker image is: $(secretcli version)" 986 | 987 | local prng_seed 988 | prng_seed="$(base64 <<<'enigma-rocks')" 989 | local init_msg 990 | init_msg='{"name":"secret-secret","admin":"'"${ADDRESS[a]}"'","symbol":"SSCRT","decimals":6,"initial_balances":[],"prng_seed":"'"$prng_seed"'","config":{"public_total_supply":true}}' 991 | contract_addr="$(create_contract '.' "$init_msg")" 992 | 993 | # To make testing faster, check the logs and try to reuse the deployed contract and VKs from previous runs. 994 | # Remember to comment out the contract deployment and `test_viewing_key` if you do. 995 | # local contract_addr='secret18vd8fpwxzck93qlwghaj6arh4p7c5n8978vsyg' 996 | # VK[a]='api_key_Ij3ZwkDOTqMPnmCLGn3F2uX0pMpETw2LTyCkQ0sDMv8=' 997 | # VK[b]='api_key_hV3SlzQMC4YK50GbDrpbjicGOMQpolfPI+O6pMp6oQY=' 998 | # VK[c]='api_key_7Bv00UvQCkZ7SltDn205R0GBugq/l8GnRX6N0JIBQuA=' 999 | # VK[d]='api_key_A3Y3mFe87d2fEq90kNlPSIUSmVgoao448ZpyDAJkB/4=' 1000 | 1001 | log "contract address: $contract_addr" 1002 | 1003 | # This first test also sets the `VK[*]` global variables that are used in the other tests 1004 | test_viewing_key "$contract_addr" 1005 | test_deposit "$contract_addr" 1006 | test_transfer "$contract_addr" 1007 | test_send "$contract_addr" 1008 | test_transfer_from "$contract_addr" 1009 | test_send_from "$contract_addr" 1010 | 1011 | log 'Tests completed successfully' 1012 | 1013 | # If everything else worked, return successful status 1014 | return 0 1015 | } 1016 | 1017 | main "$@" 1018 | --------------------------------------------------------------------------------